Warum hat Lua keine "Weiter" -Anweisung?

142

Ich habe in den letzten Monaten viel mit Lua zu tun gehabt und ich mag die meisten Funktionen wirklich, aber mir fehlt immer noch etwas unter diesen:

  • Warum gibt es keine continue?
  • Welche Problemumgehungen gibt es dafür?
Dant
quelle
12
Da diese Frage gestellt wurde, erhielt Lua eine gotoErklärung, die zur Implementierung von continue verwendet werden kann. Siehe die Antworten unten.
11.

Antworten:

69

In Lua 5.2 ist die beste Problemumgehung, goto zu verwenden:

-- prints odd numbers in [|1,10|]
for i=1,10 do
  if i % 2 == 0 then goto continue end
  print(i)
  ::continue::
end

Dies wird in LuaJIT seit Version 2.0.1 unterstützt

Catwell
quelle
46
Ich hoffe, dass sie einen tatsächlichen continueeinen Tag enthalten. Der gotoErsatz sieht nicht sehr gut aus und benötigt mehr Zeilen. Würde das nicht auch Probleme verursachen, wenn Sie mehr als eine Schleife in einer Funktion hätten, beide mit ::continue::? Einen Namen pro Schleife zu erfinden, klingt nicht nach einer anständigen Sache.
ET
65

Die Art und Weise, wie die Sprache den lexikalischen Bereich verwaltet, führt zu Problemen beim Einbeziehen von gotound continue. Beispielsweise,

local a=0
repeat 
    if f() then
        a=1 --change outer a
    end
    local a=f() -- inner a
until a==0 -- test inner a

Die Deklaration local ainnerhalb des Schleifenkörpers maskiert die genannte äußere Variable a, und der Gültigkeitsbereich dieses lokalen Bereichs erstreckt sich über die Bedingung der untilAnweisung, sodass die Bedingung die innerste testet a.

Wenn continuevorhanden, müsste es semantisch eingeschränkt werden, um erst gültig zu sein, nachdem alle in der Bedingung verwendeten Variablen in den Geltungsbereich gekommen sind. Dies ist eine schwierige Bedingung, die dem Benutzer dokumentiert und im Compiler durchgesetzt werden kann. Verschiedene Vorschläge zu diesem Thema wurden diskutiert, einschließlich der einfachen Antwort, continueden repeat ... untilStil der Schleife nicht zuzulassen. Bisher hatte keiner einen ausreichend überzeugenden Anwendungsfall, um sie in die Sprache aufzunehmen.

Die Umgehung besteht im Allgemeinen darin, die Bedingung umzukehren, die dazu führen würde, dass a continueausgeführt wird, und den Rest des Schleifenkörpers unter dieser Bedingung zu sammeln. Also die folgende Schleife

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

könnte geschrieben werden

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

Es ist klar genug und normalerweise keine Belastung, es sei denn, Sie haben eine Reihe von aufwändigen Keulungen, die den Schleifenbetrieb steuern.

RBerteig
quelle
5
Aus einem Python-Hintergrund kommend ist dies eine verwirrende Antwort, da jeder Bereich dort bereits vor der Ausführung die lokalen Variablen kennt. Dh ich habe beim Erreichen einen ungebundenen lokalen Variablenfehler erwartet until....
Ubershmekel
2
Vor der Einführung von gotoLua 5.2 wurde in der Lua-Community viel darüber diskutiert . Hat natürlich gotodas gleiche Problem. Sie entschieden schließlich, dass unabhängig von den Laufzeit- und / oder Codegenerierungskosten zum Schutz die Vorteile einer Flexibilität goto, die sowohl zur Emulation continueals auch auf mehreren Ebenen verwendet werden kann, wert sind break. Sie müssten die Lua-Listenarchive nach den relevanten Threads durchsuchen , um die Details zu erhalten. Da sie eingeführt haben goto, war es offensichtlich nicht unüberwindbar.
RBerteig
71
Es ist nichts "klar genug", Code zu schreiben, ohne fortzufahren. Es ist ein Anfängerfehler, Code in einer Bedingung zu verschachteln, in der ein Fortsetzen hätte verwendet werden sollen, und die Notwendigkeit, solchen hässlichen Code zu schreiben, sollte keine Sympathie erhalten. Es gibt absolut keine Entschuldigung.
Glenn Maynard
4
Diese Erklärung macht keinen Sinn. localist eine reine Compiler-Direktive - es spielt keine Rolle, zwischen welchen Laufzeitinstruktionen localund variabler Verwendung sie liegen - Sie müssen im Compiler nichts ändern, um das gleiche Gültigkeitsbereichsverhalten beizubehalten. Ja, dies ist möglicherweise nicht so offensichtlich und erfordert zusätzliche Dokumentation. Um es jedoch noch einmal zu wiederholen, sind NULL-Änderungen im Compiler erforderlich. repeat do break end until trueBeispiel in meiner Antwort generiert bereits genau den gleichen Bytecode, mit dem der Compiler fortfahren würde. Der einzige Unterschied besteht darin, dass continueSie für die Verwendung keine hässliche zusätzliche Syntax benötigen würden.
Oleg V. Volkov
7
Dass Sie die innere Variable testen können, spricht für fehlerhaftes Design. Die Bedingung liegt außerhalb des inneren Bereichs und sollte keinen Zugriff auf die darin enthaltenen Variablen haben. Betrachten Sie das Äquivalent in C: do{int i=0;}while (i == 0);fehlgeschlagen oder in C ++: do int i=0;while (i==0);schlägt ebenfalls fehl ("wurde in diesem Bereich nicht deklariert"). Leider zu spät, um das jetzt in Lua zu ändern.
Pedro Gimeno
45

Sie können den Schleifenkörper zusätzlich einwickeln repeat until trueund dann do break endinnen verwenden, um fortzufahren. Natürlich müssen Sie zusätzliche Flags einrichten, wenn Sie auch wirklich breakaußerhalb der Schleife sein möchten.

Dies wird 5 Mal wiederholt und jedes Mal 1, 2 und 3 gedruckt.

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

Diese Konstruktion übersetzt sogar einen wörtlichen Opcode JMPin Lua-Bytecode!

$ 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
Oleg V. Volkov
quelle
4
Diese Antwort ist nett, erfordert aber immer noch 3 Zeilen anstelle von nur einer. (Wenn "continue" ordnungsgemäß unterstützt wurde) Es ist jedoch etwas hübscher und sicherer als ein goto-Label, da für diesen Namen möglicherweise Konflikte bei verschachtelten Schleifen vermieden werden müssen.
ET
3
Es vermeidet jedoch das "echte" Problem mit goto, da Sie nicht für jedes Pseudo-Continue einen neuen Bezeichner / eine neue Bezeichnung erfinden müssen und dass es weniger fehleranfällig ist, da der Code im Laufe der Zeit geändert wird. Ich bin damit einverstanden, dass Fortfahren nützlich wäre , aber diese IMO ist das nächstbeste (und es erfordert wirklich zwei Zeilen für die Wiederholung / bis im Vergleich zu einem formelleren "Weiter"; .. und selbst dann, wenn Sie sich mit Zeile befassen Sie können immer "do repeat" und " till true end" schreiben, zum Beispiel: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson
1
Schön zu sehen, dass die Leute tatsächlich über Leistung nachdenken und sogar luacauf SO! Habe eine wohlverdiente Gegenstimme :)
DarkWiiPlayer
16

Direkt vom Designer von Lua selbst :

Unser Hauptanliegen bei "Weiter" ist, dass es mehrere andere Kontrollstrukturen gibt, die (unserer Ansicht nach) mehr oder weniger so wichtig sind wie "Weiter" und sie möglicherweise sogar ersetzen. (ZB brechen Sie mit Labels [wie in Java] oder sogar mit einem allgemeineren Goto.) "Continue" scheint nicht spezieller zu sein als andere Kontrollstrukturmechanismen, außer dass es in mehr Sprachen vorhanden ist. (Perl hat tatsächlich zwei "continue" -Anweisungen, "next" und "redo". Beide sind nützlich.)

Stuart P. Bentley
quelle
5
Ich liebe das Eingeständnis: "Beide sind nützlich" direkt nach einer Erklärung von "Wir werden es nicht tun"
David Ljung Madison Stellar
2
Es war , den Umfang zu beachten , dass sie wurden zu Adresse suchen , wenn sie tut es tun, durch ein „goto“ Konstrukt in 5.2 Hinzufügen (nicht frei worden war , als diese Antwort geschrieben wurde). Siehe diese Antwort von 2012 , nachdem 5.2.0 veröffentlicht wurde.
Stuart P. Bentley
3
Richtig - weil 'goto' als anständiges Programmierkonstrukt anerkannt ist. (Ende Sarkasmus) Ah gut.
David Ljung Madison Stellar
2
Aber es klang nicht vernünftiger als "Ich habe nur vergessen, continuein Lua zu stecken, sorry."
Neoedmund
16

