为什么Lua没有"继续"声明?

Dan*_*ant 135 lua loops language-design

在过去的几个月里,我一直在与Lua打交道,我非常喜欢大部分的功能,但我仍然缺少其中的一些东西:

  • 为什么没有continue
  • 它有什么变通方法?

cat*_*ell 67

在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
Run Code Online (Sandbox Code Playgroud)

自版本2.0.1起,LuaJIT支持此功能

  • 我希望他们有一天会包含一个实际的"继续".`goto`替换看起来不太好,需要更多行.另外,如果你在一个函数中有多个循环来执行此操作,并且使用`:: continue ::`,这不会造成麻烦吗?每个循环组成一个名称听起来不是一件好事. (39认同)
  • 这个论点可以扩展到`break`甚至`return`,`break`去哪里?并“返回”到哪里?此外,“break”功能也可以通过“goto”来实现,创造出不止“一种方式”。但是 Lua 有这两个关键字并且非常有用。 (8认同)
  • “goto”赋予了语言表现力,因为“Continue”继续到哪里?在嵌套循环时,使用名为“goto”的“goto”代替“Continue 2”或“Continue 3”,可以更清晰、甚至更强大地为编码人员提供选择。也许付出的代价是多写一行代码,但它仍然实现了真正的“一种方法”,而不是一些现在实现一件事的语言,因为它不是那么尖锐,需要实现 N 种方法来完成同样的事情以晦涩的方式做事。 (5认同)
  • @Arkt8 有一个区别:“break”和“continue”只会_离开_一个或多个词法范围,但永远不会_进入_它们。作为编译器/静态分析器,这可能会或可能不会让你的生活变得更加困难。 (2认同)

RBe*_*eig 64

语言管理词法范围的方式会产生包括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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

可以写

-- 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
Run Code Online (Sandbox Code Playgroud)

它足够清楚,通常不会成为负担,除非你有一系列精心设计的剔除来控制循环操作.

  • 在没有继续的情况下编写代码没有"足够明确".将代码嵌入到应该使用continue的条件中是一个新手错误,并且编写像这样的丑陋代码的需要不应该得到任何同情.绝对没有任何借口. (59认同)
  • 来自python背景这是一个令人困惑的答案,因为在运行之前,每个范围已经知道它的局部变量是什么.即在达到`until ...`的情况下,我期望未绑定的局部变量错误. (5认同)
  • 你可以测试内部变量说有缺陷的设计.条件超出了内部范围,它不应该访问其中的变量.考虑C中的等价:`do {int i = 0;} while(i == 0);`失败,或者在C++中:`do int i = 0; while(i == 0);`也失败("在这方面没有申明").不幸的是,现在在Lua改变现在太晚了. (5认同)
  • 在 Lua 5.2 中引入 goto 之前,Lua 社区对此进行了很多讨论。当然,`goto` 也有同样的问题。他们最终决定,无论运行时和/或代码生成成本是为了防止它发生什么,都值得拥有一个灵活的 `goto` 来模拟 `continue` 和多级 `break`。您必须在 [Lua list archives](http://lua-users.org/lists/lua-l/) 中搜索相关线程以获取详细信息。由于他们确实引入了`goto`,这显然不是不可逾越的。 (3认同)
  • (最重要的是“没有令人信服的用例”。如果他们不理解 continue 的重要性,他们就没有地方设计语言。) (3认同)
  • 这种解释毫无意义.`local`是仅用于编译器的指令 - 在`local`和变量用法之间的运行时文件无关紧要 - 你不需要在编译器中改变任何东西以保持相同的作用域行为.是的,这可能不是那么明显,需要一些额外的文档,但是,再次重申,它需要编译器中的ZERO更改.`重复do break end until true`我的答案中的例子已经生成**与编译器将继续相同的字节码**唯一的区别是使用`continue`你不需要难看的额外语法来使用它. (3认同)
  • 虽然这本身是正确的,但从历史上看这是不正确的。未添加“继续”,因为它会与现有的“转到”实现冲突。相反,Lua 开发团队不断地向 Lua 开发团队发出继续的愿望,直到他们决定添加 goto,因为原因,从而关闭“继续”请求...... (2认同)
  • @axkibe Methinks Lua 设计师/开发人员从未听说过 Dijkstra,甚至只是他(in?)著名的“GOTO 被认为是有害的”。;-) (2认同)

