Optional Parameters - Page 18
July 13, 2001
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 pass in just 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];
}
[Lines 1 and 2 above are one line. They have been split for
formatting purposes.]
Requiring Variables Rather than Values - Page 17
Professional Perl Programming
Returning Values from Subroutines - Page 19
|