Prototyping Code References - Page 16
July 13, 2001
Other than $, @ (and the synonymous %),
we can supply one other basic prototype character:
&. This tells Perl that the parameter to be
supplied is a code reference to an anonymous subroutine. This is
not as far-fetched as it might seem; the sort
function accepts such an argument, for example.
Here is how we could prototype the do_list
subroutine we introduced when we covered anonymous subroutines
earlier:
sub do_list (&@) {
my ($subref, @in) = @_;
my @out;
foreach (@in) {
push @out, &$subref ($_);
}
return @out;
}
The prototype requires that the first argument be a code
reference, since the subroutine cannot perform any useful
function on its own. Either a subroutine reference or an explicit
block will satisfy the prototype; for example:
@words = ("ehT", "terceS", "egasseM");
do_list {print reverse($_[0] =~/./g), "\n"} @words;
Note how this syntax is similar to the syntax of Perl's built-in
sort, map, and grep
functions.
Subroutines as Scalar Operators
We mentioned previously that subroutines can be thought of as
user-defined list operators, and used much in the same way as
built-in functions (that also work as list operators) like
print, chomp, and so on. However, not
all of Perl's functions are list operators. Some, such as
abs, only work on scalars, and interpret their
argument in a scalar context (or simply refuse to execute) if we
try to supply a list.
Defining subroutines with a prototype of ($)
effectively converts them from being list operators to scalar
operators. Returning to our capitalize example, if
we decided that, instead of allowing it to work on lists, we want
to force it to only work on scalars, we would write it like this:
sub capitalize ($) {
$_[0] = ucfirst (lc $_[0]);
}
However, there is a sting in the tail. Before the prototype was
added this subroutine would accept a list and capitalize the
string in the first element, coincidentally returning it at the
same time. Another programmer might be using it in the following
way, without our knowledge:
capitalize (@list);
While adding the prototype prevents multiple strings being passed
in a list, an array variable still fits the prototype, as we saw
earlier. Suddenly, the previously functional
capitalize turns the passed array into a scalar
number:
@countries = ("england", "scotland", "wales");
capitalize (@countries);
The result of this is that the number '3' is passed into
capitalize. Since this is not a variable, it causes
a syntax error when we try to assign to $_[0]. If we
chose to return a result rather than modifying the passed
argument, then the code would all be perfectly valid, but badly
bugged. However, a program that is used to print 'England' might
start printing '3' instead. This is more than a little confusing,
and not intuitively easy to track down.
The key problem here is not that we are passing an array instead
of a scalar, but that we are checking for a scalar value rather
than a scalar variable, which is what we actually require. In the
next section we will see how to do that.
Parameters (Continued) - Page 15
Professional Perl Programming
Requiring Variables Rather than Values - Page 17
|