Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Ways to implement a closure

by sequel (Novice)
on Oct 15, 2004 at 18:37 UTC ( [id://399610]=perlquestion: print w/replies, xml ) Need Help??

sequel has asked for the wisdom of the Perl Monks concerning the following question:

Hello All, I wonder is there any other way to implement a closure other than anonymous subroutine?? Sequel

Replies are listed 'Best First'.
Re: Ways to implement a closure
by tmoertel (Chaplain) on Oct 15, 2004 at 21:07 UTC
    (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.

      tmoertel, your functional programming tutorials in Perl continuosly boggles my mind. If you keep up like this I'll probably end up learning all I need on functional programming before I ever get around to learning Haskell.

      Thank you very much!

      fanboy pernod
      --
      Mischief. Mayhem. Soap.

      Great stuff. This really broadens my perl-horizon.

      Thanks, tos


      Is simplicity best or simply the easiest Martin L. Gore
Re: Ways to implement a closure
by dragonchild (Archbishop) on Oct 15, 2004 at 18:42 UTC
    { my $counter = 0; sub get_next_counter { ++$counter } }

    get_next_counter() is "closed over" $counter. So, in this instance, get_next_counter() is a closure that is also a named subroutine.

    Does that help?

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: Ways to implement a closure
by ikegami (Patriarch) on Oct 15, 2004 at 19:24 UTC

    A closure is a type of subroutine, so if you rule out anonymous subroutines, that only leaves named subroutines. dragonchild already posted an example of that.

    However, two things come to mind which are similiar to closures: tied variables and objects. Both can be used to execute code on hidden or encapsulated data. Refer to perlobj and perltie.

      Some people define any data structure that closes over an environment to be a closure. (Some people don't.) In which case this is a closure:
      sub demonstration { my $demonstration_data = { demo_data => shift }; return { demonstration_data => $demonstration_data, }; }
      because $demonstration_data knows to stick around even though the function ended.

      An example of a language where the data would not be expected to stick around is C - if you don't watch out the demonstration data would be stuck on the stack, and a few function calls later would not be there to be found.

        I see. Well there you go, yet another way!

        Nit: that code works in C (given some imagined Hash class), and the data does stick around:

        Hash<...>* demonstration<...>(... a) Hash<...>* hash_ptr = new Hash<...>("demo_data", a); return new Hash<...>( "demonstration_data", hash_ptr ); }

        I think you meant this:

        sub demonstration { my %demonstration_data = ( demo_data => shift ); return { demonstration_data => \%demonstration_data, }; }

        Which wouldn't work in C:

        Hash<...>* demonstration<...>(... a) Hash<...> hash("demo_data", a); return new Hash<...>( "demonstration_data", \hash XXX BUG ); }

        I disagree with it being a closure, even using the broader definition of a closure.

        If you mean that a closure (in Perl) is anything that increases reference count anywhere, then I understand you, but must say that that's the broadest definition of a closure I've ever heard. With that definition though, this below would be equally much a closure:

        sub demonstration { return { demonstration_data => { demo_data => shift }, }; }
        If you mean that it's a closure because you use a lexical variable in there, I disagree that it's a closure. Adding that it must close over/bind a lexical environment (which is what some people think and I think you are referring to), the returned hash reference is not a closure because it doesn't bind anything in its lexical surrounding.

        $demonstration_data knows to stick around even though the function ended

        In fact, it doesn't. The value it references knows to stay around. The instance of $demonstration_data doesn't stay around itself. Changing $demonstration_data after the latter hash reference is created does not change anything in it. This code below illustrates that.

        sub demonstration { my $demonstration_data = { demo_data => shift }; return { demonstration_data => $demonstration_data, }, sub { $demonstration_data = $_[0]; }; } my ($data, $modifier) = demonstration('foo'); print Dumper $data; $modifier->({ a => 1 }); print Dumper $data; __END__ $VAR1 = { 'demonstration_data' => { 'demo_data' => 'foo' } }; $VAR1 = { 'demonstration_data' => { 'demo_data' => 'foo' } };
        The hash value holds a RV that references the same PVHV as $demonstration_data references. $demonstration_data and demonstration()->{demonstration_data} are different RVs, but reference the same value.

        One can argue that the reference operator \ can act as a closure. Looking at lexical variables, it binds the current instance of the lexical variable. The similarity between \ and an anonymous closure and the contrast to other scalar types is demonstrated below.

        sub foo { my $foo = shift; return $foo, [ $foo ], \$foo, sub :lvalue { $foo } ; } my ($noref, $aref, $sref, $cref) = foo('foo'); my $dump = sub { print "\$noref: $noref\n", "\$aref : $aref->[0]\n", "\$sref : $$sref\n", "\$cref : " . $cref->() . "\n\n"; }; $dump->(); $noref = 'bar'; # Changes the first value. $dump->(); $aref->[0] = 'baz'; # Changes the second value. $dump->(); $$sref = 'zip'; # Changes the two last values. $dump->(); $cref->() = 'zap'; # Changes the two last values. $dump->(); __END__ Long output, run it yourself.
        Your hash above is no more closure than the array reference here. Neither the hash or the array bind the variables used in it - they store copies of the values the variables hold. In your case the value is a reference but that is no different from any other non-reference value except that it increases a ref count somewhere when copied, but that has nothing to do with closing over any environment.

        One may argue that the array reference itself is a closure and so may be, if you look at [ LIST ] as syntactic sugar for do { my @foo = LIST; \@foo }. That does not make any data structure keeping a reference to the anonymous array a closure though. It's just a data structure that holds a closure.

        ihb

        Read argumentation in its context!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://399610]
Approved by FoxtrotUniform
Front-paged by dragonchild
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (5)
As of 2024-04-19 15:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found