Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

comment on

( #3333=superdoc: print w/replies, xml ) Need Help??
This is just a little report for the monks out there about some interesting behaviour that closures exhibit. Take a look at the following code and see if you can predict what it will show.
use strict; use warnings; { my $x = 'A'; sub f { sub { $x++ } } sub g { sub { $x++ } if $x } } my $F=f(); my $G=g(); print $F->(),$G->(),"," for 1..4;
Dont peek now!

Well, my bet is that you thought it produced AB,CD,EF,GH,

And if you did you would be wrong. It instead prints out 0A,1B,2C,3D,

I bumped into this one while answering a challenge over the CB by Petruchio to write a dynamic accessor generator for the code in the post Flyweight Objects and garbage collection. It took me 10 minutes to write the code, find the weirdness and then post the code only for myself Petruchio, dws, Corion, Danger to spend about 3 hours debugging and experimentating. Luckily wog finally came along and saved us by posting this link to an explanation. It turns out to be a subtle bug in the way closures are implemented in perl.

Normally you would expect that a closure has access to all of the variables inside of the lexical scope that it was declared in. Well it doesnt quite work out the way it should.

sub g() has localised copy of $x in its 'pad' or local variable space. It got there because the  if $x; brought it inside from the anonymous block. Ie, the sub g() is acting as a closure itself (contrary to many written documents about perl.) When g() is called it makes a clone of the anonymous subroutine with a copy of this value inside of it. All of this is pretty obvious and to be expected.

sub f() on the other hand does not have a localised copy of $x inside of it when it creates its anonymous sub. This sub gets a copy of the package level variable $x, which is of course undefined. When that copy is ++ it coerces numeric context and then returned value is 0. In this case f() is NOT a closure even though the subref it returns IS, but its a closure of the wrong scoped block. And therin lies the bug.

The moral of this story is simple. Closures are partially broken in perl. Named subroutines should act like closures always, but do not. Apparently this is the cause of no end of trouble in the mod_perl world. But even in non mod_perl its a good thing to keep in mind. Workarounds are simple, if a named subroutine needs to create a closure with access to variables from the scope it was declared in it needs to localize them internally before creating any closures of its own. A simple spurious refrence to the variable is enough. For instance f() could be 'fixed' to behave as expected by the replacing it with the following

sub f { $x; sub { $x++ }}
Although it will produce an error about scalar in void context.

Well hopefully with this warning none of the other monks will have to figure this one out on their own.

And I suggest reading the link that wog so kindly provided as its a much better explanation than I have given, and it has a bit of a lecture in it about closures that is quite worth reading. Another point would be that if you do plan to take a look at this in more detail a suggestion tye provided was to use a liberal quantity of  print \$x; throughout the script to see which version of $x is being used in each part. It makes it much easier to see what is going on.

Yves with lots and lots of help from Petruchio, dws, Corion, wog and danger --
You are not ready to use symrefs unless you already know why they are bad. -- tadmc (CLPM)


In reply to Funkyness with closures... by demerphq

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others having an uproarious good time at the Monastery: (7)
    As of 2020-04-01 08:32 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?
      To "Disagree to disagree" means to:









      Results (186 votes). Check out past polls.

      Notices?