Odpowiedzi:
W Lua 5.2 najlepszym obejściem jest użycie goto:
-- prints odd numbers in [|1,10|]
for i=1,10 do
if i % 2 == 0 then goto continue end
print(i)
::continue::
end
Jest to obsługiwane w LuaJIT od wersji 2.0.1
continue
jeden dzień. goto
Wymiana nie wygląda bardzo ładnie i potrzebuje więcej linii. Ponadto, czy nie spowodowałoby to problemów, gdybyś miał więcej niż jedną pętlę wykonującą to w jednej funkcji, obie z ::continue::
? Tworzenie nazwy dla każdej pętli nie brzmi jak przyzwoita rzecz.
Sposób, w jaki język zarządza zakresem leksykalnym, stwarza problemy z włączaniem zarówno goto
i continue
. Na przykład,
local a=0
repeat
if f() then
a=1 --change outer a
end
local a=f() -- inner a
until a==0 -- test inner a
Deklaracja local a
wewnątrz ciała pętli maskuje zewnętrzną zmienną o nazwie a
, a zakres tego lokalnego rozciąga się na warunek until
instrukcji, więc warunek testuje najbardziej wewnętrzną a
.
Gdyby continue
istniał, musiałby być ograniczony semantycznie, aby był ważny tylko wtedy, gdy wszystkie zmienne użyte w warunku wejdą w zakres. Jest to trudny warunek do udokumentowania użytkownikowi i wprowadzenia go w kompilatorze. Omówiono różne propozycje dotyczące tego problemu, w tym prostą odpowiedź polegającą na blokowaniu continue
za pomocą repeat ... until
stylu pętli. Jak dotąd żaden nie miał wystarczająco przekonującego przypadku użycia, aby włączyć je do języka.
Obejście polega na ogół na odwróceniu warunku, który spowodowałby continue
wykonanie a, i zebraniu reszty treści pętli pod tym warunkiem. Tak więc następująca pętla
-- 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
można napisać
-- 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
Jest to wystarczająco jasne i zwykle nie jest obciążeniem, chyba że masz serię wyszukanych eliminacji kontrolujących działanie pętli.
until...
.
goto
do Lua 5.2. Oczywiście goto
ma ten sam problem. Ostatecznie zdecydowali, że bez względu na koszty środowiska wykonawczego i / lub generowania kodu, które miałyby przed nimi chronić, warte są korzyści wynikające z posiadania elastycznego rozwiązania, goto
które może być używane do emulacji zarówno na continue
wielu poziomach, jak i na wielu poziomach break
. Aby uzyskać szczegółowe informacje , musisz przeszukać archiwa listy Lua pod kątem odpowiednich wątków. Odkąd wprowadzili goto
, oczywiście nie było to nie do pokonania.
local
to dyrektywa tylko dla kompilatora - nie ma znaczenia, jakie instrukcje w czasie wykonywania znajdują się pomiędzy local
i użycie zmiennych - nie musisz nic zmieniać w kompilatorze, aby zachować takie samo zachowanie zakresu. Tak, może to nie być takie oczywiste i wymagać dodatkowej dokumentacji, ale, aby powtórzyć, wymaga ZERO zmian w kompilatorze. repeat do break end until true
przykład w mojej odpowiedzi już generuje dokładnie ten sam kod bajtowy, który kompilator użyłby dalej, jedyną różnicą jest to continue
, że nie potrzebowałbyś brzydkiej dodatkowej składni, aby go użyć.
do{int i=0;}while (i == 0);
fail lub w C ++: do int i=0;while (i==0);
również nie powiedzie się („nie został zadeklarowany w tym zakresie”). Niestety za późno, żeby to zmienić teraz w Lua.
Możesz dodatkowo owinąć pętlę, repeat until true
a następnie użyć do break end
wewnątrz, aby uzyskać efekt kontynuacji. Oczywiście będziesz musiał ustawić dodatkowe flagi, jeśli chcesz również naprawdę break
wyjść z pętli.
Spowoduje to zapętlenie 5 razy, za każdym razem drukując 1, 2 i 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
Ta konstrukcja przekłada się nawet na dosłowny jeden kod operacji JMP
w kodzie bajtowym Lua!
$ 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
luac
wyjście na SO! Życzę wszystkim zasłużonych głosów :)
Prosto od samego projektanta Lua :
Naszym głównym zmartwieniem w przypadku „kontynuuj” jest to, że istnieje kilka innych struktur kontrolnych, które (naszym zdaniem) są mniej więcej tak ważne jak „kontynuuj”, a nawet mogą je zastąpić. (Np. Zerwij z etykietami [jak w Javie] lub nawet bardziej ogólnym poleceniem goto.) „Kontynuuj” nie wydaje się bardziej szczególne niż inne mechanizmy struktury kontrolnej, z wyjątkiem tego, że występuje w większej liczbie języków. (Perl ma właściwie dwie instrukcje „kontynuuj”, „next” i „redo”. Obie są przydatne.)
continue
do Lua, przepraszam”.
Pierwsza część odpowiedzi w FAQ jak zabitego zauważył.
Jeśli chodzi o obejście problemu, możesz zawinąć ciało pętli w funkcję i return
wcześniej, np
-- 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
Lub jeśli chcesz mieć obie break
i continue
funkcjonalność, poproś funkcję lokalną o wykonanie testu, np
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
collectgarbage("count")
nawet po twoich prostych 100 próbach, a wtedy porozmawiamy. Taka „przedwczesna” optymalizacja uratowała w zeszłym tygodniu jeden projekt o dużym obciążeniu przed ponownym uruchamianiem co minutę.
Nigdy wcześniej nie używałem Lua, ale wyszukałem go w Google i wymyśliłem to:
Sprawdź pytanie 1.26 .
To częsta skarga. Autorzy Lua uznali, że kontynuacja była tylko jednym z wielu możliwych nowych mechanizmów kontroli przepływu (fakt, że nie może działać z regułami zakresu powtarzania / dopóki nie był czynnikiem drugorzędnym).
W Lua 5.2 znajduje się instrukcja goto, której można łatwo użyć do wykonania tej samej pracy.
Napotkaliśmy ten scenariusz wiele razy i po prostu używamy flagi do symulacji kontynuacji. Staramy się również unikać stosowania instrukcji goto.
Przykład: Kod zamierza wydrukować instrukcje od i = 1 do i = 10, z wyjątkiem i = 3. Ponadto wypisuje również „początek pętli”, „koniec pętli”, „jeśli początek” i „jeśli koniec”, aby zasymulować inne zagnieżdżone instrukcje istniejące w kodzie.
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
osiąga się poprzez zamknięcie wszystkich pozostałych instrukcji aż do końca zakresu pętli flagą test.
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
Nie mówię, że jest to najlepsze podejście, ale dla nas działa doskonale.
Lua to lekki język skryptowy, który powinien być jak najmniejszy. Na przykład wiele operacji jednoargumentowych, takich jak inkrementacja przed / po, nie jest dostępnych
Zamiast kontynuować, możesz użyć 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
Ponownie z odwracaniem, możesz po prostu użyć następującego kodu:
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end
Ponieważ jest to niepotrzebne¹. Jest bardzo niewiele sytuacji, w których deweloper by tego potrzebował.
A) Kiedy masz bardzo prostą pętlę, powiedzmy 1- lub 2-liniową, możesz po prostu odwrócić stan pętli i nadal jest bardzo czytelny.
B) Kiedy piszesz prosty kod proceduralny (czyli sposób, w jaki pisaliśmy kod w ubiegłym wieku), powinieneś również stosować programowanie strukturalne (czyli jak napisaliśmy lepszy kod w ubiegłym wieku)
C) Jeśli piszesz kod zorientowany obiektowo, treść pętli powinna składać się z nie więcej niż jednego lub dwóch wywołań metod, chyba że można to wyrazić w jedno- lub dwuwierszowym (w takim przypadku zobacz A)
D) Jeśli piszesz kod funkcjonalny, po prostu zwróć zwykłe wywołanie końcowe dla następnej iteracji.
Jedynym przypadkiem, w którym chciałbyś użyć continue
słowa kluczowego, jest zakodowanie Lua tak, jak w Pythonie, którym po prostu nie jest .²
O ile nie ma zastosowania A), w którym to przypadku nie ma potrzeby stosowania żadnych obejść, należy wykonywać programowanie strukturalne, obiektowe lub funkcjonalne. To są paradygmaty, do których stworzono Lua, więc walczyłbyś z językiem, gdybyś zrobił wszystko, co w twojej mocy, aby uniknąć ich wzorców³.
Kilka wyjaśnień:
¹ Lua to bardzo minimalistyczny język. Próbuje mieć tak mało funkcji, jak to tylko możliwe, a continue
stwierdzenie nie jest w tym sensie istotną funkcją.
Myślę, że ta filozofia minimalizmu została dobrze ujęta przez Roberto Ierusalimschy w wywiadzie z 2019 roku :
dodaj to i to, i tamto, odłóż to, a na końcu rozumiemy, że ostateczny wniosek nie zadowoli większości ludzi i nie postawimy wszystkich opcji, których wszyscy chcą, więc nic nie wkładamy. W końcu tryb ścisły jest rozsądnym kompromisem.
² Wydaje się, że duża liczba programistów przyjeżdża do Lua z innych języków, ponieważ każdy program, dla którego próbują napisać, używa go, a wielu z nich nie chce pisać niczego innego niż język wybór, co prowadzi do wielu pytań, takich jak „Dlaczego Lua nie ma funkcji X?”
Matz opisał podobną sytuację z Ruby w niedawnym wywiadzie :
Najpopularniejszym pytaniem jest: „Jestem ze społeczności języka X; czy nie możesz wprowadzić funkcji z języka X do Rubiego?” Lub coś w tym rodzaju. A moja zwykła odpowiedź na te prośby brzmi… „nie, nie zrobiłbym tego”, ponieważ mamy inny projekt języka i różne zasady rozwoju języka.
³ Jest kilka sposobów obejścia tego problemu; niektórzy użytkownicy sugerowali użycie goto
, co w większości przypadków jest wystarczająco dobrym przybliżeniem, ale bardzo szybko staje się bardzo brzydkie i całkowicie zrywa z zagnieżdżonymi pętlami. Używanie goto
s naraża Cię również na ryzyko rzucenia kopii SICP za każdym razem, gdy pokażesz komuś swój kod.
continue
może być wygodną funkcją, ale to nie oznacza, że jest to konieczne . Wiele osób używa Lua bez niego, więc naprawdę nie ma powodu, aby był to coś innego niż zgrabna funkcja, która nie jest niezbędna w żadnym języku programowania.
goto
oświadczenie, które można wykorzystać do wdrożenia kontynuacji. Zobacz odpowiedzi poniżej.