Der erste Teil ist in der antwortete FAQ als getötet aufgezeigt.

Als Problemumgehung können Sie den Körper der Schleife in eine Funktion einbinden und returnfrühzeitig daraus, z

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

Wenn Sie beides breakund continueFunktionalität wünschen , lassen Sie die lokale Funktion den Test durchführen, z

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
finnw
quelle
15
Bitte nicht. Sie erstellen bei jeder Iteration eine Abschlussumgebung, und dies ist eine RIESIGE Verschwendung von Speicher und GC-Zyklen.
Oleg V. Volkov
4
Überprüfen Sie collectgarbage("count")auch nach Ihren einfachen 100 Versuchen, und dann werden wir reden. Eine solche "vorzeitige" Optimierung hat letzte Woche ein Hochlastprojekt vor dem Neustart jede Minute bewahrt.
Oleg V. Volkov
4
@ OlegV.Volkov Während dieses Beispiel den GC relativ stark belastet, leckt es nicht - Alle temporären Verschlüsse werden gesammelt. Ich weiß nichts über Ihr Projekt, aber die meisten IME-Neustarts sind auf Lecks zurückzuführen.
Finnw
9

Ich habe Lua noch nie benutzt, aber ich habe es gegoogelt und mir Folgendes ausgedacht:

http://www.luafaq.org/

Überprüfen Sie Frage 1.26 .

Dies ist eine häufige Beschwerde. Die Lua-Autoren waren der Ansicht, dass das Fortfahren nur einer von mehreren möglichen neuen Kontrollflussmechanismen ist (die Tatsache, dass es nicht mit den Bereichsregeln von Wiederholung / Bis arbeiten kann, war ein sekundärer Faktor).

In Lua 5.2 gibt es eine goto-Anweisung, mit der derselbe Job problemlos ausgeführt werden kann.

getötet
quelle
7

Wir können es wie folgt erreichen, es werden gerade Zahlen übersprungen

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
Dilip
quelle
5

Wir sind diesem Szenario oft begegnet und verwenden einfach ein Flag, um die Fortsetzung zu simulieren. Wir versuchen, die Verwendung von goto-Anweisungen ebenfalls zu vermeiden.

Beispiel: Der Code beabsichtigt, die Anweisungen von i = 1 bis i = 10 mit Ausnahme von i = 3 zu drucken. Außerdem werden "Schleifenstart", "Schleifenende", "Wenn Start" und "Wenn Ende" gedruckt, um andere verschachtelte Anweisungen zu simulieren, die in Ihrem Code vorhanden sind.

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

wird erreicht, indem alle verbleibenden Anweisungen bis zum Ende der Schleife mit einem Testflag versehen werden.

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

Ich sage nicht, dass dies der beste Ansatz ist, aber er funktioniert perfekt für uns.

winux
quelle
3

Auch beim Invertieren können Sie einfach den folgenden Code verwenden:

for k,v in pairs(t) do
  if not isstring(k) then 
    -- do something to t[k] when k is not a string
end
8lakester
quelle
Das Problem bei der Inversion besteht darin, dass eine Reihe häufig mehrere Bedingungen enthält (z. B. um Benutzereingaben zu validieren). Und weil es an jedem Punkt des Weges zu einem Kurzschluss kommen muss, bedeutet Inversion, dass die Bedingungen kontinuierlich verschachtelt werden müssen (anstelle von "Ist das schlecht? Dann entkommen; sonst ist das schlecht? Dann entkommen", was sehr einfach ist. Sie erhalten am Ende Code wie "Ist das in Ordnung? Dann ist das in Ordnung? Dann ist das in Ordnung? Dann tun Sie das", der sehr übertrieben ist.
Leslie Krause
3

Lua ist eine leichte Skriptsprache, die so klein wie möglich sein soll. Beispielsweise sind viele unäre Operationen wie das Inkrementieren vor / nach dem Inkrementieren nicht verfügbar

Anstatt fortzufahren, können Sie goto like verwenden

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
Ankit ihelper Sharma
quelle
-3

Warum gibt es keine Fortsetzung?

Weil es unnötig ist¹. Es gibt sehr wenige Situationen, in denen ein Entwickler es brauchen würde.

A) Wenn Sie eine sehr einfache Schleife haben, z. B. einen 1- oder 2-Liner, können Sie die Schleifenbedingung einfach umdrehen und sie ist immer noch gut lesbar.