Ole*_*kov 39

你可以将循环体包裹起来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
Run Code Online (Sandbox Code Playgroud)

这种结构甚至可以转换为JMPLua字节码中的文字一个操作码!

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 这个答案很好,但仍然需要3行而不是一行.(如果"继续"被正确支持)虽然它比goto标签更漂亮,更安全,因为对于那个名称,嵌套循环可能需要避免冲突. (4认同)
  • 但是,它确实避免了goto的"真正"问题,因为你不必为每个psuedo-continue创建一个新的标识符/标签,并且随着代码的修改,它不会出错.*我同意继续会很有用*,但是这个IMO是下一个最好的东西(它实际上需要两行重复/直到更正式的"继续;"......即便如此,如果你有关心有了行数,你总是可以写"do repeat"和"until true end",例如:https://gist.github.com/wilson0x4d/f8410719033d1e0ef771) (3认同)
  • 很高兴看到人们真正考虑性能,甚至在 SO 上提供“luac”输出!有一个当之无愧的赞成票:) (2认同)

fin*_*nnw 16

第一部分在常见问题回答,因为被杀害者指出.

至于一个变通方法,你可以在一个函数中包装循环体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
Run Code Online (Sandbox Code Playgroud)

或者,如果你想同时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
Run Code Online (Sandbox Code Playgroud)

  • 请不要.您在每次迭代时创建闭包环境,这是对内存和GC周期的巨大浪费. (14认同)
  • 去查看`collectgarbage("count")`即使在你的简单的100次尝试后我们也会说话.这种"过早"的优化使得一个高负载项目在上周每分钟重新启动. (4认同)
  • @ OlegV.Volkov虽然这个例子确实对GC施加了相对较高的负载,但它没有泄漏 - 所有临时闭包都将被收集.我不知道你的项目,但IME最重复的重新启动是由于泄漏. (4认同)

Stu*_*ley 15

直接来自Lua自己的设计师:

我们对"继续"的主要关注是,有一些其他控制结构(在我们看来)或多或少与"继续"一样重要,甚至可能取而代之.(例如,中断与标签[中的Java],甚至是更通用转到)"继续"似乎并不比其他控制结构的机制较为特殊,除了它是存在于更多的语言.(Perl实际上有两个"continue"语句,"next"和"redo".两者都很有用.)

  • 是的 - 因为“goto”被公认为是一个不错的编程结构。(结束讽刺)嗯嗯。 (7认同)
  • 我喜欢这个准入:在"我们不打算这样做"的解释之后,"两者都很有用" (4认同)
  • 但这听起来并不比“我只是忘记将 `continue` 放入 Lua,抱歉。”更合理。 (4认同)
  • 要注意的是,他们在“完成”时希望解决的范围是通过在5.2中添加“ goto”构造(编写此答案时尚未发布)来实现的。请参阅[发布于2012年的答案](/sf/answers/905077981/),发布于5.2.0之后。 (2认同)

小智 9

Lua 是轻量级的脚本语言,希望尽可能的小。例如,许多一元运算如前/后增量不可用

您可以使用 goto 代替继续

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
Run Code Online (Sandbox Code Playgroud)


小智 8

我之前从未使用过Lua,但是我用Google搜索并想出了这个:

http://www.luafaq.org/

检查问题1.26.

这是一个常见的抱怨.Lua的作者认为,继续只是众多可能的新控制流机制中的一种(事实上它不能与重复范围规则一起工作/直到次要因素.)

在Lua 5.2中,有一个goto语句,可以很容易地用于完成相同的工作.


Dil*_*lip 5

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

local len = 5
for i = 1, len do
    repeat 
        if i%2 == 0 then break end
        print(" i = "..i)
        break
    until true
end
Run Code Online (Sandbox Code Playgroud)

O / P:

i = 1
i = 3
i = 5
Run Code Online (Sandbox Code Playgroud)


win*_*nux 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
Run Code Online (Sandbox Code Playgroud)

通过用测试标志将所有剩余的语句括起来直到循环的结束范围来实现。

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
Run Code Online (Sandbox Code Playgroud)

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