为什么Lua没有“continue”语句?
我在过去几个月里一直在处理 Lua,我真的很喜欢它的大部分功能,但我仍然在其中缺失一些东西:
- 为什么没有
continue
? - 有哪些解决方法可用?
原文链接 https://stackoverflow.com/questions/3524970
语言的词法作用域处理方式在包含 goto
和 continue
时会出现问题。例如,
local a=0
repeat
if f() then
a=1 --change outer a
end
local a=f() -- inner a
until a==0 -- test inner a
循环体内的局部变量 a
的声明掩盖了外部命名为 a
的变量,且该局部变量的作用域延伸到 until
语句的条件部分,因此条件测试的是最内层的 a
。
如果存在 continue
,则必须在所有在条件中使用的变量都进入作用域之后才可以语义上限制它的有效性。这是一项难以向用户说明并在编译器中执行的条件。围绕此问题已经讨论了各种提案,包括简单的回答,即禁止 repeat ... until
循环风格中使用 continue
。到目前为止,没有一个足够引人注目的用例能够使它们被包含在语言中。
解决方法通常是倒转会导致 continue
执行的条件,并在该条件下收集其余的循环体。因此,下面的循环
-- not valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
if isstring(k) then continue end
-- do something to t[k] when k is not a string
end
可以改写为
-- valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end
end
它足够清晰,通常不会成为负担,除非你有一系列精心设计的筛选条件来控制循环操作。
来自 Lua 设计者本人的直击心灵:
我们对 "continue" 的主要担忧是,还有几种其他的控制结构(在我们看来)与 "continue" 一样重要,甚至可能取代它。(例如,使用标签的 break [如 Java 中的] 或者更加通用的 goto。)"continue" 不比其他控制结构机制更特殊,唯一的不同是它存在于更多的编程语言中。(实际上,Perl 有两个 "continue" 语句,"next" 和 "redo",两者都很有用。)
第一部分在 FAQ 中已经回答,就像 slain 指出的那样。
至于解决方法,你可以将循环体包装在一个函数中并从函数中早早地 return
,例如:
-- 打印从1到99的奇数
for a = 1, 99 do
(function()
if a % 2 == 0 then
return
end
print(a)
end)()
end
或者如果你想要 break
和 continue
的功能,则可以让局部函数执行测试,例如:
local a = 1
while (function()
if a > 99 then
return false; -- break
end
if a % 2 == 0 then
return true; -- continue
end
print(a)
return true; -- continue
end)() do
a = a + 1
end
再次翻转,您可以使用以下代码:
for k,v in pairs(t) do
if not isstring(k) then
-- 当 k 不是字符串时对 t[k] 执行某些操作
end
在 Lua 5.2 中,最好的解决方法是使用 goto:
-- 打印 [|1,10|] 之间的奇数
for i=1,10 do
if i % 2 == 0 then goto continue end
print(i)
::continue::
end
这在 LuaJIT 2.0.1 及以上版本中被支持。
你可以将循环体包装在额外的repeat until true
中,然后在循环内部使用do break end
来实现continue
的效果。当然,如果你也想要真正地break
循环,你还需要设置额外的标志。
以下代码会循环5次,每次打印1、2、3。
for idx = 1, 5 do
repeat
print(1)
print(2)
print(3)
do break end -- 跳到下一个循环
print(4)
print(5)
until true
end
在Lua字节码中,这个结构甚至可以转化成单个操作码JMP
!
$ luac -l continue.lua
main <continue.lua:0,0> (22 instructions, 88 bytes at 0x23c9530)
0+ params, 6 slots, 0 upvalues, 4 locals, 6 constants, 0 functions
1 [1] LOADK 0 -1 ; 1
2 [1] LOADK 1 -2 ; 3
3 [1] LOADK 2 -1 ; 1
4 [1] FORPREP 0 16 ; to 21
5 [3] GETGLOBAL 4 -3 ; print
6 [3] LOADK 5 -1 ; 1
7 [3] CALL 4 2 1
8 [4] GETGLOBAL 4 -3 ; print
9 [4] LOADK 5 -4 ; 2
10 [4] CALL 4 2 1
11 [5] GETGLOBAL 4 -3 ; print
12 [5] LOADK 5 -2 ; 3
13 [5] CALL 4 2 1
14 [6] JMP 6 ; to 21 -- 这里!如果从代码中删除"do break end",结果只会因为这一行而不同。
15 [7] GETGLOBAL 4 -3 ; print
16 [7] LOADK 5 -5 ; 4
17 [7] CALL 4 2 1
18 [8] GETGLOBAL 4 -3 ; print
19 [8] LOADK 5 -6 ; 5
20 [8] CALL 4 2 1
21 [1] FORLOOP 0 -17 ; to 5
22 [10] RETURN 0 1
我们可以按照下面的方式实现,它将跳过偶数
local len = 5
for i = 1, len do
repeat
if i%2 == 0 then break end
print(" i = "..i)
break
until true
end
输出:
i = 1
i = 3
i = 5
我们已经遇到了这种情况很多次,我们只是使用标记来模拟continue。我们也尽量避免使用goto语句。
例子:代码打算打印从i=1到i=10的语句,除了i=3。另外,它还打印"loop start"、"loop end"、"if start"和"if end"来模拟存在于你的代码中的其他嵌套语句。
size = 10
for i=1, size do
print("loop start")
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
--continue
end
print(j)
print("if end")
end
print("loop end")
end
通过将所有剩余语句直到循环的结束范围用一个测试标记括起来来实现。
size = 10
for i=1, size do
print("loop start")
local continue = false; -- 在循环开始时初始化标记
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
continue = true
end
if continue==false then -- 测试标记
print(j)
print("if end")
end
end
if (continue==false) then -- 测试标记
print("loop end")
end
end
我不是说这是最好的方法,但它对我们完美地起作用了。
为什么Lua没有continue语句?
因为这是不必要的¹。很少有情况下开发人员需要使用它。
A) 当你有一个非常简单的循环时,比如一个1或2行循环,那么你可以把循环条件反转过来,它仍然非常易读。
B) 当你编写简单的过程式代码(即我们在上个世纪编写代码的方式)时,你应该使用结构化编程(即我们在上个世纪编写更好代码的方式)。
C) 如果你正在编写面向对象的代码,你的循环体应该不超过一到两个方法调用,除非可以在一个或两行代码中表达(在这种情况下,参见A)。
D) 如果你正在编写函数式代码,只需为下一次迭代返回一个简单的尾调用。
唯一需要使用continue
关键字的情况是,如果你想要像Python一样编写Lua,而实际上它并不是这样的。²
有哪些解决方法?
除非满足A),在这种情况下不需要任何解决方法,你应该使用结构化、面向对象或函数式编程。这些范式是Lua建立的,因此,如果你费尽力气避免它们的模式,就会与语言相抗衡。³
一些澄清:
¹Lua是一门非常简约的语言。它试图尽可能少地提供功能,而continue
语句并不是一个必要的功能。
我认为罗伯托·伊鲁撒利姆希在这个2019年的访谈中很好地捕捉到了这种简约主义哲学:
加这个,再加那个,放出这个,最后我们理解最终结论不会满足大多数人,我们不会放所有每个人都想要的选项,因此我们什么都不放。最后,严格模式是一个合理的妥协。
²似乎有大量的程序员从其他语言转到Lua,因为他们试图为他们尝试脚本化的任何程序使用它,他们中的许多人似乎只想写除了他们选择的语言之外的任何东西,这导致了很多像“为什么Lua没有X特性?”这样的问题。
最常见的问题是:“我来自X语言社区;你不能将来自语言X的功能引入到Ruby中吗?”。或类似的事情。对于这些请求,我通常的回答是…… “不,我不会这样做”,因为我们有不同的语言设计和不同的语言开发政策。
³有几种方法可以绕过这个问题;一些用户建议使用goto
,在大多数情况下这是足够好的近似值,但很快就会变得非常丑陋,并且在嵌套循环中完全失效。使用goto
还有让你陷入在任何时候都有可能有SICP的副本扔到你头上的危险。
Lua 是一种轻量级脚本语言,它希望尽可能地小。例如,许多一元操作,如前/后增量操作不可用。
可以使用 goto 代替 continue,例如:
arr = {1,2,3,45,6,7,8}
for key,val in ipairs(arr) do
if val > 6 then
goto skip_to_next
end
# perform some calculation
::skip_to_next::
end
- 如何在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中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
我以前从未使用过 Lua,但是我通过谷歌搜索找到了这个:
http://www.luafaq.org/
请看 问题 1.26。