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

Haskell doesn't act differently. If you translate the Haskell to Lisp:

    (funcall (elt (mapcar (lambda (x) (lambda (y) (+ x y))) '(1 2 3)) 0) 2)
     ==> 3
Then the Lisp works the same as the Haskell.

The difference is that "loop" mutates the loop variable, whereas mapping makes a copy. In Haskell, there is no way to mutate a variable (that's not even what variables are), so that's the only way you can express the concept. In Lisp, you can either copy or mutate, and you get a different result depending on which you choose.

Closures always work this way. Otherwise, you couldn't write things like:

    (defun make-incrementers ()
        (let ((i 0))
            (cons (lambda () (+ i 1))
                  (lambda () (- i 1)))))



Similarly, if you use map instead, you get the right answer:

    >>> map(lambda x: lambda i: x + i, range(0,10))[3](4)
    7


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.


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


I think you mean:

    (defun make-incrementers ()
        (let ((i 0))
            (cons (lambda () (incf i))
                  (lambda () (decf i)))))


Yes, I did.


Ah nice. Thanks for the explanation.




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

Search: