1

考虑以下 lua 代码:

f = {}

for i = 1, 10 do
    f[i] = function()
        print(i .. " ")
    end
end

for k = 1, 10 do
    f[k]()
end

这将打印从 1 到 10 的数字。在这种情况下,i对外部循环的每次迭代的值都是封闭的。这就是我一直以来对闭包的理解,我很高兴......

...直到我将一些 lua 代码移植到 c# 中,我尝试做同样的事情:

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]();
}

现在我将数字 10 打印了 10 次(让我们忘记 lua 数组是基于 1 的)。实际上,在这种情况下,闭包对变量起作用,而不是对它的值起作用,这很有意义,因为我只是在第一个循环结束后才调用函数。

JavaScript 似乎具有相同的语义(关闭变量):

var f = []

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

for (var k = 0; k < 10; k++)
{
    f[k]();
}

实际上,这两种行为都很有意义,但当然是不相容的。

如果有“正确”的方法来做到这一点,那么 lua 或 c# 和 JavaScript 都是错误的(我还没有尝试过其他语言)。所以我的问题是:“在循环中关闭变量的“正确”语义是什么?

编辑:我不是在问如何“解决”这个问题。我知道我可以在循环中添加一个局部变量并关闭该变量以获得 c#/JavaScript 中的 lua 行为。我想知道关闭循环变量的理论上正确含义是什么,以及哪些语言以每种方式实现关闭的简短列表的奖励积分。

编辑:改写我的问题:“在 lambda 演算中关闭循环变量的行为是什么?”

4

4 回答 4

5

Lua 手册准确地解释了为什么这样做。它用 while 循环来描述索引 for 循环,如下所示:

 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

注意循环变量v是如何在循环范围内声明while的。这样做是为了准确地允许您正在做的事情。

于 2013-01-15T04:14:36.910 回答
3

没有“正确”的方法。有不同的方法。在 C# 中,您可以通过将变量设置为循环范围来修复它:

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

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

在 JavaScript 中,您可以通过创建和调用匿名函数来添加范围:

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

C# 中的迭代变量没有循环范围。JavaScript 没有块作用域,只有函数作用域。他们只是不同的语言,他们做的事情不同。

于 2013-01-15T04:09:53.113 回答
2

“在 lambda 演算中关闭循环变量的行为是什么?”

lambda 演算中没有循环变量。

于 2013-01-15T16:57:09.600 回答
1

关闭循环变量就像关闭任何其他变量。问题在于特定于语言的循环结构以及它们是否转换为将循环变量置于循环内部或外部的代码。

例如,如果您while在 C#、Lua 或 JavaScript 中使用循环,则所有三种语言的结果都是相同的 (10)。同样适用for(;;)于 JavaScript 或 C# 中的循环(在 Lua 中不可用)。

但是,如果您for (i in x)在 JavaScript 中使用循环,您会发现每个闭包都会获得i(output: 0 1 2 3 ...) 的新副本。在Luafor i=x,yforeachC# 中也是如此。同样,这与这些语言如何构建这些循环以及它们如何将循环变量的值暴露给循环体有关,而不是闭包语义上的差异。

实际上,在 C# 的情况下foreach,此行为从 4.5更改为 5 。这个构造:

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

用于翻译成(伪代码):

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

在 C# 5 中,这已更改为:

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

这是一个突破性的变化,这样做是为了在关闭循环变量时更好地满足程序员的期望。闭包语义没有改变;循环变量的位置确实。

于 2013-01-15T18:08:06.633 回答