Just A Summary

Piers Cawley Practices Punditry

Javascript scoping makes my head hurt 13

Posted by Piers Cawley Thu, 20 Mar 2008 11:41:00 GMT

Who came up with the javascript scoping rules? What were they smoking. Here’s some Noddy Perl that demonstrates what I’m on about:

my @subs; 
for my $i (0..4) {
  push @subs, sub { $i }
}

print $subs[0]->(); # => 0;

Here’s the equivalent (or what I thought should be the equivalent) in Javscript:

var subs = [];
for (var i in [0,1,2,3,4]) {
  subs[i] = function () {
    return i;
  }
}
alert subs[0]() // => 4

What’s going on? In Perl, $i is scoped to the for block. Essentially, each time through the loop, a new variable is created, so the generated closures all refer to different $is. In Javascript, i is scoped to the for loop’s containing function. Each of the generated closures refer to the same i. Which means that, to get the same effect as the perl code, you must write:

var subs = [];
for (var shared_i in [0,1,2,3,4]) {
  (function (i) {
    subs[i] = function () {
      return i;
    };
  })(shared_i);
}
subs[0]() // => 0

Dodgy Ruby scoping

I had initially planned to write the example “How it should work” code in Ruby, but it turns out that Ruby’s for has the same problem:

subs = [];
for i in 0..4
  subs << lambda { i }
end
subs[0].call # => 4

Which is one reason why sensible Ruby programmers don’t use for. If I were writing the snippet in ‘real’ Ruby, I’d write:

subs = (0..4).collect { |i|
  lambda { i }
}
subs[0].call # => 0

My conclusion

Javascript is weird. Okay, so you already know this. In so many ways it’s a lovely language, but it does have some annoyingly odd corners.

Comments

Leave a response

  1. Brendan about 2 hours later:

    Here’s how I would have done this sort of thing in Javascript:

    var subs = [];
    for (var i in [0,1,2,3,4]) {
      subs[i] = {
        execute: function() {return this.i;}
      };
      subs[i].i = i;
    }
    alert(subs[0].execute());

    You’re right, though: Javascript’s scoping is very often contrary to expectations.

  2. anon about 4 hours later:

    I think it’s perl that’s being dodgy here, for a ‘scripting’ language. The equivalent in python behaves the same as js and ruby:

    [lambda: i for i in range(5)]0() # 4
    Note though, if you use the value of i in the list comprehension scope rather than keeping the reference alive with the lambda, it acts as you want it to:
    [lambda n=i: n for i in range(5)]0() # 0
    A bit of a cheat, but a short way to spell it.

  3. Piers Cawley about 6 hours later:

    @Brendan: I’ve always disliked that style – what’s the point of manually unpicking a perfectly serviceable closure?

  4. Piers Cawley about 6 hours later:

    @anon: Ah, one more reason to maintain my dislike for Python.

  5. James Duncan about 8 hours later:

    In JavaScript 1.7 you get “let” which does what you’re looking for. Of course that doesn’t help you in the majority of browsers, but it is a recognized problem.

  6. Aristotle Pagaltzis about 8 hours later:

    Yeah, scoping is insufficiently thought out in Javascript and downright braindead in Python. At least in Javascript, you can fix this particular example by reformulating it in the same way as the reformulated Ruby example:

    var subs = [0,1,2,3,4].map( function(i) {
    	return function() { i };
    } );

    Of course I’d write this in Perl just like you did in “real Ruby”:

    my @subs = map { my $i = $_; sub { $i } } 0 .. 4;

    And gosh, here I was annoyed by the verbosity of having to write “sub” in front of every block snippet. Welcome to Javascript which makes you write “function()”!

  7. Piers Cawley 1 day later:

    Heh. Javascript continues in its mission to turn people onto Lisp (but with extra syntax) eh? Are any of the various Javascript implementations doing tail call optimization yet? It’d certainly make continuation passing style a good deal easier to manage.

  8. Piers Cawley 1 day later:

    Aristotle: And no way to alias @function either. Giles Bowkett came up with a neat ruby hack:

    alias :L :lambda

    Which I find rather delightful.

  9. Aristotle Pagaltzis 1 day later:

    Ah, then you’ll enjoy lambda. :-)

  10. Daniel Berger 4 days later:

    Pssh, Ruby doesn’t require a third party library:

    http://www.oreillynet.com/ruby/blog/2007/10/fun_with_unicode_1.html

  11. Piers Cawley 4 days later:

    Daniel: You obviously haven’t looked at lambda.pm very closely. It’s not substantially longer than the ruby implementation, but it has some very neat tricks for lexically scoping the λ.

    Perl’s conventions for module inclusion and pragma-like modules knock Ruby’s into a cocked hat, frankly.

  12. David Cantrell 5 days later:

    The loop counter is declared outside the for{} block, so to me it makes sense that it could be scoped to the containing block. Perl’s the odd one out here. I like the way it’s odd, but it’s still odd.

    And, digging into the dusty recesses of my memory, isn’t that the same as how a for-loop counter would be scoped in C?

  13. Piers Cawley 5 days later:

    Who cares how it’s scoped in C? Frankly, the difference in scope only really becomes important/noticeable when your language gets closures. The slight weirdness of the Perl 5 scoping’s been addressed in Perl 6: It’s now:

    foreach @array -> $each {
      ...
    }

    Where -> $whatever { ... } is the new way of declaring parameterized anonymous blocks. I’m sure it won’t be long before someone writes a library to let them spell that as λ $foo { ... }

Comments



Just A Summary