Perché Lua non ha dichiarazioni "continua"?


144

Ho avuto a che fare molto con Lua negli ultimi mesi e mi piacciono molto la maggior parte delle funzionalità, ma mi manca ancora qualcosa tra quelle:

  • Perché non c'è continue?
  • Quali soluzioni alternative ci sono?

12
Da quando questa domanda è stata posta, Lua ha ottenuto una gotodichiarazione che può essere utilizzata per implementare continua. Vedi le risposte sotto.
IF

Risposte:


71

In Lua 5.2 la soluzione migliore è usare goto:

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

Questo è supportato in LuaJIT dalla versione 2.0.1


47
Spero che includano un continuegiorno reale . La gotosostituzione non sembra molto bella e necessita di più linee. Inoltre, ciò non creerebbe problemi se avessi più di un ciclo che lo fa in una funzione, entrambi con ::continue::? Creare un nome per loop non sembra una cosa decente da fare.
ET,

66

Il modo in cui la lingua gestisce l'ambito lessicale crea problemi con l'inclusione di entrambi gotoe continue. Per esempio,

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

La dichiarazione di local aall'interno del corpo del loop maschera la variabile esterna denominata ae l'ambito di quel locale si estende attraverso la condizione untildell'istruzione in modo che la condizione stia testando la più interna a.

Se continueesistesse, dovrebbe essere limitato semanticamente per essere valido solo dopo che tutte le variabili utilizzate nella condizione sono entrate nell'ambito. Questa è una condizione difficile da documentare per l'utente e far valere nel compilatore. Sono state discusse varie proposte su questo tema, inclusa la semplice risposta di non consentire continuecon lo repeat ... untilstile del loop. Finora, nessuno ha avuto un caso d'uso sufficientemente convincente per includerli nella lingua.

Il lavoro intorno è generalmente di invertire la condizione che causerebbe l' continueesecuzione di un, e raccogliere il resto del corpo del loop in quella condizione. Quindi, il seguente ciclo

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

potrebbe essere scritto

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

È abbastanza chiaro, e di solito non è un onere a meno che tu non abbia una serie di abbattimenti elaborati che controllano il funzionamento del loop.


5
Proveniente da un background di Python, questa è una risposta confusa perché ogni ambito già conosce quali sono le sue variabili locali prima di essere eseguito. Vale a dire, mi aspettavo un errore variabile locale non associato in caso di raggiungimento until....
ubershmekel,

2
Si è discusso molto di questo nella comunità di Lua prima dell'introduzione di gotoLua 5.2. Naturalmente, gotoha lo stesso problema. Alla fine decisero che qualunque fosse il costo di runtime e / o di generazione del codice da proteggere da esso valeva i vantaggi di avere un flessibile gotoche può essere usato per emulare sia a continuepiù livelli break. Dovresti cercare negli archivi dell'elenco Lua i thread pertinenti per ottenere i dettagli. Dal momento che hanno introdotto goto, ovviamente non era insormontabile.
RBerteig,

72
Non c'è niente di "abbastanza chiaro" nella scrittura del codice senza continuare. È un errore da principiante annidare il codice all'interno di un condizionale in cui avrebbe dovuto essere usato un continue, e la necessità di scrivere un brutto codice come quello non dovrebbe ricevere alcuna simpatia. Non ci sono assolutamente scuse.
Glenn Maynard,

4
Questa spiegazione non ha senso. localè una direttiva solo per il compilatore - non importa quali sono le istruzioni di runtime tra locale l'uso variabile - non è necessario modificare nulla nel compilatore per mantenere lo stesso comportamento di scoping. Sì, questo potrebbe non essere così ovvio e richiedere una documentazione aggiuntiva, ma, per ribadirlo, richiede modifiche ZERO nel compilatore. repeat do break end until trueesempio nella mia risposta genera già esattamente lo stesso bytecode con cui il compilatore continuerebbe, l'unica differenza è che con continuete non avresti bisogno di una brutta sintassi extra per usarlo.
Oleg V. Volkov,

7
Il fatto che sia possibile testare la variabile interna parla di un design imperfetto. La condizione è al di fuori dell'ambito interno e non dovrebbe avere accesso alle variabili al suo interno. Si consideri l'equivalente in C: do{int i=0;}while (i == 0);fail o anche in C ++: do int i=0;while (i==0);fail ("non è stato dichiarato in questo ambito"). Purtroppo è troppo tardi per cambiarlo adesso a Lua.
Pedro Gimeno,

47

Puoi avvolgere il corpo del loop in più repeat until truee quindi utilizzarlo do break endall'interno per effetto di continue. Ovviamente, dovrai impostare ulteriori flag se intendi anche breakuscire davvero dal circuito.

Questo ripeterà 5 volte, stampando 1, 2 e 3 ogni volta.

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

Questa costruzione si traduce persino in un codice operativo letterale JMPin bytecode 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

4
Questa risposta è buona, ma richiede ancora 3 righe invece di una sola. (se "continua" era adeguatamente supportato) È un po 'più bello e più sicuro di un'etichetta goto, poiché per quel nome potrebbe essere necessario evitare gli scontri per i cicli nidificati.
ET,

3
tuttavia, evita il problema "reale" di goto in quanto non è necessario inventare un nuovo identificatore / etichetta per ogni psuedo-continue e che è meno soggetto ad errori poiché il codice viene modificato nel tempo. concordo sul fatto che continui sarebbe utile , ma questa IMO è la cosa migliore successiva (e richiede davvero due righe per la ripetizione / fino a quando un "continua;" più formale ... e anche allora, se fossi così interessato alla riga conta che potresti sempre scrivere "ripeti" e "fino alla fine vera", ad esempio: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson,

1
È bello vedere che le persone considerano effettivamente le prestazioni e forniscono anche luacoutput su SO! Avere un meritato voto :)
DarkWiiPlayer

17

Direttamente dal designer di Lua stesso :

La nostra principale preoccupazione per "continuare" è che ci sono molte altre strutture di controllo che (a nostro avviso) sono più o meno importanti quanto "continuare" e possono persino sostituirlo. (Ad esempio, rompere con etichette [come in Java] o anche un goto più generico.) "Continue" non sembra più speciale di altri meccanismi di struttura di controllo, tranne che è presente in più lingue. (In realtà Perl ha due istruzioni "continue", "next" e "redo". Entrambe sono utili.)


5
Adoro l'ammissione: "Entrambi sono utili" subito dopo una spiegazione di "non lo faremo"
David Ljung Madison Stellar,

2
Era per notare l'ambito che stavano cercando di affrontare quando lo hanno fatto , aggiungendo un costrutto "goto" in 5.2 (che non era stato rilasciato al momento della stesura di questa risposta). Vedi questa risposta dal 2012 , dopo il rilascio della 5.2.0.
Stuart P. Bentley,

3
Giusto, perché "goto" è ben noto per essere un costrutto di programmazione decente. (fine sarcasmo) Ah bene.
David Ljung Madison Stellar il

2
Ma non sembrava più ragionevole di "Ho appena dimenticato di mettere continuein Lua, scusa."
neoedmund,

17

La prima parte è risolta in senso FAQ come ucciso fuori appuntito.

Per quanto riguarda una soluzione alternativa, puoi avvolgere il corpo del loop in una funzione e returnpresto da quello, ad es

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

Oppure, se si desidera sia la funzionalità breakche la continuefunzionalità, fare eseguire il test dalla funzione locale, ad es

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
Per favore, no. Si crea un ambiente di chiusura su ogni iterazione e questo è un enorme spreco di memoria e cicli GC.
Oleg V. Volkov,

4
vai a controllare collectgarbage("count")anche dopo i tuoi semplici 100 tentativi e poi parleremo. Tale ottimizzazione "prematura" ha salvato un progetto ad alto carico dal riavvio ogni minuto la scorsa settimana.
Oleg V. Volkov,

4
@ OlegV.Volkov mentre questo esempio mette un carico relativamente elevato sul GC, non perde - Tutte le chiusure temporanee verranno raccolte. Non so del tuo progetto, ma la maggior parte dei riavvii ripetuti dell'IME sono dovuti a perdite.
finnw,

10

Non ho mai usato Lua prima, ma l'ho cercato su Google e ho pensato a questo:

http://www.luafaq.org/

Controlla la domanda 1.26 .

