3 ms·
Perl 5.10.0 gives three different sets of answers. Map works as the original author expected: @fs = map {sub {$_ + shift}} (0..9); print $fs[$_](3), " " f
by gamache 18y ago
Perl 5.10.0 gives three different sets of answers.
Map works as the original author expected:
@fs = map {sub {$_ + shift}} (0..9);
print $fs[$_](3), " " for (0..9);
>>> 3 4 5 6 7 8 9 10 11 12
A Perlish for loop, or rather a statement with a "for" modifier:
push @fs, sub {$_ + shift} for (0..9);
print $fs[$_](3), " " for (0..9);
>>> 3 4 5 6 7 8 9 10 11 12
The same loop, unpacked into a more universal syntax:
for (0..9) {push @fs, sub {$_ + shift}}
print $fs[$_](3), " " for (0..9);
>>> 3 4 5 6 7 8 9 10 11 12
Here's where things start getting awesome. Let's name our loop variable $i, rather than using the Perlish default $_.
for $i (0..9) {push @fs, sub {$i + shift}}
print $fs[$_](3), " " for (0..9);
>>> 3 3 3 3 3 3 3 3 3 3
And now let's write a C-style for loop.
for ($i=0; $i<10; $i++) {push @fs, sub {$i + shift}}
print $fs[$_](3), " " for (0..9);
>>> 13 13 13 13 13 13 13 13 13 13
I am not sure why the last two versions work differently; I'd halfway expect the bottom version, but the "for $i (0..9)" loop leaves me scratching my head.
- jrockway 18y agoI am not sure why the last two versions work differently; I'd halfway expect the bottom version, but the "for $i (0..9)" loop leaves me scratching my head. $_ is dynamically scoped, so you are not capturing anything in your lambda. The reason they appear to work is because you are binding $_ dynamically to the right thing when you are printing the list. Example: my @fs = map {sub {$_ + shift}} 0..9; $_ = 42; say $fs[0]->(1); # 43 Oops. Perl 5.10 lets you lexical-ize $_, but you didn't do this. Try again and let us know how it goes ;)
- gamache 18y agoI understand the operational difference between using $_ and $i; it's the last two examples which confuse me, because one set of closures ends up using the very last value for $i (as I would expect for an $i scoped outside the loop), and the other uses the very first (as I would not expect at all).
- jrockway 18y agoNone of your examples create closures. You can only close over lexicals, but all the variables you create are global. (Your loops involving $i also affect the global $i. If you want it lexically scoped, you have to say so.) Your first two examples work because you are adding $_ and the first arg to the function. The for loop you use to print $fs[$_] binds $_, and your anonymous function uses that value. The result is something that appears to work. If you change the loop variable to something else, though, $_ will be unbound, and you will be adding to an undefined value. ("use warnings" will whine about this.) If you then create functions that add $i and the argument, your loop stops working, because the loop binds $_ instead of $i. Basically, you need some "my" keywords in there before your example becomes meaningful in any way. "use strict" and "use warnings" are your friend. ("use strict" will whine about the 'for $i (...)' construct that should be 'for my $i (...)', and "use warnings" will whine about $i being undefined when you try to evaluate one of the created functions.) One last thing, if you were experimenting in Devel::REPL (re.pl), you would have noticed this problem much sooner. strict and warnings are on by default, which would have produced errors and warnings for all of your examples. It also dumps the result of evaluation with Data::Dump::Streamer, so you can actually see what's being closed over. Examples: > my @fs = map { sub { $_ + $_[0] } } 1..2 $ARRAY1 = [ sub { package Devel::REPL::Plugin::Packages::DefaultScratchpad; use warnings; use strict 'refs'; $_ + $_[0]; }, 'V: $ARRAY1->[0]' ]; $ARRAY1->[1] = $ARRAY1->[0]; DDS shows you the code, and it is even smart enough to know that the two functions you produced are identical. If we do it "right": > my @fs = map { my $i = $_; sub { $i + $_[0] } } 1..2 my ($i,$i_eclipse_1); $i = 1; $i_eclipse_1 = 2; $ARRAY1 = [ sub { package Devel::REPL::Plugin::Packages::DefaultScratchpad; use warnings; use strict 'refs'; $i + $_[0]; }, sub { package Devel::REPL::Plugin::Packages::DefaultScratchpad; use warnings; use strict 'refs'; $i_eclipse_1 + $_[0]; } ]; You can see that the two functions are different, and that they don't share the same $i.
- berntb 18y ago