Luaに「継続」ステートメントがないのはなぜですか?


143

私は過去数か月でLuaをたくさん扱ってきました、そして私はほとんどの機能が本当に好きですが、それらの中でまだ何かが足りません:

  • なぜないのcontinueですか?
  • それにはどのような回避策がありますか?

12
この質問が行われたため、Luaはgoto続行の実装に使用できるステートメントを取得しました。以下の回答をご覧ください。
lhf

回答:


70

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でサポートされています。


47
実際のcontinue一日が含まれているといいのですが。goto交換は非常に素晴らしい見て、より多くの行を必要としません。また、1つの関数でこれを実行する複数のループがあり、両方ともループがある場合、問題は発生し::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存在する場合、条件で使用されるすべての変数がスコープに入った後でのみ有効になるように、意味的に制限する必要があります。これは、ユーザーに文書化してコンパイラーに適用するのが難しい条件です。ループ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

それは十分に明確であり、ループ操作を制御する一連の精巧なカリングがない限り、通常は負担にはなりません。


5
Pythonの背景から来ると、これは混乱を招く答えです。実行する前に、そこにあるすべてのスコープがローカル変数をすでに知っているからです。つまり、に到達した場合に、バインドされていないローカル変数エラーが発生すると予想していましたuntil...
ubershmekel

2
gotoLua 5.2に導入される前に、Luaコミュニティでこれについて多くの議論がありました。当然、goto同じ問題があります。彼らは最終的に、実行時間やコード生成のコストがそれから保護することは何でも、goto両方continueとマルチレベルの両方をエミュレートするために使用できる柔軟性を持つことの利点に見合うと判断しましたbreak。詳細を取得するには、関連するスレッドのLuaリストアーカイブを検索する必要があります。彼らが導入したので、goto明らかに乗り越えられなかったわけではありません。
RBerteig 2012年

72
続行せずにコードを書くことについて、「十分に明確」なことは何もありません。継続が使用されるべき条件の中にコードをネストするのは初心者の間違いであり、そのような醜いコードを書く必要は同情を受けるべきではありません。言い訳は絶対にありません。
Glenn Maynard

4
この説明は意味がありません。localコンパイラのみのディレクティブです-ランタイムの命令localと変数の使用の間に関係はありません-同じスコープの動作を維持するためにコンパイラで何かを変更する必要はありません。はい、これはそれほど明白ではなく、いくつかの追加のドキュメントが必要な場合がありますが、繰り返しますが、コンパイラでのゼロ変更が必要です。repeat do break end until true私の答えの例では、コンパイラが続行する場合とまったく同じバイトコードがすでに生成されていcontinueますが、唯一の違いは、それを使用するために醜い追加の構文を必要としないことです。
Oleg

7
内部変数をテストできるということは、設計の欠陥について語っています。条件は内部スコープの外にあり、内部の変数にアクセスできません。C:での同等のdo{int i=0;}while (i == 0);失敗、またはC ++:do int i=0;while (i==0);での失敗も検討してください(「このスコープでは宣言されていません」)。残念ながら、今ルアでそれを変えるには遅すぎます。
Pedro Gimeno、2016年

46

ループ本体を追加でラップし、継続の効果の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

この構造JMPは、Luaバイトコードのリテラル1オペコードにも変換されます。

$ 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
この答えはいいですが、それでも1行ではなく3行が必要です。( "continue"が適切にサポートされている場合)gotoラベルよりも少しきれいで安全ですが、その名前の場合、ネストされたループでは衝突を回避する必要がある場合があるためです。
2014

