Skip to content

Instantly share code, notes, and snippets.

@graue
Last active December 14, 2015 11:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save graue/5079734 to your computer and use it in GitHub Desktop.
Save graue/5079734 to your computer and use it in GitHub Desktop.
Weirdness with JS closures (Lua behaves differently).
NOTE: Scroll down for explanation (the difference turns out to be scoping, not closures per se).
$ cat juices.lua
local fruits = {"apple", "orange", "grape"}
local juicers = {}
for i,v in ipairs(fruits) do
local fruit = v
juicers[i] = function() return fruit .. " juice" end
end
print(juicers[1]());
print(juicers[2]());
print(juicers[3]());
$ lua juices.lua
apple juice
orange juice
grape juice
$ cat juices.js
var fruits = ["apple", "orange", "grape"];
var juicers = [];
for (var i in fruits) {
var fruit = fruits[i];
juicers[i] = function() { return fruit + " juice"; }
}
console.log(juicers[0]());
console.log(juicers[1]());
console.log(juicers[2]());
$ node juices.js
grape juice
grape juice
grape juice
@graue
Copy link
Author

graue commented Mar 4, 2013

Python is like JavaScript here:

$ cat juices.py 
fruits = ["apple", "orange", "grape"]
juicers = []

for fruit in fruits:
    juicers.append(lambda: fruit + " juice")

print(juicers[0]())
print(juicers[1]())
print(juicers[2]())
$ python juices.py 
grape juice
grape juice
grape juice

@graue
Copy link
Author

graue commented Mar 4, 2013

Hmm, idiomatic(?) Clojure does what I want, but it's not really a meaningful comparison because this isn't a for-loop. No variable's getting updated. Is there a way to do something like the for-loops above in Clojure?

(def fruits ["apple" "orange" "grape"])

(def juicers
  (map (fn [fruit] (fn [] (str fruit " juice"))) fruits))

(println ((nth juicers 0)))
(println ((nth juicers 1)))
(println ((nth juicers 2)))
$ lein exec juices.clj
apple juice
orange juice
grape juice

@graue
Copy link
Author

graue commented Mar 4, 2013

OK, I'm starting to understand.

This JS code does what I want:

var fruits = ["apple", "orange", "grape"];
var juicers = [];

for (var i in fruits) {
    var fruit = fruits[i];

    function makeJuicer(juiced) {
        return function() { return juiced + " juice"; }
    }

    juicers[i] = makeJuicer(fruit);
}

console.log(juicers[0]()); // apple juice
console.log(juicers[1]()); // orange juice
console.log(juicers[2]()); // grape juice

It seems the actual difference between JS and Lua is indeed one of scope, but it isn't lexical vs. dynamic scoping. In JavaScript, only a function creates its own scope. An if block, for example, doesn't:

var foo = 42;
if (true) {
    var foo = -10;
}
console.log(foo); // prints -10

While in Lua, any block has its own scope:

local foo = 42
if true then
    local foo = -10
end
print(foo) -- prints 42

Again, Python works like JavaScript in this respect. One might expect that juiced_fruit here is a local variable, only existing inside the for loop:

>>> for fruit in fruits:
...     juiced_fruit = fruit
...     juicers.append(lambda: juiced_fruit + " juice") 

But it's not:

>>> juiced_fruit
'grape'

Python needs a function to supply a more specific scope, like JS:

fruits = ["apple", "orange", "grape"]
juicers = []

for fruit in fruits:
    def make_juicer(juiced_fruit):
        return lambda: juiced_fruit + " juice"

    juicers.append(make_juicer(fruit))

print(juicers[0]()) # apple juice
print(juicers[1]()) # orange juice
print(juicers[2]()) # grape juice

@graue
Copy link
Author

graue commented Mar 4, 2013

So closures actually work the same way in Lua as in JavaScript and Python. If you write the loop like this in Lua:

local fruit = ""
for i,v in ipairs(fruits) do
    fruit = v
    juicers[i] = function() return fruit .. " juice" end
end

print(juicers[1]());
print(juicers[2]());
print(juicers[3]());

...you again get "grape juice" three times. None of the 3 languages are copying variables when they create a closure. In all three languages, the closure references the original variable, which can change.

But in Lua, a new variable called v is created in each iteration of the loop; it doesn't update the old v. Likewise in the code up top, a new local variable called fruit was created on each iteration of the loop. In this version here we had to explicitly override that behavior. Closures are the same between Lua and JS/Python; scoping is not.

@dschobel
Copy link

dschobel commented Mar 4, 2013

yeah, so long story short, block scoping makes the world sane and python and javascript are inferior for not having it. that's my conclusion to all this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment