-
-
Save graue/5079734 to your computer and use it in GitHub Desktop.
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 |
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
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.
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 :)
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?