Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

comment on

( [id://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":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (3)
As of 2024-04-25 17:37 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found