Professional Perl | 17
[previous] |
Professional Perl
Optional Parameters
A prototype such as ($$$@) allows us to define a subroutine with three required parameters and any number of optional ones, but it is something of an all-or-nothing solution. It does not allow us to define a subroutine that is intended to take at least three, and no more than four, parameters. Instead we use a semicolon, to separate the mandatory parameters from the optional ones.
The following subroutine, which calculates mass, is a variation on the volume subroutine from earlier. It takes the same three dimensions and a fourth optional parameter of the density. If the density is not supplied it is assumed to be 1.
sub mass ($$$;$) {
return volume(@_) * (defined($_[3])? $_[3]: 1);
We might be tempted to use &volume to pass the local version of @_ to it directly. However, using & suppresses the prototype, so instead we pass @_ explicitly. Since mass has its own prototype we could arguably get away with it, but overriding the design of our subroutines for minor increases in efficiency is rarely a good idea.
Using a semicolon does not preclude the use of @ to gobble up any extra parameters. We can for instance define a prototype of ($$$;$@), which means three mandatory scalar parameters, followed by an optional scalar followed by an optional list. That differs from ($$$;@) in that we don't have to pass a fourth argument, but if we do it must be scalar.
We can also define optional variables. A prototype of ($$$;\$) requires three mandatory scalar parameters and an optional fourth scalar variable. For instance, we can extend the volume subroutine to place the result in a variable passed as the fourth argument, if one is supplied:
sub volume ($$$;\$) {
$volume = $_[0] * $_[1] * $_[2];
${$_[3]} = $volume if defined $_[3];
And here is how we could call it:
volume(1, 4, 9, $result); # $result ends up holding 36
Disabling Prototypes
All aspects of a subroutine's prototype are disabled if we call it using the old style prefix &. This can occasionally be useful, but is also a potential minefield of confusion. To illustrate, assume that we had redefined our capitalize subroutine to only accept a single scalar variable:
sub capitalize (\$) {
$_[0] = ucfirst (lc $_[0]);
Another programmer who had been calling the unprototyped version with a list to capitalize the first string now encounters a syntax error.
capitalize (@countries); # ERROR: not a scalar variable
One way they could fix this is to just pass in the first element. However, they can also override the prototype and continue as before by prefixing their subroutine call with an ampersand:
capitalize ($countries[0]); # pass only the first element
&capitalize @countries; # disable the prototype
Naturally this kind of behavior is somewhat dangerous, so it is not encouraged; that's the whole point of a prototype. However, the fact that an ampersand disregards a prototype means that we cannot generate a code reference for a subroutine and still enforce the prototype:
$subref = \&mysubroutine; # prototype not active in $subref
This can be a real problem. For instance, the sort function behaves differently if it is given a prototyped sort function (with a prototype of ($$)), passing the values to be compared rather than setting the global variables $a and $b. However, defining a named subroutine with a prototype and then passing a reference to it to sort doesn't work. The only way to retain a prototype on a subroutine reference is to define it as an anonymous subroutine in the first place:
# capitalize as a anonymous subroutine
$capitalize_sub = sub (\$) {
$_[0] = ucfirst (lc $_[0]);
}
And using reverse:
# an anonymous 'sort' subroutine - use as 'sort $in_reverse @list'
$in_reverse = sub ($$) {
return $_[1] <=> $_[0];
}
[previous] |
Created: April 3, 2001
Revised: April 3, 2001