in reply to Ways to implement a closure
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:
It takes a given suffix, remembers it, and returns a new function that will apply the remembered suffix to any string we give it:sub make_suffix_appender { my $suffix = shift; return sub { return $_[0] . $suffix; } }
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:my $bang_appender = make_suffix_appender("!"); print $bang_appender->("Hello"); # Hello! print $bang_appender->("Dude"); # Dude!
We might use them like so:sub apply { my $fn = shift; return sub { $fn->(@_); } } sub apply_to_each { my $fn = shift; return sub { map { $fn->($_) } @_; } }
We can also capture regex values in closures to good effect: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)
Now we can use the helper function to build "regex matchers":sub make_regex_matcher { my $regex = shift; return sub { local $_ = $_[0]; /$regex/g; } }
Why would we want to make all these little functions? Because we can glue them together to make complicated things happen.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
One of the most fundamental kinds of glue is composition, which turns individual functions into function pipelines:
Now let's glue some of our earlier functions together to make some simple 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) } @_; }
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: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)
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($parenify_each, $word_matcher)->("a b c"); # (a)(b)(c)
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.print compose($word_matcher, $parenify_each)->("a b c"); # abc
And now, our pipeline playground:my $id = sub { wantarray ? @_ : $_[0] };
Here's the output: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"); } } }
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.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)
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.
Tom Moertel : Blog / Talks / CPAN / LectroTest / PXSL / Coffee / Movie Rating Decoder
|
---|
Replies are listed 'Best First'. | |
---|---|
Shameless praise
by pernod (Chaplain) on Oct 16, 2004 at 18:09 UTC | |
Re^2: Ways to implement a closure
by tos (Deacon) on Oct 18, 2004 at 08:51 UTC |