【发布时间】:2024-04-13 20:05:01
【问题描述】:
在过去的几个月里,我一直在与 Lua 打交道,我真的很喜欢其中的大部分功能,但我仍然缺少其中的一些东西:
- 为什么没有
continue? - 有哪些解决方法?
【问题讨论】:
-
自从提出这个问题后,Lua 得到了一个
goto语句,可以用来实现 continue。请参阅下面的答案。
标签: loops lua language-design
在过去的几个月里,我一直在与 Lua 打交道,我真的很喜欢其中的大部分功能,但我仍然缺少其中的一些东西:
continue?【问题讨论】:
goto 语句,可以用来实现 continue。请参阅下面的答案。
标签: loops lua language-design
在 Lua 5.2 中,最好的解决方法是使用 goto:
-- prints odd numbers in [|1,10|]
for i=1,10 do
if i % 2 == 0 then goto continue end
print(i)
::continue::
end
LuaJIT 从 2.0.1 版本开始支持此功能
【讨论】:
continue。 goto 替换看起来不太好,需要更多行。此外,如果您在一个函数中有多个循环执行此操作,并且都使用::continue::,这不会造成麻烦吗?为每个循环起一个名字听起来不是一件体面的事情。
goto 赋予语言表现力,因为continue 继续在哪里?取而代之的是 continue 2 或 continue 3 在嵌套循环时,命名为 goto 使代码更清晰,甚至更强大,为编码人员提供了选择。也许付出的代价是多写一行代码,但它仍然实现了一个真正的“一条路”,而不是一些现在实现一件事的语言,因为它不是那么奇怪,需要实现 N 种方法来做同样的事情以晦涩难懂的方式。
break,甚至return,break到哪里?和return 去哪里?此外,break 功能也可以通过goto 来实现,创造的不仅仅是“一条路”。但是 Lua 有这两个关键字,而且非常有用。
语言管理词法范围的方式会导致同时包含 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
循环体内local 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
这很清楚,除非您有一系列精心挑选的控制循环操作的剔除,否则通常不会成为负担。
【讨论】:
until... 的情况下,我预计会出现未绑定的局部变量错误。
goto 之前,Lua 社区对此进行了很多讨论。当然,goto 也有同样的问题。他们最终决定,无论运行时和/或代码生成成本是为了保护它,都值得拥有一个灵活的goto 来模拟continue 和多级break。您必须搜索 Lua list archives 以获取相关线程以获取详细信息。既然他们确实介绍了goto,那显然不是不可逾越的。
local 是仅编译器指令 - 在 local 和变量使用之间的运行时指令无关紧要 - 您无需更改编译器中的任何内容即可保持相同的作用域行为。是的,这可能不是那么明显,需要一些额外的文档,但是,再次重申,它需要对编译器进行零更改。我的答案中的repeat do break end until true 示例已经完全生成编译器将继续使用的相同字节码,唯一的区别是continue 你不需要丑陋的额外语法来使用它。
do{int i=0;}while (i == 0); 失败,或者在 C++ 中:do int i=0;while (i==0); 也失败(“未在此范围内声明”)。不幸的是,现在在 Lua 中改变它为时已晚。
您可以将循环体包裹在额外的repeat until true 中,然后在里面使用do break end 以获得继续的效果。当然,如果您还打算真正将break 跳出循环,则需要设置其他标志。
这将循环 5 次,每次打印 1、2 和 3。
for idx = 1, 5 do
repeat
print(1)
print(2)
print(3)
do break end -- goes to next iteration of for
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 -- Here it is! If you remove do break end from code, result will only differ by this single line.
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
【讨论】:
luac 输出!有一个当之无愧的赞成票:)
Straight from the designer of Lua himself:
我们对“继续”的主要关注是,还有其他几个控制结构(在我们看来)或多或少与“继续”一样重要,甚至可能取代它。 (例如,用标签中断 [如在 Java 中] 甚至更通用的 goto。)“继续”似乎并不比其他控制结构机制更特别,只是它出现在更多语言中。 (Perl 实际上有两个“继续”语句,“下一步”和“重做”。两者都很有用。)
【讨论】:
continue 放入 Lua,抱歉。”更合理。
至于解决方法,您可以将循环体包装在一个函数中,并从该函数中提前return,例如
-- Print the odd numbers from 1 to 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
【讨论】:
collectgarbage("count") 即使在你简单的 100 次尝试之后,然后我们会谈谈。这种“过早”的优化让一个高负载项目免于上周每分钟重启一次。
我以前从未使用过 Lua,但我在 Google 上搜索了一下并想出了这个:
这是一个常见的抱怨。 Lua 的作者认为 continue 只是许多可能的新控制流机制之一(事实上它不能与 repeat/until 的范围规则一起工作,这是次要因素。)
在 Lua 5.2 中,有一个 goto 语句可以很容易地用来做同样的工作。
【讨论】:
Lua 是轻量级的脚本语言,它希望尽可能小。例如,许多一元操作,如前/后增量是不可用的
您可以使用 goto like 代替 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
【讨论】:
我们可以如下实现,它会跳过偶数
local len = 5
for i = 1, len do
repeat
if i%2 == 0 then break end
print(" i = "..i)
break
until true
end
O/P:
i = 1
i = 3
i = 5
【讨论】:
我们多次遇到这种情况,我们只是使用一个标志来模拟继续。我们也尽量避免使用 goto 语句。
示例:代码打算打印从 i=1 到 i=10 的语句,除了 i=3。此外,它还会打印“循环开始”、“循环结束”、“如果开始”和“如果结束”来模拟代码中存在的其他嵌套语句。
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; -- initialize flag at the start of the loop
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
continue = true
end
if continue==false then -- test flag
print(j)
print("if end")
end
end
if (continue==false) then -- test flag
print("loop end")
end
end
我并不是说这是最好的方法,但它对我们来说非常有效。
【讨论】:
再次使用反相,您可以简单地使用以下代码:
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end
【讨论】:
因为没有必要¹。开发人员需要它的情况很少。
A) 当你有一个非常简单的循环时,比如 1 或 2 线,那么你可以把循环条件反过来,它仍然很可读。
B) 当您编写简单的过程代码(也就是我们在上个世纪如何编写代码)时,您还应该应用结构化编程(也就是我们在上个世纪如何编写更好的代码)
C) 如果你正在编写面向对象的代码,你的循环体应该包含不超过一到两个方法调用,除非它可以用一个或两个行来表示(在这种情况下,请参阅 A)
D) 如果您正在编写函数式代码,只需为下一次迭代返回一个普通的尾调用。
您想要使用 continue 关键字的唯一情况是,如果您想像 Python 一样编写 Lua,但事实并非如此。²
除非 A) 适用,在这种情况下不需要任何变通方法,您应该进行结构化、面向对象或函数式编程。这些是 Lua 构建的范式,所以如果你竭尽全力避免它们的模式,你就会与这种语言作斗争。³
一些澄清:
¹ Lua 是一种非常简约的语言。它试图拥有尽可能少的功能,而continue 语句在这个意义上并不是必不可少的功能。
我认为Roberto Ierusalimschy 在2019 interview 中很好地捕捉到了这种极简主义哲学:
添加那个,那个,那个,把那个说出来,最后我们明白最后的结论不会让大多数人满意,我们不会把每个人都想要的所有选项都放进去,所以我们什么都不放。最后,严格模式是一个合理的折衷方案。
² 似乎有大量程序员从其他语言转向 Lua,因为他们尝试编写脚本的任何程序都碰巧使用它,而且他们中的许多人似乎不想写任何东西他们选择的语言,这导致了许多问题,例如“为什么 Lua 没有 X 功能?”
Matz 在recent interview 中描述了与 Ruby 类似的情况:
最受欢迎的问题是:“我来自语言 X 社区;你能不能将语言 X 的功能介绍给 Ruby?”,或类似的问题。而我通常对这些请求的回答是……“不,我不会那样做”,因为我们有不同的语言设计和不同的语言开发政策。
³有几种方法可以解决这个问题;一些用户建议使用goto,在大多数情况下这是一个足够好的近似值,但很快就会变得非常难看,并且会因嵌套循环而完全中断。使用gotos 还会让您面临在向其他人展示您的代码时收到一份 SICP 副本的危险。
【讨论】:
continue 可能是一个方便的功能,但这并不是必需。很多人在没有它的情况下使用 Lua 就很好,所以除了一个对任何编程语言都不是必需的简洁功能之外,真的没有其他任何理由。