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

Hi monks,
given the following sample:
for my $i (1..3) { $numbers{$i} = sub { print $i; }; } &{$numbers{2}};
The result is 2, but I don't understand why. Whenever the sub runs, $i is way out of scope (add print $i below the } of the for loop to test) - but within the sub it still exists even if that sub runs in a complely different context?

Replies are listed 'Best First'.
Re: Constant value in anonymous sub
by almut (Canon) on Feb 19, 2010 at 09:25 UTC

    It's called a closure. See perlref.

Re: Constant value in anonymous sub
by Xiong (Hermit) on Feb 19, 2010 at 13:44 UTC

    This is a perfectly good example of a closure -- by the book, one might almost say.

    (Indeed, you want to take a good hard look at perldoc perlref. But if you're new to this, it might take a bit of study before the light comes on; so it did for me.)

    Take another look at your own example code (a well-written example, thank you). You notice that when you execute &{$numbers{2}}; that $i is not really anywhere to be seen. Indeed, $i is out of scope. But by that time, you have defined three anonymous subroutines and put coderefs to them into the array @numbers. This is all fine and exactly what you want. You aren't keying %numbers with $i. You're keying with a literal, 2.

    Let's move up to a more aggressive closure demo:

    Here, $step and $stepper are enclosed in make_stepper(). Both are within the same lexical scope, so $stepper has no trouble at all making use of $step, which is shifted in whenever make_stepper() is called. Now $step goes out of scope as soon as its block terminates; but you might say it is "frozen" into the anonymous subroutine make_stepper() spits out.

    Note that the second parm (99) is ignored when $quaddle is defined. This works as expected for 13 and 22; when $quaddle is called with no parameters, a warning is raised (and at the correct line of the code, too). This is what you want! The definition of $stepper is just that: a definition. It doesn't execute until instanced and called.

    Take a look at the outputs of $i and $j. One might naively think both would be incremented.

    But $j is not incremented from call to call; it goes out of scope at the end of the $stepper sub declaration and is never seen again. Each run of any of the instances creates a fresh $j, increments it to 1, prints it, and promptly forgets it.

    Much more interesting is $i. This doesn't keep track of how many steppers have been made; rather it tells how often each one has been called. Note that each instance has its own counter. Think about this and see if you can tell why. Can you modify this demo to add a counter that does track how many steppers have been instanced?

    Besides perldoc perlref, you might also want to take a look at Perl 5 Wiki and Perl.com on closures.

      Sorry I disagree that this is a good example - "by the book" - of a closure, because it's far to magic.

      Each closure has a new instance of $i which is mutable, looking at the code I would expect either always the same instance of $i or $i being an alias of an immutable literal... but it's neither of these...

      for my $i (1..3) { $numbers{$i} = sub { $i++;print $i }; }

      Cheers Rolf

      UPDATE:

      OK my problem derived from the difference between range operator and comma operator in producing lists:

      DB<37> for my $i (1,2,3) { $numbers{$i} = sub { $i++;print $i }; pri +nt \$i} SCALAR(0x9338210)SCALAR(0x92bdc90)SCALAR(0x9338190) DB<38> &{$numbers{1}} Modification of a read-only value attempted at (eval 43)[/usr/share/pe +rl/5.10/perl5db.pl:638] line 2.
        Sorry I disagree that this is a good example - "by the book" - of a closure, because it's far to magic.
        Each closure has a new instance of $i which is mutable, looking at the code I would expect either always the same instance of $i or $i being an alias of an immutable literal... but it's neither of these...

        Rolf, $i is a variable and it is always mutable. At the end of the closure (the loop) it goes out of scope, so there is no way to reference it directly. There are still references to $i in the subroutines that were generated inside the loop/closure. Via these subroutines you can do whatever you like to $i, including returning it (as reference, of course) and manipulating it outside of a subroutine.

        Basically leaving a block only removes the symbol table entry for something. The object itself doesn't cease to exist until all references to it go away. This is very much by the book for Perl.

        - doug