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

我在过去几个月里一直在处理 Lua,我真的很喜欢它的大部分功能,但我仍然在其中缺失一些东西:

  • 为什么没有 continue
  • 有哪些解决方法可用?

原文链接 https://stackoverflow.com/questions/3524970

点赞
stackoverflow用户374744
stackoverflow用户374744

我以前从未使用过 Lua,但是我通过谷歌搜索找到了这个:

http://www.luafaq.org/

请看 问题 1.26

这是一个常见的抱怨。Lua 作者认为continue只是可能的新控制流机制之一(它无法与repeat/until 的作用域规则相匹配是次要因素)。

在 Lua 5.2 中,有一个简单易用的goto语句可以完成相同的任务。

2010-08-19 18:39:12
stackoverflow用户68204
stackoverflow用户68204

语言的词法作用域处理方式在包含 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

循环体内的局部变量 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

它足够清晰,通常不会成为负担,除非你有一系列精心设计的筛选条件来控制循环操作。

2010-08-19 22:58:56
stackoverflow用户34799
stackoverflow用户34799

来自 Lua 设计者本人的直击心灵:

我们对 "continue" 的主要担忧是,还有几种其他的控制结构(在我们看来)与 "continue" 一样重要,甚至可能取代它。(例如,使用标签的 break [如 Java 中的] 或者更加通用的 goto。)"continue" 不比其他控制结构机制更特殊,唯一的不同是它存在于更多的编程语言中。(实际上,Perl 有两个 "continue" 语句,"next" 和 "redo",两者都很有用。)

2011-05-24 17:02:03
stackoverflow用户12048
stackoverflow用户12048

第一部分在 FAQ 中已经回答,就像 slain 指出的那样。

至于解决方法,你可以将循环体包装在一个函数中并从函数中早早地 return,例如:

-- 打印从1到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
2011-05-25 17:10:54
stackoverflow用户1751479
stackoverflow用户1751479

再次翻转,您可以使用以下代码:

for k,v in pairs(t) do
  if not isstring(k) then
    -- 当 k 不是字符串时对 t[k] 执行某些操作
end
2012-10-16 22:10:54
stackoverflow用户204011
stackoverflow用户204011

在 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 及以上版本中被支持。

2012-10-17 07:58:53
stackoverflow用户936986
stackoverflow用户936986

你可以将循环体包装在额外的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
2012-12-11 17:20:54
stackoverflow用户2799257
stackoverflow用户2799257

我们可以按照下面的方式实现,它将跳过偶数

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
2014-09-11 07:06:46
stackoverflow用户748115
stackoverflow用户748115

我们已经遇到了这种情况很多次,我们只是使用标记来模拟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

我不是说这是最好的方法,但它对我们完美地起作用了。

2016-08-03 08:30:35
stackoverflow用户4984564
stackoverflow用户4984564

为什么Lua没有continue语句?

因为这是不必要的¹。很少有情况下开发人员需要使用它。

A) 当你有一个非常简单的循环时,比如一个1或2行循环,那么你可以把循环条件反转过来,它仍然非常易读。

B) 当你编写简单的过程式代码(即我们在上个世纪编写代码的方式)时,你应该使用结构化编程(即我们在上个世纪编写更好代码的方式)。

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

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

唯一需要使用continue关键字的情况是,如果你想要像Python一样编写Lua,而实际上它并不是这样的。²

有哪些解决方法?

除非满足A),在这种情况下不需要任何解决方法,你应该使用结构化、面向对象或函数式编程。这些范式是Lua建立的,因此,如果你费尽力气避免它们的模式,就会与语言相抗衡。³


一些澄清:

¹Lua是一门非常简约的语言。它试图尽可能少地提供功能,而continue语句并不是一个必要的功能。

我认为罗伯托·伊鲁撒利姆希在这个2019年的访谈中很好地捕捉到了这种简约主义哲学:

加这个,再加那个,放出这个,最后我们理解最终结论不会满足大多数人,我们不会放所有每个人都想要的选项,因此我们什么都不放。最后,严格模式是一个合理的妥协。

²似乎有大量的程序员从其他语言转到Lua,因为他们试图为他们尝试脚本化的任何程序使用它,他们中的许多人似乎只想写除了他们选择的语言之外的任何东西,这导致了很多像“为什么Lua没有X特性?”这样的问题。

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

最常见的问题是:“我来自X语言社区;你不能将来自语言X的功能引入到Ruby中吗?”。或类似的事情。对于这些请求,我通常的回答是…… “不,我不会这样做”,因为我们有不同的语言设计和不同的语言开发政策。

³有几种方法可以绕过这个问题;一些用户建议使用goto,在大多数情况下这是足够好的近似值,但很快就会变得非常丑陋,并且在嵌套循环中完全失效。使用goto还有让你陷入在任何时候都有可能有SICP的副本扔到你头上的危险。

2019-11-18 16:43:26
stackoverflow用户6208842
stackoverflow用户6208842

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
2020-02-25 03:13:56