Subroutines in Perl

Subroutines let programmers extend the Perl language…at least in theory. There are certain pitfalls for which you need to be alert. This article, the first of three parts, will warn you about those pitfalls and help you avoid them. It is excerpted from chapter nine of the book Perl Best Practices, written by Damian Conway (O’Reilly; ISBN: 0596001738). Copyright © 2006 O’Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O’Reilly Media.

If you have a procedure with ten parameters, you probably missed some. —Alan Perlis

Subroutines are one of the two primary problem-decomposition tools available in Perl, modules being the other. They provide a convenient and familiar way to break a large task down into pieces that are small enough to understand, concise enough to implement, focused enough to test, and simple enough to debug.

In effect, subroutines allow programmers to extend the Perl language, creating useful new behaviours with sensible names. Having written a subroutine, you can immediately forget about its internals, and focus solely on the abstracted process or function it implements.

So the extensive use of subroutines helps to make a program more modular, which in turn makes it more robust and maintainable. Subroutines also make it possible to structure the actions of programs hierarchically, at increasingly high levels of abstraction, which improves the readability of the resulting code.

That’s the theory, at least. In practice, there are plenty of ways that using subroutines can make code less robust, buggier, less concise, slower, and harder to understand. The guidelines in this chapter focus on avoiding those outcomes.

Call Syntax


Call subroutines with parentheses but without a leading &.


It’s possible to call a subroutine without parentheses, if it has already been declared in the current namespace:

  sub coerce;

  # and later…

  my $expected_count = coerce $input, $INTEGER, $ROUND_ZERO;

But that approach can quickly become much harder to understand:

  fix my $gaze, upon each %suspect;

More importantly, leaving off the parentheses on subroutines makes them harder to distinguish from builtins, and therefore increases the mental search space when the reader is confronted with either type of construct. Your code will be easier to read and understand if the subroutines always use parentheses and the built-in functions always don’t:

  my $expected_count = coerce($input, $INTEGER, $ROUND_ZERO);

  fix(my $gaze, upon(each %suspect));

Some programmers still prefer to call a subroutine using the ancient Perl 4 syntax, with an ampersand before the subroutine name:

  &coerce($input, $INTEGER, $ROUND_ZERO);

  &fix(my $gaze, &upon(each %suspect));

Perl 5 does support that syntax, but nowadays it’s unnecessarily cluttered. Barewords is forbidden under use strict , so there are far fewer situations in which a subroutine call has to be disambiguated.

On the other hand, the ampersand itself is visually ambiguous; it can also signify a bitwise AND operator, depending on context. And context can be extremely subtle:

  $curr_pos  = tell &get_mask();    # means: tell(get_mask() )
 
$curr_time = time &get_mask();   # means: time() & get_mask(
)

Prefixing with & can also lead to other subtle (but radical) differences in behaviour:

  sub fix {
      my (@args) = @_ ? @_ : $_;   # Default to fixing $_ if no args provided

      # Fix each argument by grammatically transforming it and then printing it…
     
for my $arg (@args) {
          
$arg =~ s/A the b/some/xms;
         
$arg =~ s/e z/es/xms;
         
print $arg;
      }

      return;
  }

  # and later…

  &fix(‘the race’);   # Works as expected, prints: ‘some races’
 
for (‘the gaze’, ‘the adhesive’) {
     
&fix;          # Doesn’t work as expected: looks like it should fix($_),
                    # but actually means fix(@_), using this scope’s @_!
                    # See the ‘perlsub’ manpage for details
 
}

All in all, it’s clearer, less ambiguous, and less error-prone to reserve the &subname syntax for taking references to named subroutines:

  set_error_handler( &log_error );

Just use parentheses to indicate a subroutine call:

  coerce($input, $INTEGER, $ROUND_ZERO);

  fix( my $gaze, upon(each %suspect) );

  $curr_pos  = tell get_mask();
  $curr_time = time & get_mask();

And always use the parentheses when calling a subroutine, even when the subroutine takes no arguments (like get_mask()). That way it’s immediately obvious that you intend a subroutine call:

  curr_obj()->update($status);   # Call curr_obj() to get an object,
                                 # then call the update()method on that object

and not a typename:

curr_obj->update($status) ;           # Maybe the same (if currobj() already declared),
                                    # otherwise call update() on class ‘curr_obj’

{mospagebreak title=Homonyms} 


Don’t give subroutines the same names as built-in functions.


If you declare a subroutine with the same name as a built-in function, subsequent invocations of that name will still call the builtin…except when occasionally they don’t. For example:   

  sub lock {
     
my ($file) = @_;
     
return flock $file, LOCK_SH;
 
}

  sub link {
     
my ($text, $url) = @_;
     
return qq{<a href="$url">$text</a>};
  }

  lock($file);                     # Calls ‘lock’ subroutine; built-in ‘lock’ hidden
 
print link($text, $text_url);   
# Calls built-in ‘link'; ‘link’ subroutine hidden

