|We don't bite newbies here... much|
Funkyness with closures...by demerphq (Chancellor)
|on Sep 30, 2001 at 04:31 UTC||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.
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
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.