Item 27: Use hashes to pass named parameters.


Although Perl provides no method of automatically naming parameters in the subroutine to which they are passed (in other words, no " formal parameters" [6] see Item 23), there are a variety of ways that you can call functions with an argument list that provides both names and values. All of these mechanisms require that the subroutine being called do some extra work while processing the argument list. In other words, this feature isn't built into Perl either. However, this is a blessing in disguise. Different implementations of named parameters are appropriate at different times. Perl makes it easy to write and use almost any implementation you want.

[6] Not yet, anyway. An extension of the prototyping mechanism (see Item 28) to allow formal parameters is contemplated but not implemented as of this writing.

A simple approach to named parameters looks like this:

Parse named parameters with a hash.

This illustrates a simple method of using named parameters in a Perl subroutine.

 sub uses_named_params {  my %param = (      foo => 'val1',      bar => 'val2',      @_    ); 

Here are some defaults for parameters foo and bar . Then overlay input args on defaults.

 # now, use $param{foo}, $param{bar}, etc.  } 
 

You would call it in this way:

 uses_named_params(bar => 'myval1', bletch => 'myval2'); 

That wasn't very many lines of code, was it? And they were all fairly simple. This is a natural application for hashes. You may want to allow people to call a subroutine with either " positional" parameters or named parameters. The simplest thing to do in this case is to prefix parameter names with a minus sign. Check the first argument to see if it begins with a minus. If it does, process the arguments as named parameters. Here's one straightforward approach:

 sub uses_minus_params {    my @defaults = (      -foo => 'val1',      -bar => 'val2',    ); 

Param names now begin with a minus.

 if ($_[0] =~ /^-/) {      push @defaults, @_;    } else {      my $n = 1;      while (@_) {        $defaults[$n] = shift;        $n += 2;      }    } 

Read in params as a hash if first arg starts with '-' .

Or give positional params names.

 my %param = @defaults; 

Create param hash.

 # now, use $param{-foo}, $param{-bar}  } 
 

You can call this subroutine with either named or positional parameters:

 uses_minus_params(-foo => 'myval1', -xtra =>'myval2');  uses_minus_params('myval1', 'myval2'); 

Note

Stay away from single-character parameter names, for example, -e and -x . In addition to being overly terse, those are file test operators (see Item 56).


If you use this method for processing named parameters, you refer to the arguments inside your subroutine by using a hash whose keys are prefixed with minus signs, for example, $param{-foo} , $param{-bar} . Using identifiers preceded by minus signs as arguments or keys may look a little funny to you at first ("Is that really Perl?"), but Perl actually treats barewords preceded by minus signs as though they were strings beginning with minus signs. This is generally convenient , but this approach does have a couple of drawbacks. First, if you want to use the positional argument style and need to pass a negative first argument, you have to supply it as a string with leading whitespace or do something else equally ungainly. Second, although an identifier with a leading minus sign gets a little special treatment from Perl, the identifier isn't always forcibly treated as a string, as it would be to the left of => or alone inside braces. [7] Thus, you may have to quote a parameter like -print , lest it turn into -1 (while also printing the value of $_ ).

[7] More recent versions of Perl quote barewords appearing to the left of => even when prefixed by minus signs, so -print => 'foo' now works as expected.

There are plenty of applications where these issues don't present a problem, but there are some where where one or both do. In this case, you may want to resort to yet another technique, which is to pass named parameters in an anonymous hash:

 sub uses_anon_hash_params {    my @defaults = (      foo => 'val1',      bar => 'val2',    ); 

Plain old param names again.

 my %param;    if (ref $_[0] eq 'HASH') {      %param = (@defaults, %{shift()});    } else {      my $n = 1;      while (@_) {        $defaults[$n] = shift;        $n += 2;      } 

If argument is a hash ref, overlay it on defaults.

Otherwise, give positional params names.

 %param = @defaults;    }    # use $param{foo}, $param{bar}  } 

Then construct a hash.

The syntax for using named and positional parameters now looks like:

 uses_anon_hash_params( {foo => 3, test => 10} );  uses_anon_hash_params(-123, 345); 

Or even:

 uses_anon_hash_params {foo => 3, test => 10}; 

This is a pretty complicated piece of boilerplate to have at the beginning of a subroutine. If you have several subroutines that accept named parameters, you will probably want to create a subroutine that does most of the work. Here is a subroutine that implements the anonymous hash technique:

Process parameters in an anonymous hash with a subroutine.

This subroutine pre-processes named or positional arguments and returns the result as a hash reference.

 sub do_params {    my $arg = shift;    my @defaults = @{shift()};    my %param; 

The arguments are a reference to an argument list and a reference to an array (not hash) of param names and default values in positional order.

 if (ref $$arg[0] eq 'HASH') {      %param = (@defaults, %{$$arg[0]});    } else {      my $n = 1;      my @arg = @$arg;      while (@arg) {        $defaults[$n] = shift @arg;        $n += 2;      }      %param = @defaults;    }    \%param;  } 

Overlay named parameters.

Or name positional parameters.

Return a reference to a hash of params.

And here's how you might use it:

 sub uses_anon_hash_params {    my $param =      do_params(        \@_,        [foo => 'val1', bar => 'val2']      ); 

First, call do_params with a reference to the arg list and an array of defaults.

do_params returns a reference to a hash.

 for (keys %$param) {      print "$_: $$param{$_}\n";    }  } 

Now, use $$param{foo} , $$param{bar} , etc.

Each of the techniques illustrated here has its own advantages and drawbacks. Use the technique that best suits your application, or, if none is quite right, adapt one as necessary.



Effective Perl Programming. Writing Better Programs with Perl
Effective Perl Programming: Writing Better Programs with Perl
ISBN: 0201419750
EAN: 2147483647
Year: 1996
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net