B) Wenn Sie einfachen prozeduralen Code schreiben (auch bekannt als wie wir Code im letzten Jahrhundert geschrieben haben), sollten Sie auch strukturierte Programmierung anwenden (auch bekannt als wie wir besseren Code im letzten Jahrhundert geschrieben haben).

C) Wenn Sie objektorientierten Code schreiben, sollte Ihr Schleifenkörper aus nicht mehr als einem oder zwei Methodenaufrufen bestehen, es sei denn, er kann in einem Ein- oder Zweizeiler ausgedrückt werden (in diesem Fall siehe A).

D) Wenn Sie Funktionscode schreiben, geben Sie einfach einen einfachen Tail-Call für die nächste Iteration zurück.

Der einzige Fall, in dem Sie ein continueSchlüsselwort verwenden möchten, ist, wenn Sie Lua wie Python codieren möchten, was es einfach nicht ist.²

Welche Problemumgehungen gibt es dafür?

Sofern A) nicht zutrifft, sollten Sie in diesem Fall keine Problemumgehungen vornehmen. Sie sollten eine strukturierte, objektorientierte oder funktionale Programmierung durchführen. Das sind die Paradigmen, für die Lua gebaut wurde. Sie würden also gegen die Sprache kämpfen, wenn Sie alles daran setzen, ihre Muster zu vermeiden.³


Einige Klarstellungen:

¹ Lua ist eine sehr minimalistische Sprache. Es wird versucht, so wenige Funktionen wie möglich zu haben, und eine continueAussage ist in diesem Sinne kein wesentliches Merkmal.

Ich denke, diese Philosophie des Minimalismus wird von Roberto Ierusalimschy in diesem Interview von 2019 gut festgehalten :

füge das und das und das hinzu, lösche das und am Ende verstehen wir, dass die endgültige Schlussfolgerung die meisten Menschen nicht zufriedenstellen wird und wir werden nicht alle Optionen setzen, die jeder will, also setzen wir nichts. Am Ende ist der strikte Modus ein vernünftiger Kompromiss.

² Es scheint eine große Anzahl von Programmierern zu geben, die aus anderen Sprachen nach Lua kommen, weil jedes Programm, für das sie ein Skript erstellen, es zufällig verwendet, und viele von ihnen scheinen nichts anderes als ihre Sprache schreiben zu wollen Auswahl, die zu vielen Fragen wie "Warum hat Lua keine X-Funktion?" führt.

Matz hat kürzlich in einem Interview eine ähnliche Situation mit Ruby beschrieben :

Die beliebteste Frage lautet: "Ich komme aus der Language X-Community. Können Sie Ruby keine Funktion aus der Sprache X vorstellen?" Oder so ähnlich. Und meine übliche Antwort auf diese Anfragen lautet… "Nein, das würde ich nicht tun", weil wir unterschiedliche Sprachdesign- und Sprachentwicklungsrichtlinien haben.

³ Es gibt einige Möglichkeiten, sich darum zu kümmern. Einige Benutzer haben die Verwendung vorgeschlagen goto, was in den meisten Fällen eine gute Annäherung ist, aber sehr schnell sehr hässlich wird und vollständig mit verschachtelten Schleifen bricht. Wenn Sie gotos verwenden, besteht auch die Gefahr, dass Sie eine Kopie von SICP erhalten, wenn Sie Ihren Code jemand anderem zeigen.

DarkWiiPlayer
quelle
1
Ich habe abgelehnt, weil der allererste Satz offensichtlich falsch ist und der Rest der Antwort nicht hilfreich ist.
Bfontaine
Nicht hilfreich? Vielleicht; Es ist eine etwas meinungsbasierte Antwort. Der erste Satz ist jedoch offensichtlich wahr; continueMöglicherweise ist dies eine praktische Funktion, die jedoch nicht erforderlich ist . Viele Leute benutzen Lua ganz gut ohne es, also gibt es wirklich keinen Grund dafür, dass es etwas anderes als eine nette Funktion ist, die für keine Programmiersprache wesentlich ist.
DarkWiiPlayer
Das ist kein Argument: Man kann nicht behaupten, dass Menschen "ohne sie in Ordnung" sind, wenn sie keine Wahl haben.
Bfontaine
Ich denke, wir haben dann nur unterschiedliche Definitionen von "notwendig".
DarkWiiPlayer