Perl considers some of its builtins (like link ) to be “more built-in” than others (like lock ), and chooses accordingly whether to call your subroutine of the same name. If the builtin is “strongly built-in”, an ambiguous call will invoke it, in preference to any subroutine of the same name. On the other hand, if the builtin is “weakly built-in”, an ambiguous call will invoke the subroutine of the same name instead.

Even if these subroutines did always work as expected, it’s simply too hard to main tain code where the program-specific subroutines and the language’s keywords overlap:

  sub crypt { return "You’re in the tomb of @_n"     }
  sub map   { return "You have found a map of @_n"  }
  sub chop  { return "You have chopped @_n"     }
  sub close { return "The @_ is now closedn" }
  sub hex   { return "A hex has been cast on @_n"     }

  print crypt( qw( Vlad Tsepes ) );        # Subroutine or builtin?

  for my $reward (qw( treasure danger) ) {
     
print map($reward, ‘in’, $location); # Subroutine or builtin
 
}

  print hex(‘the Demon’);                  # Subroutine or builtin
 
print chop(‘the Demon’);                 # Subroutine or builtin
?

There is an inexhaustible supply of subroutine names available; names that are more descriptive and unambiguous. Use them:

  sub in_crypt        { return "You’re in the tomb of @_n"  }
  sub find_map        { return "You have found a map of @_n"  }
  sub chop_at         { return "You have chopped @_n"    }
  sub close_the       { return "The @_ is now closedn"      }
  sub hex_upon        { return "A hex has been cast on @_n" }

  print in_crypt( qw( Vlad Tsepes ) );

  for my $reward (qw( treasure danger )) {
     
print find_map($reward, ‘in’, $location);
  }

  print hex_upon(‘the Demon’);
  print chop_at(‘the Demon’);

{mospagebreak title=Argument Lists}


Always unpack @_ first.


Subroutines always receive their arguments in the @_ array. But accessing them via $_[0], $_[1], etc. directly is almost always a Very Bad Idea. For a start, it makes the code far less self-documenting:

  Readonly my $SPACE => q{ };

  # Pad a string with whitespace…
 
sub padded {
     
# Compute the left and right indents required…
     
my $gap   = $_[1] – length $_[0] ;
      my $left  = $_[2] ? int($gap/2) : 0;
      my $right = $gap – $left;

      # Insert that many spaces fore and aft…
     
return $SPACE x $left
           . $_[0]
           . $SPACE x $right;
 
}

Using “numbered parameters” like this makes it difficult to determine what each argument is used for, whether they’re being used in the correct order, and whether the computation they’re used in is algorithmically sane. Compare the previous ver sion to this one:

  sub padded {
      my ($text, $cols_count, $want_centering) = @_;

      # Compute the left and right indents required…
     
my $gap   = $cols_count – length $text;
      my $left  = $want_centering ? int($gap/2) : 0;
      my $right = $gap – $left;

      # Insert that many spaces fore and aft…
     
return $SPACE x $left
           . $text
           . $SPACE x $right;
  }

Here the first line unpacks the argument array to give each parameter a sensible name. In the process, that assignment also documents the expected order and intended purpose of each parameter. The sensible parameter names also make it easier to verify that the computation of $left and $right is correct.

A mistake when using numbered parameters:

  my $gap   = $_[1] – length $_[2] ;
  my $left  = $_[0] ? int($gap/2) : 0;
  my $right = $gap – $left;

is much harder to identify than when named variables are in the wrong places:

  my $gap   = $cols_count – length $want_centering;
  my $left  = $text ? int($gap/2) : 0;
  my $right = $gap – $left;

Moreover, it’s easy to forget that each element of @_ is an alias for the original argument; that changing $_[0] changes the variable containing that argument:

  # Trim some text and put a "box" around it…
 
sub boxed {
      $_[0] =~ s{A s+ | s+ z}{}gxms;
      return "[$_[0]]";
 
}

Unpacking the argument list creates a copy, so it’s far less likely that the original arguments will be inadvertently modified:

  # Trim some text and put a "box" around it…
 
sub boxed
{
      my ($text) = @_;

      $text =~ s{A s+ | s+ z}{}gxms;
      return "[$text]";
  }

It’s acceptable to unpack the argument list using a single list assignment as the first line of the subroutine:

  sub padded {
      my ($text, $cols_count, $want_centering) = @_;

      # [Use parameters here, as before]
 
}

Alternatively, you can use a series of separate shift calls as the subroutine’s first “paragraph”:

  sub padded {
      my $text           = shift;
      my $cols_count     = shift;
      my $want_centering = shift;

      # [Use parameters here, as before]
 
}

The list-assignment version is more concise, and it keeps the parameters together in a horizontal list, which enhances readability, provided that the number of parameters is small.

The shift -based version is preferable, though, whenever one or more arguments has to be sanity-checked or needs to be documented with a trailing comment:

  sub padded {
      my $text           = _check_non_empty(shift);
      my $cols_count     = _limit_to_positive(shift);
      my $want_centering = shift;

      # [Use parameters here, as before]
 
}

