为什么Lua没有“继续”声明?


142

在过去的几个月中,我一直在与Lua打交道,我真的很喜欢大多数功能,但是我仍然缺少其中的一些功能:

  • 为什么没有continue
  • 有什么解决方法?

12
自从提出这个问题以来,Lua得到了goto可以用于继续执行的声明。请参阅下面的答案。
lhf

Answers:


69

在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支持此功能。


46
我希望它们包括实际的continue一天。该goto替换不很好看,需要更多的线路。另外,如果您在一个函数中有多个循环同时执行此操作,那不会造成麻烦::continue::吗?在每个循环中命名一个名字听起来并不像一件好事。
2014年

65

语言管理词法范围的方式产生了同时包含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存在,则必须在语义上进行限制,使其仅在条件中使用的所有变量都进入范围后才有效。这是很难记录给用户并在编译器中强制执行的条件。已经讨论了有关此问题的各种建议,包括不允许continue使用repeat ... until循环样式的简单答案。到目前为止,还没有足够令人信服的用例将它们包含在语言中。

解决方法通常是反转将导致continue执行a的条件,并在该条件下收集其余的循环体。因此,以下循环

-- 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

除非您有一系列控制循环操作的精心挑选的剔除器,否则就足够清楚了,通常不会成为负担。


5
来自python背景,这是一个令人困惑的答案,因为那里的每个作用域在运行之前都已经知道其局部变量是什么。也就是说,在达到的情况下,我期望出现未绑定的局部变量错误until...
ubershmekel,2012年

2
在引入gotoLua 5.2 之前,Lua社区中对此进行了大量讨论。自然,goto具有相同的问题。他们最终决定,无论为了防止运行时和/或代码生成成本而付出的代价,都值得拥有一种goto可用于模拟continue多级和多级的灵活性的好处break。您必须在Lua列表档案中搜索相关线程以获取详细信息。自从他们引入以来goto,它显然不是不可克服的。
RBerteig 2012年

71
没有继续写代码就没有“足够清楚”的地方。将代码嵌套在应该使用continue的条件中是一个新手错误,并且编写这样的丑陋代码的需求不应引起任何同情。绝对没有任何借口。
格伦·梅纳德

4
这种解释是没有道理的。local是仅编译器指令-介于什么运行时指令local和变量用法之间无关紧要-您无需在编译器中进行任何更改即可维持相同的作用域行为。是的,这可能并不那么明显,需要一些其他文档,但是再次重申,它要求在编译器中进行零更改。repeat do break end until true我的答案中的示例已经生成了与编译器将继续使用的字节码完全相同的字节码,唯一的区别是,continue您不需要难看的额外语法来使用它。
Oleg V. Volkov,

7
您可以测试内部变量说明存在缺陷的设计。条件不在内部范围之内,因此它不能访问其中的变量。考虑C:do{int i=0;}while (i == 0);失败或C ++:do int i=0;while (i==0);失败的等效项(“未在此范围内声明”)。不幸的是,现在改变现状已经太迟了。
Pedro Gimeno

45

您可以将循环体另外包装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

这种构造甚至可以将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

4
这个答案很好,但是仍然需要3行而不是仅仅1行。(如果正确支持“ continue”),但它比goto标签更漂亮和安全,因为对于该名称,嵌套循环可能需要避免冲突。
2014年

3
但是,它确实避免了goto的“实际”问题,因为您不必为每个伪连续创建新的标识符/标签,并且随着时间的推移修改代码的可能性也较小。我同意继续将是有用的,但是此IMO是次要的事情(重复和直到比较正式的“继续”它确实需要两行;如果您担心行,则即使如此)算你总是可以写“重复”和“直到真正的结局”,例如:gist.github.com/wilson0x4d/f8410719033d1e0ef771
肖恩·威尔逊

1
很高兴看到人们实际考虑性能,甚至提供luacSO的输出!拥有当之无愧的
赞誉

16

直接来自Lua的设计师

