The ability to pass subroutine references is a powerful feature. Any
subroutine you write that takes arguments can be thought of as a
general routine and the arguments provide the specifics. For example, a
subroutine that calculates the square root of a number is general, and
the argument provides the specific number to calculate the square root
from and return the result. Passing a subroutine reference is no
different in that respect, but rather than passing something static
like a number or a string, you can pass a definition of an action to
take.
The File::Find module, which defines the find() routine (we saw this
module back in February), is an excellent example. The find() routine
is a general directory tree traversal subroutine whose first argument
is a reference to a subroutine, followed by a list of directories to
traverse. The find() routine traverses the listed directories and calls
the subroutine reference for each directory entry found (it also
provides a package global variable holding the current directory entry,
for your subroutine to use). This gives the user of the module a very
flexible means of accomplishing virtually any task involving directory
traversal. Because the user supplies the intended action, the find()
routine itself need only worry about the vagaries of traversing
directories and none of the logic of detecting certain files, removing
files, renaming files, or whatever else the user wishes to do.
You already know how to create a reference to a named function, and how
to call a function through its reference -- there really isn't anything
else you need to know to write a subroutine that takes a subroutine
reference as an argument. Let's consider writing a reduce() routine in
Perl (such a routine exists in the List::Util module on CPAN, which is
implemented in C, but we will implement in Perl here).
A reduce() function takes a function reference as its first argument
and then a list of values. It "reduces" the list by applying the passed
function to the first two values, then to the result of that and the
next value in turn. Thus, it could be used to easily produce sums of
lists by supplying a function reference that simply adds two arguments
together:
#!/usr/bin/perl -w
use strict;
my @array = (1,2,3,4);
my $sum = reduce(\&add,@array);
print $sum;
sub add {
$_[0] + $_[1]
}
sub reduce {
my $sref = shift;
return @_ if @_ < 2;
my $init = shift;
for (@_) {
$init = $sref->($init, $_);
}
return $init;
}
We have taken the extra caution of simply returning the argument list
without calling the sub reference if a list of only one element is
passed to reduce().
The above produces a result of 10. Using the same reduce function we
could produce the results of multiplying through the list, or dividing
through the list, or any other action we created a function to do:
sub product { $_[0] * $_[1] }
sub divide { $_[0] / $_[1] }
my $product = reduce(\&product, @array);
my $division = reduce(\÷, @array);
We can avoid creating named subroutines by using anonymous subroutines
(just like we can have anonymous arrays and hashes, we can have
anonymous subroutines which are references to subroutines with no
names). We create such a subroutine reference by using the 'sub'
keyword followed immediately by the block of code (no name is
specified):
my $sref = sub{print "Hello World\n"};
$sref->();
So now we can use reduce() as follows:
my $sum = reduce( sub{ $_[0] + $_[1] }, @array);
my $product = reduce( sub{ $_[0] * $_[1] }, @array);
my $division = reduce( sub{ $_[0] / $_[1] }, @array);
We could shorten that further using prototypes, but that is beyond the
scope of the present article.
Next Week: Subroutine references as closures.