闭包对循环变量的语义是什么?
考虑以下 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 演算中封闭循环变量的行为是什么?”
Lua 手册对此进行了详细解释。它将索引 for 循环描述为 while 循环,如下所示:
for v = e1, e2, e3 do block end
-- 等同于:
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 循环的作用域内声明的。这是特别为了允许你正在做的事情。
在 lambda 演算中没有循环变量。
闭包循环变量的处理方式与其他变量的处理方式类似。问题在于语言特定的循环结构以及它们是否翻译成把循环变量放在循环内部或外部的代码。例如,在 C#、Lua 或 JavaScript 中使用 while 循环时,这三种语言的结果都是相同的(10)。同样,在 JavaScript 或 C#中使用 for(;;) 循环时也是如此(不适用于 Lua)。
然而,如果使用 JavaScript 中的 for (i in x) 循环,则会发现每个闭包都会得到 i 的新副本(输出:“0 1 2 3 ...”)。在 Lua 中使用 for i=x,y 和 C# 中的 foreach 也是如此。这再次涉及到这些语言如何构造这些循环以及它们如何将循环变量的值暴露给循环体,而不是闭包语义的差异。
事实上,在 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>
}
这是一个破坏性的变化,为了更好地满足程序员对闭包循环变量的期望而进行的。闭包语义没有改变;循环变量的位置改变了。
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
在编程中没有“正确”的方法,只有不同的方法。在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没有块级作用域,只有函数作用域。它们只是不同的语言,它们的做法也不同。