我们对“继续”的主要关注是(我们认为)还有其他一些控制结构与“继续”一样重要,甚至可能取代它。(例如,用标签打断(如Java中的标签,甚至使用更通用的goto。)。“继续”似乎比其他控制结构机制更特殊,除了它以更多的语言提供。(Perl实际上有两个“ continue”语句,“ next”和“ redo”。两者都很有用。)


5
我喜欢这样的说法:“对我们都不会做”的解释之后,“两者都很有用”
David Ljung Madison Stellar

2
这是需要注意的范围,他们寻找地址时,他们没有做到这一点,通过添加5.2“转到”结构(当这个答案写这还没有被释放)。在5.2.0发布后,请参阅2012年的答案
Stuart P. Bentley '18年

3
正确-因为众所周知“ goto”是一种不错的编程结构。(讽刺)嗯。
David Ljung Madison Stellar

2
但这听起来比“我只是忘了放进continueLua,对不起” 更合理。
neoedmund '18

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

或者,如果您同时需要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

15
请不要。您在每次迭代中创建关闭环境,这会浪费大量的内存和GC周期。
Oleg V. Volkov

4
collectgarbage("count")即使您尝试了100次,也要检查一下,然后我们再谈。这种“过早”的优化使上周每分钟重启一次的高负荷项目免于一次失败。
Oleg V. Volkov

4
@ OlegV.Volkov虽然此示例确实在GC上施加了较高的负载,但不会泄漏-将收集所有临时关闭。我不了解您的项目,但IME多数重复重启是由于泄漏。
finnw 2012年

9

我以前从未使用过Lua,但是我用Google搜索了它,并提出了以下建议:

http://www.luafaq.org/

检查问题1.26

这是常见的投诉。Lua的作者认为,continue只是许多可能的新控制流机制之一(它不能与重复/直到范围规则一起使用的事实是次要因素)。

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


7

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

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

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

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


4

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

3

同样,通过反相,您可以简单地使用以下代码:

for k,v in pairs(t) do
  if not isstring(k) then 
    -- do something to t[k] when k is not a string
end

反转的问题是,序列中经常有多个条件(例如验证用户输入)。而且,由于在此过程中的任何一点都可能需要短路,因此反转意味着必须连续嵌套条件(而不是“先是先坏后逃;否则先后先逃逸”,这很简单,你最终的代码,如“是这样行则是这样行则是这个没关系那么做呢??”这是非常过分的。
莱斯利·克劳斯

-3

为什么没有继续?

因为这是不必要的¹。在极少数情况下,开发人员会需要它。

A)当您有一个非常简单的循环(例如1或2线)时,您只需扭转循环条件,它仍然可读性强。

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

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

D)如果您正在编写功能代码,则只需为下一次迭代返回一个简单的尾调用即可。

唯一要使用continue关键字的情况是,如果您想像Lua一样编写Lua的代码,事实并非如此。²

有什么解决方法?

除非适用A),否则无需任何解决方法,则应进行结构化,面向对象或函数式编程。这些就是Lua所针对的范例,因此,如果您竭尽所能避免使用它们的模式,那您将与该语言作斗争。³


一些澄清:

¹Lua是一种非常简约的语言。它尝试拥有尽可能少的功能,continue从这个意义上说,声明并不是必需的功能。

我认为罗伯托·伊鲁萨利姆斯基Roberto Ierusalimschy)2019年的这次采访中很好地体现了这种极简主义哲学:

最后,我们了解最终结论将无法满足大多数人的需求,并且我们不会提供所有人都想要的所有选择,因此我们什么也不做。最后,严格模式是一个合理的折衷方案。

²似乎有大量的程序员从其他语言来过Lua,因为他们尝试编写脚本的任何程序都碰巧使用了它,而且他们中的许多人似乎不想写其他任何东西,而不仅仅是他们的语言。选择,导致出现许多问题,例如“为什么Lua没有X功能?”

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

最受欢迎的问题是:“我来自X语言社区;您不能将X语言的功能引入Ruby吗?”或类似的东西。我对这些要求的通常回答是……“不,我不会那样做”,因为我们有不同的语言设计和不同的语言开发政策。

³有几种方法可以解决这个问题;一些用户建议使用goto,这在大多数情况下已经足够好了,但很快就会变得很丑陋,并且会被嵌套循环完全破坏。使用gotos还会使您有危险,每当您向其他人展示代码时,都会被抛出SICP的副本。


1
我之所以投反对票,是因为第一句话显然是错误的,其余的答案无济于事。
bfontaine

没有帮助吗?也许; 这是一个基于意见的答案。不过,第一句话显然是正确的;continue也许是一个方便的功能,但这并没有必要。没有Lua,很多人都会很好地使用Lua,因此,除了没有对任何编程语言都不重要的纯净功能之外,没有其他理由可以使用Lua。
DarkWiiPlayer

那不是一个论点:你不能说人们没有选择就“没有它就很好”。
bfontaine

我认为那时我们对“必需”有不同的定义。
DarkWiiPlayer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.