I need closure
Suppose you wanted to create a list of closures, each of which spits out a new number in sequence. Pretty easy:
my @subs;
foreach my $num( 1 .. 10 ) {
push @subs, sub { return $num };
}
print $_->(), "\n" for @subs;
This is a simplified example of something I needed to do in Javascript, but I did it wrong.
var funcs = [ ];
for( var i = 1; i < 10; i++ ) {
funcs.push( function() {
return i;
}
);
}
for( var j = 0; j < funcs.length; j++) {
alert( funcs[j]() )
}
But that just gets you this ten times:
Even though Javascript has C-like syntax, it doesn't have proper block-level scope. I knew this, but made the mistake anyway, because I have an unfortunate habit of thinking in Perl when I'm writing code in other languages. So in Javascript, each of the ten anonymous functions closes over the same i
, which has been dutifully incremented to 10. In Perl, we get a new $i
each time through the loop.
Javascript's scoping is limited to the bodies of functions. Crockford suggests the following workaround for this problem:
var funcs = [ ];
for( var i = 1; i <= 10; i++ ) {
funcs.push( function(tmp) {
return function() {
return tmp
}
}(i)
)
}
Instead of pushing ten closures over the same i
, we create and immediately invoke a function that constructs a new function closed over its argument, which is the increment value, passed in through tmp
. Since Javascript has function-level scope, we get a new tmp
each time, and a series of alert boxes that count to ten.
It's a bit ugly, and really makes me wish Javascript's designers had the sense to provide proper lexical scoping. Of course, it would be really nice if every browser came with a Perl compiler built-in, too. :)
I think you could argue that either kind of scope is "proper:" Perl creates a new lexical binding for each iteration, while JavaScript reassigns to a single binding. I think Perl's is less surprising, but I don't think JavaScript's is "improper."
You're right, it's not "improper." Just annoying. :)
Ruby gets this wrong in almost the same way.
Python, of course, is an unmitigated disaster when it comes to scoping.
Perl got this bit really really right.
Lisp which is arguably the canonical closure implementation also got this right.