为什么 Lua 没有“ keep”语句?

在过去的几个月里,我一直在与 Lua 打交道,我真的很喜欢它的大部分特性,但是我仍然缺少其中的一些东西:

  • 为什么没有 continue
  • 有什么解决办法吗?
179325 次浏览

我以前从来没用过 Lua 但我谷歌了一下,得出了这个结论:

Http://www.luafaq.org/

检查 问题1.26

这是常见的抱怨。Lua 的作者认为,Continue 只是许多可能的新控制流机制中的一种(它不能与 repeat/until 的作用域规则一起工作的事实是次要因素)

在 Lua 5.2中,有一个 goto 语句,可以很容易地用来做同样的工作。

语言管理词法范围的方式会产生包括 gotocontinue在内的问题,

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,那么它必须在语义上受到限制,只有在条件中使用的所有变量都进入作用域之后才能有效。这是一个很难向用户提供文档并在编译器中强制执行的条件。围绕这个问题的各种建议已经被讨论过,包括不允许 continuerepeat ... until风格的循环的简单答案。到目前为止,还没有一个足够引人注目的用例将它们包含在语言中。

解决方法通常是反转将导致执行 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

它非常清楚,而且通常不是一个负担,除非您有一系列控制循环操作的精心挑选。

正如 被杀所指出的,第一部分在 常见问题中得到了回答。

至于变通方法,您可以将循环体封装在一个函数中,然后提前将 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

或者,如果您希望同时具有 breakcontinue功能,那么让本地函数执行测试,例如。

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
-- do something to t[k] when k is not a string
end

在 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

从2.0.1版开始,LuaJIT 就支持这一点

你可以在附加的 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

我们可以实现如下,它将跳过偶数

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

我们多次遇到这种情况,我们只是使用一个标志来模拟继续。我们也尽量避免使用 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;  -- 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

我并不是说这是最好的方法,但它对我们非常有效。

为什么没有继续?

因为它是不必要的。很少有开发人员需要它的情况。

A)当你有一个非常简单的循环时,比如一个1或2行的循环,那么你只需要将循环条件转换一下,它仍然具有很大的可读性。

B)当你编写简单的过程代码(也就是。我们在上个世纪是如何编写代码的) ,你也应该使用结构化编程。我们是如何在上个世纪写出更好的代码的)

C)如果您正在编写面向对象的代码,您的循环体应该不超过一个或两个方法调用,除非它可以用一个或两个行来表示(在这种情况下,请参见 A)

D)如果你正在编写函数式代码,只需返回一个简单的下一次迭代的尾部调用。

唯一需要使用 continue关键字的情况是,您希望将 Lua 编码成 Python 的样子,但实际情况并非如此。2

有什么解决办法吗?

除非 A)适用,在这种情况下不需要任何变通方法,你应该做结构化,面向对象或函数式编程。这些都是 Lua 所构建的范例,所以如果你想要避免它们的模式,你就会与这种语言作斗争。3


需要澄清的是:

1 Lua 是一种非常简约的语言。它试图拥有尽可能少的特性,而 continue语句在这个意义上并不是一个重要的特性。

我认为这种极简主义的哲学很好地被 罗伯特・耶路撒冷在这个 2019年面试中捕捉到了:

加上那个,那个,还有那个,把那个拿出来,最后我们知道最终的结论不会让大多数人满意我们不会把所有人想要的选项都拿出来,所以我们什么都不会拿出来。最后,严格模式是一种合理的折衷方案。

2似乎有很多程序员从其他语言来到 Lua,因为无论他们尝试为什么程序编写脚本,都会碰巧使用它,而且他们中的很多人似乎不想编写除了他们选择的语言之外的任何东西,这导致了很多问题,比如“为什么 Lua 没有 X 功能?”

Matz 描述了 Ruby 在 最近的采访中的类似情况:

最受欢迎的问题是: “我来自语言 X 社区; 您不能介绍一个从语言 X 到 Ruby 的特性吗?”或者类似的东西。对于这些要求,我通常的回答是... “不,我不会那样做”,因为我们有不同的语言设计和不同的语言开发策略。

有几种方法可以解决这个问题; 一些用户建议使用 goto,这在大多数情况下是一个很好的近似值,但是很快就会变得非常丑陋,并且完全被嵌套的循环所破坏。使用 goto还会使您处于危险之中,无论何时向其他任何人显示代码,都会有一个 SICP 副本抛给您。

Lua 是一种轻量级的脚本语言,它希望尽可能地小。例如,许多一元操作(如前/后增量)不可用

不用继续,你可以用 goto like

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