Consider the following lua code:
f = {}
for i = 1, 10 do
f[i] = function()
print(i .. " ")
end
end
for k = 1, 10 do
f[k]()
end
This prints the numbers from 1 to 10. In this case, i
is closed over the value for each iteration of the outer loop. This is how I had always understood closures, and I was very happy...
...until I was porting some lua code into c#, and I tried to do the same thing:
var f = new Action[10];
for (int i = 0; i < 10; i++)
{
f[i] = (new Action(delegate()
{
Console.Write(i + " ");
}));
}
for (int k = 0; k < 10; k++)
{
f[k]();
}
And now I get the number 10 printed 10 times (let's forget that lua arrays are 1-based). It actually happens that in this case, the closure works over the variable, not its value, which makes a lot of sense, since I'm only calling the functions once the first loop is over.
JavaScript seems to have the same semantics (close over the variable):
var f = []
for (var i = 0; i < 10; i++)
{
f[i] = function()
{
document.write(i + ' ');
};
}
for (var k = 0; k < 10; k++)
{
f[k]();
}
Actually, both behaviors make a lot of sense, but are of course incompatible.
If there is a "correct" way to do this, then either lua, or c# and JavaScript are wrong (I haven't tried with other languages yet). So my question is: "what are the "correct" semantics of closing a variable inside a loop?"
edit: I'm not asking how to "fix" this. I know I can add a local variable inside the loop and close over that one to get the lua behavior in c#/JavaScript. I want to know what is the theoretically correct meaning of closing over a looped variable is, and bonus points for a short list of which languages implement closures in each way.
edit: To rephrase my question: "what is the behavior of closing over a looped variable in lambda calculus?"
The Lua manual explains exactly why this works. It describes the index for-loop in terms of a while loop as this:
Notice how the loop variable
v
is declared inside the scope of thewhile
loop. This is done specifically to allow exactly what you're doing.There are no loop variables in lambda calculus.
There is no "correct" way. There are different ways. In C#, you would fix it by making a variable scoped to the loop:
In JavaScript, you might add a scope by making and calling an anonymous function:
Iteration variables in C# don't have loop scope. JavaScript doesn't have block scope, just function scope. They're just different languages and they do things differently.
Closing over a loop variable is like closing over any other variable. The problem is with language-specific looping constructs and whether they translate into code that puts the loop variable inside or outside the loop.
For instance, if you use a
while
loop in C#, Lua or JavaScript, the result in all three languages is the same (10). Ditto for afor(;;)
loop in JavaScript or C# (not available in Lua).However, if you use a
for (i in x)
loop in JavaScript, you'll find that each closure gets a new copy ofi
(output:0 1 2 3 ...
). Ditto forfor i=x,y
in Lua andforeach
in C#. Again, that has to do with how those languages construct those loops and how they expose the value of the loop variable to the body of the loop, not a difference in closure semantics.In fact, in the case of C#'s
foreach
, this behavior changed from 4.5 to 5. This construct:Used to translate into (pseudocode):
In C# 5, this was changed to:
This was a breaking change, done to better meet programmer expectations when closing over the loop variable. The closure semantics didn't change; the position of the loop variable did.