Hacker News new | past | comments | ask | show | jobs | submit login

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.

$_ 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 ;)




I 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).


None 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.


That was as good as perlmonks.org! You should almost have put a warning at the top, that a reader might want to think about the problem, before reading your answer. :-)

I'm convinced and will try Devel::REPL, instead of "traditional" one-liners.


And not surprisingly this topic has already been discussed on Perlmonks ;-)

Here's one variation..... http://www.perlmonks.org/?node_id=719475




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: