Professional Perl | 14
[previous] |
Professional Perl
Named Parameters
Unlike other languages such as C or Java, Perl does not have any way to define formal parameter names for subroutines. The closest it gets is prototypes combined with retrieving parameters as lexical variables, as in:
sub surname {
my ($scalar1, $scalar2, @listarg) = @_;
...
}
However, we can implement named parameters using a hash. This provides an elegant way to pass in parameters without having to define them formally. The trick is to assign the @_ array to a hash variable. This converts the passed list into key-value pairs:
sub volume {
my %param = @_;
return $param{'height'} * $param{'width'} * $param{'length'};
}
The disadvantage of this approach is that we have to name all the parameters that we pass. It is also slower, since hashes are inherently slower than arrays in use. The advantage is that we can add more parameters without forcing the caller to supply parameters that are not needed. Of course, it also falls upon us to actually check the arguments passed and complain if the caller sends us arguments that we do not use.
We can call this subroutine using the => operator to make it clear that we are passing named parameters:
volume (height => 1, width => 4, length => 9);
We can also write the subroutine so that it accepts both named parameters and a simple list. One common technique borrowed from UNIX command line switches is to prefix named arguments with a minus, to distinguish them from unnamed arguments. To determine how the subroutine has been called, we just check the first character of the first parameter to see if it is a minus:
sub volume {
my %param;
if ($_[0]=~/^-/) { # if the first argument starts '-', assume named
# arguments
while (@_) {
my ($key, $value)=(shift, shift);
# check all names are legal ones
die "Invalid name '$key'"
if $key!~/^-(height|width|length|color|density)$/;
$key =~ s/^-//; #remove leading minus
$param{$key} = $value;
}
} else {
# no '-' on first argument - assume list arguments
$param{'height'} = shift;
$param{'width'} = shift;
$param{'length'} = shift;
}
foreach ('height', 'width', 'length') {
unless (defined $param{$_}) {
warn "Undefined $_, assuming 1";
$param{$_} = 1;
}
}
return $param{'height'} * $param{'width'} * $param{'length'};
}
In this version of the volume subroutine we handle both simple and named parameters. For named parameters we have also taken advantage of the fact that we know the names of the parameters to report a handy informative warning if any of them are undefined.
Named parameters allow us to create a common set of parameters and then add or override parameters. This makes use of the fact that if we define a hash key twice, the second definition overrides the first:
# define some default parameters
%default = (-height => 1, -width => 4, -length => 9);
# use default
print volume(%default);
# override default
print volume(%default, -length => 16);
print volume(%default, -width => 6, -length => 10);
# specify additional parameters
print volume(%default, -color => "red", -density => "13.4");
Before leaving the subject of named parameters, it is worth briefly mentioning the Alias module, available from CPAN. Alias provides the subroutines alias and attr, which generates aliases from a list of key-value pairs. Both subroutines use typeglobs to do the job.
The alias subroutine takes a list of key-value pairs as its argument, and is therefore suited to subroutines. The type of variable defined by the alias is determined by the type of value it is aliased to; a string creates a scalar, a list creates an array. Here is yet another volume subroutine that uses alias:
#!/usr/bin/perl
# volalias.pl
use warnings;
use strict;
no strict 'vars';
use Alias;
# subroutine using 'alias'
sub volume {
alias @_;
return $height * $width * $length;
}
# a call to the subroutine
print volume(height => 1, length => 9, color => 'red', width => 4);
# aliased variables visible here
print " = $height x $width x $length \n";
However, alias suffers from three serious deficiencies. The first is that it is not compatible with strict vars; if we want strict variables we will have to declare all the aliased variables with use vars or (preferably) our. Another is that alias creates global aliases that persist outside the subroutine, which is not conducive to good programming. The third is that if we only use the variable once we'll get a warning from Perl about it. The script above does not do that because of the last line. Comment out that line, and all three variables will generate used only once warnings.
attr takes a reference to a hash and creates aliases based on the keys and values in it. attr $hashref is similar to alias %{$hashref}, but localizes the aliases that it creates. It is ideal to use with object methods for objects based around hashes since each object attribute becomes a variable (hence the name):
#!/usr/bin/perl
# attr.pl
use warnings;
use strict;
{
package Testing;
use Alias;
no strict 'vars'; # to avoid declaring vars
sub new {
return bless {
count => [3, 2, 1],
message => 'Liftoff!',
}, shift;
}
sub change {
# define @count and $message locally
attr(shift);
# this relies on 'shift' being a hash reference
@count = (1, 2, 3);
$message = 'Testing, Testing';
}
}
my $object = new Testing;
print "Before: ", $object->{'message'}, "\n";
$object->change;
print "After : ", $object->{'message'}, "\n";
print $Testing::message, "\n"; # warning - 'attr' vars do not persist
close Testing::count;
We can also define 'constants' with the const subroutine. This is actually just an alias for alias (it's even defined using alias inside the module, and must be imported explicitly:
# const.pl
use Alias qw(const); # add 'alias' and/or 'attr' too, if needed
const MESSAGE => 'Testing';
print $MESSAGE, "\n";
Attempting to modify the value of a constant produces an error:
# ERROR: produce 'Modification of a read-only value attempted at ...'
$MESSAGE = 'Liftoff!';
The Alias module also provides several customization features, mainly for the attr subroutine, which allows us to control what gets aliased and how. Refer to 'perldoc Alias' for a rundown and some more examples.
[previous] |
Revised: March 22, 2001
Created: March 22, 2001