1

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?"

Panda Pajama
  • 1,283
  • 2
  • 13
  • 31
  • 1
    possible duplicate of [Why is it bad to use a iteration variable in a lambda expression](http://stackoverflow.com/questions/227820/why-is-it-bad-to-use-a-iteration-variable-in-a-lambda-expression) – Kirk Woll Jan 15 '13 at 04:07
  • JavaScript does not have block scope, you will need an additional function. – Bergi Jan 15 '13 at 04:09
  • 3
    Why do you think that there is a "correct" way? These are different languages; none of them are "wrong". They're just different. – Nicol Bolas Jan 15 '13 at 04:16
  • @NicolBolas because if there were no formal definitions for all theoretical computer science terms, programming would be even messier than it is today. So to rephrase my question and make it language-agnostic: "How does closing over a loop variable behaves in lambda calculus?" – Panda Pajama Jan 15 '13 at 04:20
  • Math has closed variables? Uh-oh. – Ry- Jan 15 '13 at 04:22
  • 4
    Lambda calculus has loop variables? – Eric Lippert Jan 15 '13 at 04:23
  • 2
    The question is not answerable. The correct behavior of a closure is whatever the language specification says. It depends on the semantics of the thing being closed over. – Eric Lippert Jan 15 '13 at 04:27
  • [4) Print Print Print... By Jon Skeet](http://www.yoda.arachsys.com/csharp/teasers.html) – Habib Jan 15 '13 at 04:29
  • @EricLippert does that mean that closures are a language implementation detail, instead of a formal construct? – Panda Pajama Jan 15 '13 at 04:32
  • 1
    @パンダパジャマ: Yes, that's exactly what they are. – Ry- Jan 15 '13 at 04:33
  • @パンダパジャマ: Remember: Pure functional languages and programming (which is where things like "closures" come from) do not *have* loops. You "loop" via recursion. And in recursion, you would close over the current value of the "loop variable", because that's the currently visible stack. Lua, JavaScript, and C# are not functional languages, though they do incorporate aspects of functional programming. So for them, it's really dealer's choice over how to handle concepts that don't exist in pure functional languages. – Nicol Bolas Jan 15 '13 at 05:22
  • @NicolBolas of course you don't have loops, but you do have scoping, and you can definitely use a free variable in any function, effectively closing it. But once again, you're using the variable, not its value when the closure was created (it makes no sense in lambda calculus). Closures are always over variables, not values, and loops should be no different, regardless of whether or not they exist in a particular language. – Panda Pajama Jan 15 '13 at 05:29
  • @パンダパジャマ: Right, but remember: in pure functional languages, variables and values are the same thing. Variables are *immutable*; they *can't* change once they're set. That's why pure functional languages use recursion instead of looping, because loops require changing the loop variable. Which is not possible. So again, it's dealer's choice when you're not talking about a functional language. – Nicol Bolas Jan 15 '13 at 05:53

4 Answers4

5

The Lua manual explains exactly why this works. It describes the index for-loop in terms of a while loop as this:

 for v = e1, e2, e3 do block end

--Is equivalent to:

 do
   local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
   if not (var and limit and step) then error() end
   while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do
     local v = var
     block
     var = var + step
   end
 end

Notice how the loop variable v is declared inside the scope of the while loop. This is done specifically to allow exactly what you're doing.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I had seen the details of that implementation, but I didn't know it was made specifically to allow this. Do you have a specific quote on that? – Panda Pajama Jan 15 '13 at 04:28
  • Found it at "Programming in Lua" section 7.2. It turns out that lua doesn't close over values, it just creates new ones specifically on the generic for construct. It's trivial to show how lua behaves in the same way as c#/JavaScript by rewriting it as a `while`. So the correct way is to close over the variable, not the value. – Panda Pajama Jan 15 '13 at 04:52
  • C# 5 changes `foreach` to move `v` from outside the loop to inside the loop, for the same reason. – Mud Jan 15 '13 at 17:33
3

There is no "correct" way. There are different ways. In C#, you would fix it by making a variable scoped to the loop:

for (int i = 0; i < 10; i++)
{
    int j = i;

    f[i] = (new Action(delegate()
    {
        Console.Write(j + " ");
    }));
}

In JavaScript, you might add a scope by making and calling an anonymous function:

for (var i = 0; i < 10; i++) {
    (function(i) {
        f[i] = function() {
            document.write(i + ' ');
        };
    })(i);
}

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.

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • I know how to "fix" it, but that was not my question. You say there is no "correct" way, but pretty much everything in CS is formally defined, so I think there must be a theoretically correct way. – Panda Pajama Jan 15 '13 at 04:18
  • 1
    @パンダパジャマ: Not as far as I know (or will be unwilling to argue). Computer science can't formally define how every language should behave, 'cause they're all kind of different. – Ry- Jan 15 '13 at 04:19
  • okay, so what is the behavior in lambda calculus? – Panda Pajama Jan 15 '13 at 04:22
  • @パンダパジャマ: I have no idea what lambda calculus is. Does it have closed variables? You can find out, then. – Ry- Jan 15 '13 at 04:23
  • Seems like the correct way is to close over the variable, not the value. Lua also behaves like this, but specifically creates new local variables on the generic `for` construct. Rewriting it with a `while` shows that lua does indeed behave like this, and therefore all these three languages behave the same. – Panda Pajama Jan 15 '13 at 04:54
  • @パンダパジャマ: Most languages act like that, sure, but it's not necessarily right because everyone does it. – Ry- Jan 15 '13 at 04:59
  • Is there any language that doesn't act like that? – Panda Pajama Jan 15 '13 at 05:00
  • @パンダパジャマ: Dunno, haven't bothered looking. I think purely functional ones don't have the concept, though, and I can go ahead and write a language that doesn't act like that at any time. – Ry- Jan 15 '13 at 05:01
  • I can also go and write a language in which assignment actually adds two numbers, but that wouldn't make it correct. Closing over the value actually makes no sense, because the entire idea of closures is modifying a variable outside of the function scope, so if you always closed over values, all closed variables would be local to each function, therefore negating the entire meaning of implementing closures. It's "correct" in the sense that if it didn't work like that, it wouldn't be a closure. – Panda Pajama Jan 15 '13 at 05:06
  • Oh, and purely functional languages do have closures. Have you ever used Haskell? – Panda Pajama Jan 15 '13 at 05:09
  • @パンダパジャマ: No. I don't know functional languages at all ("I think" after all `:)`). I had heard that their "variables" are immutable, though, so the concept of a closure wouldn't exist. Guess not. – Ry- Jan 15 '13 at 05:12
  • @ パンダパジャマ: And "not acting like that" doesn't necessarily mean "closing on a value". I will remember when I'm awake. 'Night. – Ry- Jan 15 '13 at 05:14
2

"what is the behavior of closing over a looped variable in lambda calculus?"

There are no loop variables in lambda calculus.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
1

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 a for(;;) 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 of i (output: 0 1 2 3 ...). Ditto for for i=x,y in Lua and foreach 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:

 foreach (var x in l) { <loop body> }

Used to translate into (pseudocode):

 E e = l.GetEnumerator()
 V v
 while (e.MoveNext()) {
      v = e.Current
      <loop body>
 }

In C# 5, this was changed to:

 E e = l.GetEnumerator()
 while (e.MoveNext()) {
      V v = e.Current
      <loop body>
 }

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.

Community
  • 1
  • 1
Mud
  • 28,277
  • 11
  • 59
  • 92