(Updated 20041017: Added final, put-it-all-togther example.)
While closures are almost always created via anonymous subroutines
(a la sub { ... }) at the lowest level, the real
fun only comes about when you delegate this chore to some kind helper
function that builds anonymous subroutines for you. Thus, in heavily
functional code, you usually don't use sub { } yourself
but instead rely upon a library of helpers to do the grunt work for
you.
For example, here's a function that builds functions to append
a given suffix to strings:
sub make_suffix_appender {
my $suffix = shift;
return sub {
return $_[0] . $suffix;
}
}
It takes a given suffix, remembers it, and returns a new function
that will apply the remembered suffix to any string we give it:
my $bang_appender = make_suffix_appender("!");
print $bang_appender->("Hello"); # Hello!
print $bang_appender->("Dude"); # Dude!
We can have even more fun if we create higher-order functions that
remember other functions in their closures. Here's a pair of
functions that can be used to apply other functions to a list of
arguments as a whole or to each argument, one at a time:
sub apply {
my $fn = shift;
return sub {
$fn->(@_);
}
}
sub apply_to_each {
my $fn = shift;
return sub {
map { $fn->($_) } @_;
}
}
We might use them like so:
my $upcase = apply( sub { uc("@_") } );
print $upcase->("Hello"); # HELLO
print $upcase->("Hello", "world"); # HELLO WORLD
my $parenify_all = apply( sub { "(@_)" } );
my $parenify_each = apply_to_each( $parenify_all );
print $parenify_all ->("Hello", "world"); # (Hello world)
print $parenify_each->("Hello", "world"); # (Hello)(world)
We can also capture regex values in closures to good effect:
sub make_regex_matcher {
my $regex = shift;
return sub {
local $_ = $_[0];
/$regex/g;
}
}
Now we can use the helper function to build "regex matchers":
my $digit_matcher = make_regex_matcher( qr/(\d+)/ );
my @digits = $digit_matcher->( "123-blah-45-6789" );
print "@digits"; # 123 45 6789
my $word_matcher = make_regex_matcher( qr/(\w+)/ );
my @words = $word_matcher->( "123-blah-45-6789" );
print "@words"; # 123 blah 45 6789
Why would we want to make all these little functions? Because we
can glue them together to make complicated things happen.
One of the most fundamental kinds of glue is composition, which
turns individual functions into function pipelines:
sub compose2 {
my ($f, $g) = @_;
return sub {
$f->( $g->( @_ ) );
}
};
use List::Util qw( reduce );
sub compose {
no warnings qw( once );
reduce { compose2($a,$b) } @_;
}
Now let's glue some of our earlier functions together to make
some simple pipelines:
my $up_banger = compose($bang_appender, $upcase);
print $up_banger->("Hello"); # HELLO!
my $upcase_words = compose($upcase, $word_matcher);
print $upcase_words->( "123-blah-45-6789" );
# 123 BLAH 45 6789
my $parenify_words = compose($parenify_each, $word_matcher);
print $parenify_words->( "123-blah-45-6789" );
# (123)(blah)(45)(6789)
Also, note that we don't need to store our pipelines in a variable
before we use them. We can call them on the fly:
print compose($parenify_each, $word_matcher)->("a b c");
# (a)(b)(c)
Note that when building pipelines using compose, the input
data flows through the functions right-to-left (following mathematical
convention). Watch what happens
if we parenify first and then match for words:
print compose($word_matcher, $parenify_each)->("a b c");
# abc
As a final example, let's build 27 different pipeline combinations
from our earlier collection of functions and see what each pipeline
does when applied to a reference string. To help us out, let's first
build id – the identity function – which
simply returns its input as its output. We'll use it to pass
data through stages of our pipeline unchanged.
my $id = sub { wantarray ? @_ : $_[0] };
And now, our pipeline playground:
my $fs = 0;
for my $f ($id, $parenify_all, $parenify_each) {
my $fd = (qw( id- pAl pEa ))[$fs++];
my $gs = 0;
for my $g ($id, $word_matcher, $digit_matcher) {
my $gd = (qw( id- wrd dig ))[$gs++];
my $hs = 0;
for my $h ($id, $upcase, $bang_appender) {
my $hd = (qw( id- up- bng ))[$hs++];
print "$fd-$gd-$hd: ",
compose($f,$g,$h)->("Eat more 3.14");
}
}
}
Here's the output:
id--id--id-: Eat more 3.14
id--id--up-: EAT MORE 3.14
id--id--bng: Eat more 3.14!
id--wrd-id-: Eatmore314
id--wrd-up-: EATMORE314
id--wrd-bng: Eatmore314
id--dig-id-: 314
id--dig-up-: 314
id--dig-bng: 314
pAl-id--id-: (Eat more 3.14)
pAl-id--up-: (EAT MORE 3.14)
pAl-id--bng: (Eat more 3.14!)
pAl-wrd-id-: (Eat more 3 14)
pAl-wrd-up-: (EAT MORE 3 14)
pAl-wrd-bng: (Eat more 3 14)
pAl-dig-id-: (3 14)
pAl-dig-up-: (3 14)
pAl-dig-bng: (3 14)
pEa-id--id-: (Eat more 3.14)
pEa-id--up-: (EAT MORE 3.14)
pEa-id--bng: (Eat more 3.14!)
pEa-wrd-id-: (Eat)(more)(3)(14)
pEa-wrd-up-: (EAT)(MORE)(3)(14)
pEa-wrd-bng: (Eat)(more)(3)(14)
pEa-dig-id-: (3)(14)
pEa-dig-up-: (3)(14)
pEa-dig-bng: (3)(14)
I hope this gives you some idea of how you can create and benefit from closures
without directly having to create anonymous subroutines. Just create
helper functions to do the work for you.
Cheers, Tom
P.S. For a more practical fun-with-closures example, see Re: Redirecting STDOUT from internal function with 5.6.1 restrictions, which
shows how to capture output from a filehandle (such as STDOUT or STDERR)
temporarily.
|