Questa è una lamentela comune. Gli autori di Lua hanno ritenuto che continuare fosse solo uno dei numerosi possibili nuovi meccanismi di flusso di controllo (il fatto che non potesse funzionare con le regole dell'ambito di ripetizione / fino a quando non fosse un fattore secondario).

In Lua 5.2, c'è un'istruzione goto che può essere facilmente usata per fare lo stesso lavoro.


8

Possiamo ottenerlo come di seguito, salterà i numeri pari

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

OPERAZIONE:

i = 1
i = 3
i = 5

6

Abbiamo incontrato questo scenario molte volte e abbiamo semplicemente usato una bandiera per simulare continua. Cerchiamo di evitare anche l'uso di dichiarazioni goto.

Esempio: il codice intende stampare le istruzioni da i = 1 a i = 10 tranne i = 3. Inoltre stampa anche "loop start", loop end "," if start "e" if end "per simulare altre istruzioni nidificate presenti nel codice.

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

si ottiene racchiudendo tutte le restanti istruzioni fino alla fine del ciclo con un flag di prova.

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

Non sto dicendo che questo è l'approccio migliore ma funziona perfettamente per noi.


5

Lua è un linguaggio di scripting leggero che vuole essere il più piccolo possibile. Ad esempio, molte operazioni unarie come l'incremento pre / post non sono disponibili

Invece di continuare, puoi usare 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

4

Ancora una volta con l'inversione, potresti semplicemente usare il seguente codice:

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

Il problema con l'inversione è che il più delle volte ci sono più condizionali in una serie (come per convalidare l'input dell'utente). E poiché potrebbe essere necessario un cortocircuito in qualsiasi punto lungo la strada, l'inversione significa dover annidare continuamente i condizionali (invece di "è questo male? Quindi scappa; altrimenti è così male? Quindi scappa", che è molto semplice, finisci con un codice del tipo "va bene? allora va bene? quindi va bene? poi fai questo" che è molto eccessivo.
Leslie Krause,

-2

Perché non è possibile continuare?

Perché non è necessario¹. Ci sono pochissime situazioni in cui uno sviluppatore ne avrebbe bisogno.

A) Quando hai un loop molto semplice, dì un 1 o 2-liner, allora puoi semplicemente cambiare la condizione del loop ed è ancora molto leggibile.

B) Quando scrivi un semplice codice procedurale (ovvero come abbiamo scritto codice nell'ultimo secolo), dovresti anche applicare la programmazione strutturata (ovvero come abbiamo scritto un codice migliore nell'ultimo secolo)

C) Se stai scrivendo un codice orientato agli oggetti, il tuo corpo del loop dovrebbe consistere in non più di una o due chiamate di metodo a meno che non possa essere espresso in una o due righe (nel qual caso, vedi A)

D) Se stai scrivendo un codice funzionale, restituisci semplicemente una semplice chiamata di coda per la successiva iterazione.

L'unico caso in cui vorresti usare una continueparola chiave è se vuoi codificare Lua come se fosse Python, cosa che non è.

Quali soluzioni alternative ci sono?

A meno che non si applichi A), nel qual caso non è necessaria alcuna soluzione alternativa, è necessario eseguire una programmazione strutturata, orientata agli oggetti o funzionale. Questi sono i paradigmi per cui è stata costruita Lua, quindi combatteresti contro il linguaggio se fai del tuo meglio per evitare i loro schemi.


Alcuni chiarimenti:

¹ Lua è un linguaggio molto minimalista. Cerca di avere il minor numero possibile di funzioni e continueun'affermazione non è una caratteristica essenziale in tal senso.

Penso che questa filosofia del minimalismo sia catturata bene da Roberto Ierusalimschy in questa intervista del 2019 :

aggiungi questo e quello e quello, mettilo fuori, e alla fine capiamo che la conclusione finale non soddisferà la maggior parte delle persone e non metteremo tutte le opzioni che tutti vogliono, quindi non mettiamo nulla. Alla fine, la modalità rigorosa è un ragionevole compromesso.

² Sembra che ci sia una grande quantità di programmatori che arrivano a Lua da altre lingue perché qualsiasi programma stanno cercando di scrivere per usarlo, e molti di loro vogliono non sembrano voler scrivere altro che il loro linguaggio di scelta, che porta a molte domande come "Perché Lua non ha la funzione X?"

Matz ha descritto una situazione simile con Ruby in una recente intervista :

La domanda più popolare è: "Vengo dalla comunità della lingua X; non puoi introdurre una funzionalità dalla lingua X a Ruby?" O qualcosa del genere. E la mia solita risposta a queste richieste è ... "no, non lo farei", perché abbiamo un design del linguaggio diverso e politiche di sviluppo del linguaggio diverse.

³ Ci sono alcuni modi per aggirare il problema; alcuni utenti hanno suggerito di utilizzare goto, il che è una buona approssimazione nella maggior parte dei casi, ma diventa molto brutto molto rapidamente e si rompe completamente con loop nidificati. L'uso di gotos ti mette anche in pericolo di avere una copia di SICP lanciata contro di te ogni volta che mostri il tuo codice a chiunque altro.


1
Ho annullato il voto perché la prima frase è ovviamente falsa e il resto della risposta è inutile.
bfontaine,

Inutile? Può essere; è una risposta in qualche modo basata sull'opinione. La prima frase è ovviamente vera però; continuepotrebbe essere una funzione utile, ma ciò non lo rende necessario . Molte persone usano Lua bene senza di essa, quindi non c'è davvero nessun motivo per essere nient'altro che una caratteristica ordinata che non è essenziale per nessun linguaggio di programmazione.
DarkWiiPlayer

Non è un argomento: non si può sostenere che le persone stiano "bene senza" quando non hanno alcuna scelta.
bfontaine,

Penso che allora abbiamo solo diverse definizioni di "necessario".
DarkWiiPlayer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.