Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Help with the concept of closures.

by DigitalKitty (Parson)
on Jul 05, 2003 at 23:06 UTC ( [id://271690]=perlquestion: print w/replies, xml ) Need Help??

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

Hi all.

I must confess to being a little ashamed. Having worked with perl for a reasonable length of time, the concept of a closure should not prove to be so enigmatic. To alleviate my ignorance, I was hoping some of you could evaluate the code below and tell me what is happening at each stage of program execution. Granted, this is a very simple closure but in the event that someone asks me to explain how/why it works, I would probably provide an inadequate answer (at best).

#!/usr/bin/perl -w use strict; sub shoppingList { my $item = shift(); return sub { my $otherItem = shift(); print "I need to buy a $item and a $otherItem.\n"; }; } my $itemInBasket = shoppingList( "sweater" ); my $newItemInBasket = shoppingList( "lipstick" ); &$itemInBasket( "pair of shoes" ); &$newItemInBasket( "purse" );


Thanks in advance for any help you are able to provide.

-Katie.

Replies are listed 'Best First'.
Re: Help with the concept of closures.
by Limbic~Region (Chancellor) on Jul 05, 2003 at 23:19 UTC
    DigitalKitty,
    Before explaining the code, let me try and explain what a closure is the way I understand it.

    A closure is a way to get at data that has gone out of scope. Because the reference count has not reached 0, it persists but is only accessible through the closure.

    #!/usr/bin/perl -w use strict; sub shoppingList { my $item = shift(); return sub { my $otherItem = shift(); print "I need to buy a $item and a $otherItem.\n"; }; } my $itemInBasket = shoppingList( "sweater" ); my $newItemInBasket = shoppingList( "lipstick" ); &$itemInBasket( "pair of shoes" ); &$newItemInBasket( "purse" );
    Ok - lets take the seemingly simple sub shopping list:
    It creates a lexical variable called $item and then returns a sub. The magic comes in that $item should disapear, but it doesn't since the returned sub keeps the reference count from reaching 0. You still use the lexical $item from elsewhere in the program, but you can still get at it through the closure.

    Both $ItemInBasket and $newItemInBasket are now code references. They are the referant of the returned anonymous sub, which in turn remembers $item.

    When they are de-referenced (I prefer the -> notation), they "remember" the $item and shift the argument list to get $otheritem.

    References and closures are the things Perl OO is made of and that is the next logical step.

    If you would like more information - let me know. I am sure there will be far better answers than mine anyway. I should point out that you assume the subs will be called with at least one argument. I know this is only for learning purposes, but coding for unexpected input will save you a great deal of troubleshooting time in the long run.
    Cheers - L~R

      you could imagine it this way to (hopefully) demistify what's going on:
      after the declaration of the function...my $iteminbasket = shoppingList(sweater)...imagine this is like a OO call to create a new $iteminbasket object, which declares some lexical variables etc (all black box stuff). Then...&iteminbasket("pair of shoes") ...imagine calls the &iteminbasket method of the created object. you passed it some data, but when you first created the object, it declared a lexical, so it has access to both. Hope this parallel helps a bit.
Re: Help with the concept of closures.
by broquaint (Abbot) on Jul 06, 2003 at 00:02 UTC
    Having worked with perl for a reasonable length of time, the concept of a closure should not prove to be so enigmatic.
    <plug>Perhaps Closure on Closures would be of help in shedding closures of their enigma.</plug>.
    I was hoping some of you could evaluate the code below and tell me what is happening at each stage of program execution.
    Here we go then
    my $itemInBasket = shoppingList( "sweater" );
    Assign $itemBasket the return of shoppingList( "sweater" ) (this much is obvious ;)
    my $item = shift();
    Create a new lexical in the scope of shoppingList and assign to it the first argument.
    return sub { my $otherItem = shift(); print "I need to buy a $item and a $otherItem.\n"; };
    Return the reference to a newly created anonymous subroutine.
    &$itemInBasket( "pair of shoes" );
    Execute the subroutine reference stored in $itemBasket and pass it the string "pair of shows". Now for the tricky bit ...
    my $otherItem = shift(); print "I need to buy a $item and a $otherItem.\n";
    Assign $otherItem the first argument. Now when we print out $item we are referring to the $item that was created when the anonymous sub was returned. It is still in existence because the anonymous sub 'held on' to $item as it was referred to inside the code (it's not quite as simple as this, but that explanation shall suffice). So the anonymous sub is maintaining the lexical state of the surrounding scope that it was created in.
    HTH

    _________
    broquaint

      <plug>Perhaps Closure on Closures (beta) would be of help in shedding closures of their enigma.</plug>.

      Broquaint please do not feel that you need to add <plug> tags to comments like this. The node you wrote is well written, informative and useful. You have every right to feel proud about the node and to refer people to it when necessary. My only beef with the node is that due to implementation problems I feel the term closure has special meaning in Perl and should be restricted to only a class of anonymous subs, however I am aware that I am most likely in the minority with this opinion.

      DigitalKitty should certainly review your node, and if you hadn't provided a link I would have done so myself.

      :-)


      ---
      demerphq

      <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
•Re: Help with the concept of closures.
by merlyn (Sage) on Jul 05, 2003 at 23:59 UTC
      merlyn, while I can appreciate tersity in many cases, this is not one. I really wish you had at least spent the effort on a list of tie-ins to your linked document -- but that's just me. broquaint also offered a plug, but then he continued to fullfill the OP's request. Even a quick three sentence tie-in to your document would have been good. Closures are a hard concept for many. Also, I would have at least included a positive emoticon of some sort to reduce the condescendive tone of the reply.

      But that's just me. I am aware that you are probably a busy person.

      P.S.: I am also curious about why you use the &bull; entity...

      mhoward - at - hattmoward.org
        Why does he need to write a list of tie ins? It's an article about closures (among other things). That seems fairly self explanatory to me. Should he copy and paste the entire column? Whats the point? Should he copy random parts of it? Of course closures are a fairly difficult subject.. what does that have to do with merlyn writing a column on it? I doubt he writes columns on boring, easy mundane things. How does "I have a column on it" Sound even remotely condescending to you? It's just like saying "well, heres a good book about the subject" or "This is a good reference on X".
        A reply falls below the community's threshold of quality. You may see it by logging in.
      As you pointed below, it has now been brought to my attention that you have these articles on the Internet for free. I have duly bookmarked the page and will make use of the articles. Thanks for posting the comment, even if someone determined it to be smug.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff";
      $nysus = $PM . $MCF;
      Click here if you love Perl Monks

Re: Help with the concept of closures.
by demerphq (Chancellor) on Jul 06, 2003 at 13:13 UTC

    Hi digitalkitty. Other respondants have provided a wealth of information about closures, but I'd like to point out what I consider to be a few style nits in your code.

    my $item = shift(); To me is a potential error. If the shift is intended to operate on its default var (either @_ or @ARGV depending on where the shift is called from) then dont bother with the parens. When I see the empty parens in a situation like that I think "hmm, there should be something in there, did somebody forget something?" whereas my $item = shift; says to me "they are using the default var, no worries". If you feel the parens are necessary then put the object that shift is working on in as well. (And even then the parens are superfluous. my $item = shift @_; is IMO better than my $item=shift(@_);).

    Second is the use of camelHumpIdentifiers. Generally speaking these are frowned upon in the perl scene. People that do use them tend to come from other languages where this style is prevalent and bring it along as part of the baggage from the other. There are several reasons why identifiers such as these are frowned upon (in no particular order)

    • camelHumpVars are more difficult for non native english speakers to read properly. As the development base of Perl is truely international (unlike many other langauges which are strongly US/English based, such as Java) making life easy for non english speakers is generally considered to be a desireable thing. camel_hump_vars is much easier for a non native english speaker to parse correctly.
    • camelHumpVars are more difficult to read at 4 o'clock in the morning for everybody. When you can barely keep your eyes open, and what eyes you can see are a mess of broken blood vessels (ie bloodshot) from the hours of dev, every little bit that makes things clearer is well appreciated.
    • camlHumpVars are ugly. This is just my opinion, but reviewing large quantitiy of code you will find very little written in this style and I do not believe that the above two reasons can account for this apparent consistancy. Also consider they dont scale well when the identifier contains multiple words. Ie theFunkyVarThatGetsUsedByPrintToPutSeperatorsInItsOutput is a lot harder to grok than the_funky_var_that_gets_used_by_print_to_put_seperators_in_its_output.
    • The rules for camelHumpVars dont generalize, ie its common to use all caps for constants or "near-constants", and special vars. So then you have camelHumpVars and CONSTANT_VALUES which are different rules. Whereas camel_hump_vars and CAMEL_HUMP_VARS are nicely consistant and apply everywhere.
    • larry_prefers_this_style. :-)

    A last nit is the use of ampersand in stuff like &$itemInBasket( "pair of shoes" ); Most perl programmers believe that this is ugly, and to some degree confusing. (I realize there is a small amount of debate about this, but there isnt much.) The reason is that the ampersand has special meaning in certain situations. For instance if you saw a function call like this &function; and then blihely added parens to it, then you would dramatically change the meaning of the statement. My rule for the ampersand notation is that they should only be used when they are absolutely required. This means that when they are present it is immediately notable and says to the maintenance programmer 'something special is going on here, be careful'. Whereas when you use them as general rule there is no such mental danger flag associated with them.

    The only time they are required is when you taking a reference to a named subroutine -- actually that isnt strictly true, one can use the *subname{CODE} notation to the same effect -- and when you are using the magical form &subname; or the really magical goto &subname; or when you have named a sub the same as a keyword (which is a bad idea generally anyway IMO) and need to disambiguate -- which again isnt strictly true, one can avoid them in that case by using a full qualified subroutine name.

    My rule is that $obj->attr; is ok for attributes, $obj->method() is preferable for methods, $coderef->() is preferable (compared to &$coderef();) for calling an anonymous sub, namedsub(); is preferably for calling a argumentless named subroutine, and namedsub "with arguments"; is preferable (when unambiguous) to namedsub("with arguments"); And from what Ive seen here on PM in terms of code and comments and in the vast majority of code ive reviewed from CPAN and the standard distro most people agree with me.

    Perl code is relatively "live" and "noisy", anything that can be done to reduce the level of noise (such as eliminating blocks and parens) is probably a Good Thing, and will improve the readability and thus the maintainability of your code.

    Anyway, theres lots of food for thought for you in this thread. Have fun and good luck.


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: Help with the concept of closures.
by DigitalKitty (Parson) on Jul 06, 2003 at 02:13 UTC
    Hi.

    Thank you all very much. My assumptions on how a closure functioned were reasonably accurate. The advice I received from you (the responding monks) did, however, illuminate several ambiguous aspects. Most notably, the fact that an anonymous sub was returned after the first parameter was stored in a strictly lexical variable (e.g. it wouldn't be a closure if I had used 'local' in lieu of 'my').

    -Katie.
Re: Help with the concept of closures.
by bobn (Chaplain) on Jul 06, 2003 at 01:54 UTC

    The critical thing is that even though $item seems to have gone out of scope once shoppingList() has returned, the code reference it returns has a copy of $item in it. This copy retains the value $item had at the time that shoppingList returned the reference.

    So you can call each of these references as many times as you wish, and the first thing it tells you you need will always be the same as what you gave the routine when you created the code reference with shoppingList();



    --Bob Niederman, http://bob-n.com

      It's not a copy. It's the same variable.

      sub make_closures { my $value = shift; return sub { $value++ }, sub { print "$value\n" }, sub { $value-- } +; } my ($increment, $display, $decrement) = make_closures( 3 ); $display->(); $increment->(); $display->(); $decrement->(); $display->();

        Nope. Each coderef (set of coderefs in your example - it's the time that the lexical is in scope that matters) has it's own copy.

        sub make_closures { my $value = shift; return sub { $value++ }, sub { print "$value\n" }, sub { $value-- }; } my ($increment, $display, $decrement) = make_closures( 3 ); print "first\n"; $display->(); $increment->(); $display->(); $decrement->(); $display->(); my ($increment2, $display2, $decrement2) = make_closures( 9 ); print "second\n"; $display2->(); $increment2->(); $display2->(); $decrement2->(); $display2->(); print "first again\n"; $display->(); $increment->(); $display->(); $decrement->(); $display->();

        Result:

        first
        3
        4
        3
        second
        9
        10
        9
        first again
        3
        4
        3
        

        That's why it needs to be lexical.



        --Bob Niederman, http://bob-n.com

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (7)
As of 2024-03-28 09:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found