Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

evaluation strategy of perl

by mayaTheCat (Scribe)
on Sep 20, 2002 at 14:07 UTC ( [id://199472]=perlquestion: print w/replies, xml ) Need Help??

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

hi monks, a while ago I tried to code something similar to the following one:

use warnings; use strict; my @d; for (my $i = 0; $i<3; $i++) { push @d, sub { print "$i\n" }; } &{$d[0]}(); &{$d[1]}(); &{$d[2]}();

what I expected as the result of this script was

1
2
3

however, what I got was

3
3
3

so, I concluded that perl uses a "lazy evaluation" strategy, which means that it does not assign the value of a variable until the value is really needed.

for example, in my sample code the variable $i has the value 3 at time the three functions are called.

however, knowing the reason cannot solve my problem. is there a way to force perl to assign the value of the variable at the time the function is defined?

---------------------------------
life is ... $mutation=sub{@_=split'';$gene=int(rand($#_+$=/$=));$_[$gene]=$=/$=-$_[$gene];$_=join'',@_};

Replies are listed 'Best First'.
Re: evaluation strategy of perl
by dreadpiratepeter (Priest) on Sep 20, 2002 at 14:27 UTC
    What you have inadvertantly created is called a closure. When you create a subroutine, all lexical (my) variables in the parent scope are made available to the subroutine. Event if they are no longer around when the routine is later called. Essentially, perl keeps a reference to the lexical variable associated with the routine. In your case a references to $i. When you call the routine it uses the current value of $i -- 3.
    Try this variation an see the difference:
    my @d; for (my $i = 0; $i<3; $i++) { my $j=$i; push @d, sub { print "$j\n" }; }
    In this case since $j is created with each iteration of the loop, each of the closures will have a reference to a different copy of $j.
    This should give you the right output (although not what you were expecting- 0,1,2; not 1,2,3)
    You can find out all about closures in chapter 8 of the Camel book, or by searching the Monastery.

    Hope it helps,

    -pete
    "Pain heals. Chicks dig scars. Glory lasts forever."
Re: evaluation strategy of perl
by kabel (Chaplain) on Sep 20, 2002 at 14:14 UTC
    the problem here is that $i is declared only once and at the end of the for loop it has the value 3. i think what you want is something like this:
    foreach my $i (0 .. 2) { push @d, sub { print "$i\n" }; }
    in each iteration a new variable $i is created. the old $i (from the former loop iteration) is no longer accessible via $i, but because there is a subroutine referencing it, it is not garbage collected.
Re: evaluation strategy of perl
by Aristotle (Chancellor) on Sep 20, 2002 at 14:34 UTC
    The problem is that interally, "$i\n" becomes $i . "\n" - which demonstrates why the problem occurs. You have to interpolate the value beforehand:
    for (my $i = 0; $i<3; $i++) { my $str = "$i\n"; push @d, sub { print $str }; }
    However, that will not work correctly:
    0
    1
    2
    Oops. You meant
    for (my $i = 1; $i<=3; $i++) { my $str = "$i\n"; push @d, sub { print $str }; }
    Because of this sort of problems, you should use the for(LIST) form.
    for my $i (1..3) { my $str = "$i\n"; push @d, sub { print $str }; }
    That eliminates an entire class of mistakes known as "fencepost errors", where the loop limit condition is fudged, usually resulting in one-off errors (one iteration too many or too few).

    Makeshifts last the longest.

      There is something significantly different between the two for styles in your last two examples.... Can you explain why $i binds early and $j binds late?
      #!/usr/bin/perl -wT use strict; my (@d,$i,$j); # all vars in same scope for $i (1..3) { push @d, sub { print "$i\n" }; # same as line below } for ($j = 1; $j<=3; $j++) { push @d, sub { print "$j\n" }; # same as line above } $_->() for @d; # execute all anon subs __END__ 1 2 3 4 4 4

      -Blake

        The for(LIST) doesn't bind early either. However, $i is just an alias to a different value, so rather than $i itself, the closure remembers the alias. It's easily visible if we use an array rather than a literal list.
        #!/usr/bin/perl -w use strict; my @t = (1,2,3); my @d; for my $i (@t) { push @d, sub { print "$i\n" }; } $_++ for @t; &$_ for @d; __END__ 2 3 4
        I hadn't thought about that, though in retrospect it's quite clear. Thanks for making me ponder this.

        Makeshifts last the longest.

Re: evaluation strategy of perl
by Sidhekin (Priest) on Sep 20, 2002 at 14:35 UTC

    Making a subroutine, if you want it to use values rather than variables, the way to do it is string eval. ("evaluation strategy", indeed.)

    use warnings; use strict; my @d; for (my $i = 0; $i<3; $i++) { push @d, eval qq/sub { print "$i\n" }/; } &{$d[0]}(); &{$d[1]}(); &{$d[2]}();

    However, you will note that since the value of $i through this loop was 0, 1, and 2, that's what you get in output as well ... not 1, 2, and 3 :-)

    Alternatively, you can choose to use variables, but then you need three variables. The above code makes only one. The below code uses three variables (all with the same name), and also prints 0, 1, and 2:

    use warnings; use strict; my @d; for (my $i = 0; $i<3; $i++) { my $closure_var = $i; push @d, sub { print "$closure_var\n" }; } &{$d[0]}(); &{$d[1]}(); &{$d[2]}();

    The Sidhekin
    print "Just another Perl ${\(trickster and hacker)},"

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://199472]
Approved by valdez
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (6)
As of 2024-03-28 23:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found