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

To brighten an otherwise boring day, I decided to teach myself a bit about closures.

The example in the Camel Book (3e, P. 253) worked as advertised:

sub newprint { my $x = shift; return sub { my $y = shift; print "$x, $y\n"; }; } $h = newprint("Howdy"); $g = newprint("Greetings"); &$h("world"); &$g("earthlings");

prints "Howdy, world" and "Greetings, earthlings" just fine. But next I tried:

sub make_closures { my $j = 0; my @closures; while (++$j < 7) { push (@closures, sub {print $j, "\n";}); } } foreach my $sub (&make_closures) { &$sub; }

From the above I got

7 7 7 7 7 7

from which I conclude, for lack of a better understanding, that the closure gets the value for $j that $j took on when it went out of scope, rather than the value of $j when the closure was defined. If correct, that seems a bit counterintuitive. Can anyone shed some light on this behaviour?

Replies are listed 'Best First'.
Re: Closure Confusion
by chromatic (Archbishop) on Oct 16, 2002 at 19:50 UTC

    I'll assume that you're actually returning @closures from make_closures(). Since you're reusing the same lexical $j within the loop, each closure encloses the same lexical. If you change the value in one, it'll change for all of the others.

    The solution is to create a new lexical for each closure.

    sub make_closures { my $j = 0; my @closures; while (++$j < 7) { my $value = $j; push (@closures, sub {print $value, "\n";}); } }

    I also advise you not to use the &sub method of calling functions. It has implicit behavior that may not be what you want.

Re: Closure Confusion
by broquaint (Abbot) on Oct 16, 2002 at 19:55 UTC
    This is because $j is set at 7 when you execute each sub. You probably wanted something like this
    sub make_closures { my $j = 0; my @closures; while (++$j < 7) { my $state = $j; push (@closures, sub {print $state, "\n";}); } return @closures; } foreach my $sub (&make_closures) { &$sub; } __output__ 1 2 3 4 5 6
    Now each closure will have it's own version of $state. The reason being that closures by definition save lexical state.
    HTH

    _________
    broquaint

Re: Closure Confusion
by zigdon (Deacon) on Oct 16, 2002 at 19:50 UTC
    sub make_closures { my $j = 0; my @closures; while (++$j < 7) { my $i = $j; push (@closures, sub {print $i;}); } return @closures; } foreach my $sub (&make_closures) { &$sub; }

    This seems to work. I think the problem is that $j isn't going out of scope for each of the closures... so they're all pointing to the same address in memory, instead of to new addresses for each iteration.

    But I really barely understand closures, so I may be way off base here.

    -- Dan

    PS - I couldn't get this to run from perl -e without adding the return statement. "Undefined subroutine &main:: called at -e line 13.". Am I missing something?

Re: Closure Confusion
by BrowserUk (Patriarch) on Oct 16, 2002 at 20:01 UTC

    Apart from that your sub make_closures doesn't return the array of closures so the code as supplied doesn't do anything , you have to realise that the reference to $j inside each of you generated closures is a reference to the $j in the sub make_closures(). Ie. They all refer to the same variable.

    Here's a simple mod of your code that demonstrates the behavious that you were looking for... Maybe it will help clarify things.

    #! perl -sw use strict; sub make_closures { my $j = 0; my @closures; while (++$j < 7) { my $jj = $j; push (@closures, sub {print $jj, "\n";}); } return @closures; } foreach my $sub (&make_closures) { $sub->(); } __DATA__ C:\test>205818 1 2 3 4 5 6

    Here, I am creating a new var $jj inside the loop each time around and giving it the value, therefore each generated sub has its own independant variable/value, and the $j var gets garbage collected when it goes out of scope when make_closures() terminates.


    Cor! Like yer ring! ... HALO dammit! ... 'Ave it yer way! Hal-lo, Mister la-de-da. ... Like yer ring!
Re: Closure Confusion
by kabel (Chaplain) on Oct 16, 2002 at 21:04 UTC
    i tried another way and confused myself somehow ;)

    my initial thought was that if you put the scalar value inside double quotes it immediately stringifies, thus evaluating to a string that is just printed out.
    push (@closures, sub {print "$j";});
    this does not happen - it seems that the sub call takes the block as-is and returns a code reference. is there an explicit stringify operator for scalars? is this way feasible? my feeling says no, but i do not know it.
      Don't confuse creating a closure with compilation. The closure's body is compiled at compile time, not when the closure gets created. The code of this closure says "stringify $j", but which $j is only decided at runtime. Creating a closure at runtime does not compile the code; it merely "connects" it with a specific instance of whichever lexicals it refers to. Since you only create a single $j, all your closures get "connected" to the same $j.

      Makeshifts last the longest.

        thanks to you both for clarification. in fact that is the way things are going.

        the real problem i think is that the code gets created at compile time. stringification cannot happen since the code has not run yet. so variables stay what they are (even with a hypothetical stringification operator).

        the same is for the anonymous sub enclosed in an eval (). i could imagine that stringification then works (if only the operator would exist) ... but an eval is expensive, and more than one eval is even more expensive ;)

      Declaring a closure is much like declaring a subroutine. It works just as you'd expect this code to work. (Of course, if you don't expect this code to work the way it works, we all have problems :):

      sub not_a_closure { my $j = shift; print "$j"; }
Re: Closure Confusion
by Dinosaur (Beadle) on Oct 17, 2002 at 14:41 UTC

    ++Thanks to all who replied. I think I've got this. It's not the value of the lexical that's captured by the closure, it's the instance, as the following illustrates:

    sub make_closures { my $j = shift; return (sub {++$j;}, sub {print $j, "\n";}); } my ($bump1, $print1) = make_closures(5); my ($bump2, $print2) = make_closures(10); &$print1; &$bump1; &$print1; &$print2; &$bump2; &$print2;

    which prints

    5 6 10 11

    I.e., each call to make_closures creates an instance of $j which is shared by bump and print, but unique to each call to make_closures. Hmmm. The ability to create at runtime multiple instances of a a thingy encapsulating data and behaviour. Sounds like the road to Objects.

    In other news: sorry about the typo, I was rushing out the door ("But PerlMonks is work related"). Should've waited until the morning.

    Chromatic: I know that calling &sub turns off parameter checking -- it is that you warn of?

    Again, Thanks to all.

    -- Dinosaur

      Sounds like the road to Objects.
      Or actually, something like the reverse of objects: code which data is associated to, rather than data which code is associated to.
      I know that calling ⊂ turns off parameter checking -- it is that you warn of?
      Calling a function as &function; passes the current @_ on to it. You should write &function(); if you mean to simply call it.

      Makeshifts last the longest.

Re: Closure Confusion
by Dinosaur (Beadle) on Oct 16, 2002 at 19:54 UTC

    Sorry, the make_closures sub should end with a return @closures; line.