Note the use of utility subroutines (see “Utility Subroutines” in Chapter 3) to perform the necessary argument verification and adjustment. Each such subroutine acts like a filter: it expects a single argument, checks it, and returns the argument value if the test succeeds. If the test fails, the verification subroutine may either return a default value instead, or call croak() to throw an exception (see Chapter 13). Because of that second possibility, verification subroutines should be defined in the same package as the subroutines whose arguments they are checking.

This approach to argument verification produces very readable code, and scales well as the tests become more onerous. But it may be too expensive to use within small, frequently called subroutines, in which case the arguments should be unpacked in a list assignment and then tested directly:

  sub padded {
     
my ($text, $cols_count, $want_centering) = @_;
     
croak  q{Can’t pad undefined text}    if !defined $text;
     
croak qq{Can’t pad to $cols_count columns} if $cols_count <= 0;

      # [Use parameters here, as before]
 
}

The only circumstances in which leaving a subroutine’s arguments in @_ is appropriate is when the subroutine:

  • Is short and simple
  • Clearly doesn’t modify its arguments in any way
  • Only refers to its arguments collectively (i.e., doesn’t index @_ )
  • Refers to @_ only a small number of times (preferably once) 
  • Needs to be efficient

This is usually the case only in “wrapper” subroutines:

  # Implement the Perl 6 print+newline function…
 
sub say {
     
return print @_, "n";
  }

  # and later…

  say( ‘Hello world!’ );
  say( ‘Greetings to you, people of Earth!’ );

In this example, copying the contents of @_ to a lexical variable and then immediately passing those contents to print would be wasteful.

{mospagebreak title=Named Arguments}


Use a hash of named arguments for any subroutine that has more than three parameters.


Better still, use named arguments for any subroutine that is ever likely to have more than three parameters.

Named arguments replace the need to remember an ordering (which humans are comparatively poor at) with the need to remember names (which humans are relatively good at). Names are especially advantageous when a subroutine has many optional arguments—such as flags or configuration switches—only a few of which may be needed for any particular invocation.

Named arguments should always be passed to a subroutine inside a single hash, like so:

  sub padded {
      my ($arg_ref) = @_;

      my $gap   = $arg_ref->{cols} – length $arg_ref->{text};
     
my $left  = $arg_ref->{centered} ? int($gap/2) : 0;
     
my $right = $gap – $left;

      return $arg_ref->{filler} x $left
            
. $arg_ref->{text}
            
. $arg_ref->{filler} x $right;
  }

  # and then…
 
for my $line (@lines) {
     
$line = padded({ text=>$line, cols=>20, centered=>1, filler=>$SPACE });
  }

As tempting as it may be, don’t pass them as a list of raw name/value pairs:

  sub padded {
      my %arg = @_;

      my $gap   = $arg{cols} – length $arg{text};
     
my $left  = $arg{centered} ? int($gap/2) : 0;
     
my $right = $gap – $left;

      return $arg{filler} x $left
            
. $arg{text}
            
. $arg{filler} x $right;
  }

  # and then…
 
for my $line (@lines) {
     
$line = padded( text=>$line, cols=>20, centered=>1, filler=>$SPACE );
  }

Requiring the named arguments to be specified inside a hash ensures that any mis match, such as:

  $line = padded({text=>$line, cols=>20..21, centered=>1, filler=>$SPACE});

will be reported (usually at compile time) in the caller’s context:

  Odd number of elements in anonymous hash at demo.pl line 42

Passing those arguments as raw pairs:

  $line = padded(text=>$line, cols=>20..21, centered=>1, filler=>$SPACE);

would cause the exception to be thrown at run time, and from the line inside the subroutine where the odd number of arguments were unpacked and assigned to a hash:

  Odd number of elements in hash assignment at Text/Manip.pm line 1876

It is okay to mix positional and named arguments, if there are always one or two main arguments to the subroutine (e.g., the string that padded() is supposed to pad) and the remaining arguments are merely configuration options of some kind. In any case, when there are both positional arguments and named options, the unnamed positionals should come first, followed by a single reference to a hash containing the named options. For example:

  sub padded {
      my ($text, $arg_ref) = @_;

      my $gap   = $arg_ref->{cols} – length $text;
     
my $left  = $arg_ref->{centered} ? int($gap/2) : 0;
     
my $right = $gap – $left;

      return $arg_ref->{filler} x $left . $text . $arg_ref->{filler} x $right;
  }

  # and then…
 
for my $line (@lines) {
     
$line = padded( $line, {cols=>20, centered=>1, filler=>$SPACE} );
  }

Note that using this approach also has a slight advantage in maintainability: it sets the options more clearly apart from the main positional argument.

By the way, you or your team might feel that three is not the most appropriate threshold for deciding to use named arguments, but try to avoid significantly larger values of “three”. Most of the advantages of named arguments will be lost if you still have to plough through five or six positional arguments first.

Please check back next week for the continuation of this article. 

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort