Professional Perl | 20
[previous] [next] |
Professional Perl
Closures
Closures are subroutines that operate on variables created in the context in which they were defined, rather than passed in or created locally. This means that they manipulate variables outside their own definition, but within their scope. Here is a simple example of a closure at work:
$count = 0;
sub count {return ++ $count;}
print count, count, count;
# print 12
Here the subroutine count uses the variable $count. But the variable is defined outside of the subroutine, and so is defined for as long as the program runs. Nothing particularly remarkable so far, all we are doing is defining a global variable. However, what makes closures useful is that they can be used to implement a form of memory in subroutines where the variable is global inside the subroutine, but is invisible outside. Consider the following example:
{
$count = 0;
sub count {return ++ $count;}
}
print count, count, count;
# still print 12
What makes this interesting is that the variable $count is no longer directly accessible by the time we get to the print statement. Ordinarily it would have ceased to exist at the end of the block in which it is defined because it is lexical, and therefore bounded by the block's scope. However, it is referred to in the subroutine count, which is by nature a global definition. Consequently, Perl still has a reference to the variable and so it persists. The only place the reference exists is in the subroutine count, so we have effectively created a persistent and private variable inside count.
Closures get more interesting when we create them in an anonymous subroutine. If we replace the block with a subroutine definition and count with an anonymous subroutine, we end up with this:
sub make_counter ($) {
$count = shift;
return sub {return $count++;}
}
The outer subroutine make_counter accepts one scalar variable and uses it to initialize the counter variable. We then create an anonymous subroutine that refers to the variable (thus preserving it) and returns the code reference of the anonymous subroutine. We can now use make_counter to create and use any number of persistent counters, each using its own secret counter variable:
$tick1 = make_counter(0); #counts from zero
$tick2 = make_counter(100); #counts from 10
$, = ",";
print &$tick1, &$tick2, &$tick1, &$tick2;
# displays 0, 100, 1, 10
Just because the subroutine is anonymous does not mean that it cannot accept parameters - we just access the @_ array as normal. Here is a variation of make_counter that allows us to reset the counter variable by passing a number to the anonymous subroutine:
#!/usr/bin/perl
# closure.pl
use warnings;
use strict;
sub make_counter ($) {
my $count = @_?shift:0;
return sub {
$count = $_[0] if @_;
return $count++;
}
}
my $counter = make_counter(0);
foreach (1..10) {
print &$counter, "\n";
}
print "\n"; # displays 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
$counter -> (1000); #reset the counter
foreach (1..3) {
print &$counter, "\n";
}
print "\n";
# displays 1000, 1001, 100
Closures also provide a way to define objects so that their properties cannot be accessed from anywhere other than the object's own methods. The trick is to define the object's underlying data in terms of an anonymous subroutine that has access to an otherwise inaccessible hash, in the same way that the variable $count is hidden here. We will take a look at doing this in Chapter 18, along with tied objects, which would allow us to disguise a counter like the one above as a read-only scalar variable that increments each time we access it.
[previous] [next] |
Created: April 18, 2001
Revised: April 18, 2001