3
ただし、それはgotoの「実際の」問題を回避します。これは、擬似継続ごとに新しい識別子/ラベルを作成する必要がなく、コードが時間の経過とともに変更されるため、エラーが発生しにくくなるためです。継続が有用であることに同意しますが、このIMOは次善の策です(そして、実際には、リピートまでの2行と、より正式な "継続"が必要です。たとえば、 "do repeat"と "until true end"を常に記述できるカウント:gist.github.com/wilson0x4d/f8410719033d1e0ef771
Shaun Wilson

1
人々が実際にパフォーマンスを検討しluac、SOで出力を提供するのを見るのはうれしいです!当然の
賛成投票をしてください

16

ルア自身のデザイナーから直接

「継続」に関する私たちの主な懸念は、「継続」とほぼ同じくらい重要であり、それを置き換えることさえあるいくつかの他の制御構造があることです。(たとえば、[Javaの場合のように]ラベルで中断するか、より一般的なgotoです。)「続行」は、より多くの言語で存在することを除いて、他の制御構造メカニズムよりも特別なようには見えません。(Perlには実際には、「次へ」と「やり直し」という2つの「継続」ステートメントがあります。どちらも便利です。)


5
私はアドミタンスが大好きです。「私たちはやらない」という説明の直後に「どちらも役に立つ」
David Ljung Madison Stellar

2
5.2で「goto」構成を追加することにより、彼らがそうしたときに彼ら対処しようとしてたスコープに注意することでした(この回答が書かれたときにリリースされていなかった)。5.2.0がリリースされた後の2012年の回答をご覧ください。
スチュアートP.ベントレー

3
そうです-'goto'はまともなプログラミング構造であるとよく認識されているからです。(皮肉を終える)ああ。
David Ljung Madison Stellar

2
でも、「continueルアに入れるのを忘れてしまってすみません」ほど合理的に聞こえませんでした。
neoedmund

16

殺害が指摘したように、最初の部分はFAQで回答されています。

回避策としては、ループの本体を関数でラップし、それから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

16
しないでください。各反復でクロージャ環境を作成すると、これはメモリとGCサイクルの膨大な無駄になります。
オレグV.ボルコフ

4
collectgarbage("count")単純な100回の試行の後も確認してから、話をします。このような「時期尚早」の最適化により、先週、毎分1回の再起動から1つの高負荷プロジェクトを救いました。
Oleg V. Volkov

4
@ OlegV.Volkovこの例ではGCに比較的高い負荷がかかりますが、リークは発生しません-すべての一時的なクロージャが収集されます。私はあなたのプロジェクトについては知りませんが、IMEの再起動のほとんどはリークが原因です。
finnw

9

これまでLuaを使用したことはありませんが、Googleを使ってこれを思いつきました。

http://www.luafaq.org/

質問1.26を確認してください。

これはよくある不満です。Luaの作成者は、continueは、考えられる多くの新しい制御フローメカニズムの1つにすぎないと感じました(繰り返しのスコープルールでは機能できないという事実が二次的要因でした)。

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 = 3を除くi = 10までのステートメントを出力することを目的としています。さらに、「ループスタート」、「ループエンド」、「ifスタート」、「ifエンド」も出力して、コード内に存在する他のネストされたステートメントをシミュレートします。

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を次のように使用できます

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)オブジェクト指向のコードを記述している場合、1行または2行で表現できない場合を除いて、ループ本体は1つまたは2つのメソッド呼び出しで構成する必要があります(この場合、Aを参照)。

D)関数型コードを記述している場合は、次の反復のために単純な末尾呼び出しを返すだけです。

continueキーワードを使用する唯一のケースは、PythonのようにLuaをコーディングする場合ですが、Pythonはそうではありません。²

それにはどのような回避策がありますか?

A)が適用されない限り、その場合の回避策は必要ありません。構造化、オブジェクト指向、または関数型プログラミングを実行する必要があります。これらはLuaが構築されたパラダイムです。そのため、パターンを回避するために邪魔をすると、言語と戦うことになります。³


いくつかの明確化:

¹Luaは非常にミニマルな言語です。できる限り少ない機能を使用しようとしますがcontinue、その意味ではステートメントは必須の機能ではありません。

このミニマリズムの哲学は、この2019年のインタビューRoberto Ierusalimschyによってよく理解されていると思います。

あれこれあれこれを出して、最後に私たちは最終的な結論がほとんどの人々を満足させないことを理解し、誰もが望むすべてのオプションを置くわけではないので、何も入れません。結局、strictモードは妥当な妥協案です。

²他の言語からLuaに来るプログラマーの数は非常に多いようです。なぜなら、スクリプトを作成しようとしているプログラムがたまたまそれを使用しているからです。多くのプログラマーは、自分の言語以外のものを書きたくないようです。これは、「LuaにX機能がないのはなぜですか?」などの多くの質問につながります。

Matz最近のインタビューでRubyの同様の状況を説明しました:

最も人気のある質問は、「私は言語Xコミュニティの出身です。言語XからRubyに機能を紹介できませんか?」などです。そして、これらの要求に対する私の通常の答えは...「いいえ、私はそれをしません」です。なぜなら、私たちは異なる言語設計と異なる言語開発ポリシーを持っているからです。

thisこれを回避する方法はいくつかあります。一部のユーザーはgoto、を使用することを提案しています。これはほとんどの場合十分な近似ですが、非常に醜くなり、ネストされたループで完全に壊れます。gotoまた、s を使用すると、コードを他の人に見せるたびにSICPのコピーがスローされる危険があります。


1
最初の文は明らかに誤りであり、残りの回答は役に立たないため、私は反対票を投じました。
bfontaine

役に立たない?多分; それはやや意見ベースの答えです。最初の文は明らかに真実ですが、continue便利な機能かもしれませんが、それはそれを必要としません。多くの人がLuaを使わなくても問題なく使用できます。そのため、プログラミング言語に不可欠ではないきちんとした機能以外にLuaを使用することはできません。
DarkWiiPlayer

それは議論ではありません。人々が選択の余地がない場合、人々は「それなしで大丈夫」だと主張することはできません。
bfontaine

そのときは「必要」の定義が違うだけだと思います。
DarkWiiPlayer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.