Closures - Page 23
July 27, 2001
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 123
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 123
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 100
$, = ",";
print &$tick1, &$tick2, &$tick1, &$tick2;
# displays 0, 100, 1, 101
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, 1002
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 19, 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.
Handling Context: an Example - Page 22
Professional Perl Programming
Assignable Subroutines - Page 24
|