¿Por qué Lua no tiene una declaración de "continuar"?

144

He estado lidiando mucho con Lua en los últimos meses, y realmente me gustan la mayoría de las características, pero todavía me falta algo entre ellas:

  • ¿Por qué no hay continue?
  • ¿Qué soluciones hay para ello?
Dant
fuente
12
Como se hizo esta pregunta, Lua recibió una gotodeclaración que puede usarse para implementar continuar. Ver las respuestas a continuación.
lhf

Respuestas:

71

En Lua 5.2, la mejor solución es usar goto:

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

Esto es compatible con LuaJIT desde la versión 2.0.1

pasarela
fuente
47
Espero que incluyan un continuedía real . El gotoreemplazo no se ve muy bien y necesita más líneas. Además, ¿no crearía problemas si tuviera más de un ciclo haciendo esto en una función, ambas con ::continue::? Inventar un nombre por ciclo no suena como algo digno de hacer.
ET
66

La forma en que el lenguaje gestiona el alcance léxico crea problemas al incluir ambos gotoy continue. Por ejemplo,

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 declaración de local adentro del cuerpo del bucle enmascara la variable externa nombrada a, y el alcance de ese local se extiende a través de la condición de la untildeclaración, por lo que la condición está probando lo más interno a.

Si continueexistiera, tendría que restringirse semánticamente para que solo sea válido después de que todas las variables utilizadas en la condición hayan entrado en el alcance. Esta es una condición difícil de documentar al usuario y aplicar en el compilador. Se han discutido varias propuestas sobre este tema, incluida la respuesta simple de no permitir continuecon el repeat ... untilestilo de bucle. Hasta el momento, ninguno ha tenido un caso de uso suficientemente convincente para incluirlos en el idioma.

La solución general es invertir la condición que haría continueque se ejecute a, y recolectar el resto del cuerpo del bucle bajo esa condición. Entonces, el siguiente 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

podría ser escrito

-- 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 lo suficientemente claro, y generalmente no es una carga a menos que tenga una serie de sacrificios elaborados que controlan la operación del bucle.

RBerteig
fuente
55
Viniendo de un fondo de Python, esta es una respuesta confusa porque cada ámbito ya sabe cuáles son sus variables locales antes de ejecutarse. Es decir, esperaba un error de variable local independiente en el caso de llegar until....
ubershmekel
2
Hubo mucha discusión sobre esto en la comunidad Lua antes de la introducción gotoen Lua 5.2. Naturalmente, gototiene el mismo problema. Finalmente, decidieron que, independientemente de los costos de tiempo de ejecución y / o generación de código que protegieran, valía la pena tener los beneficios de tener una flexibilidad gotoque se pueda utilizar para emular tanto continuea nivel múltiple como a nivel múltiple break. Tendría que buscar en los archivos de la lista Lua los hilos relevantes para obtener los detalles. Como lo presentaron goto, obviamente no era insuperable.
RBerteig
72
No hay nada "suficientemente claro" sobre escribir código sin continuar. Es un error novato anidar código dentro de un condicional donde se debería haber utilizado una continuación, y la necesidad de escribir código feo como ese no debería recibir ninguna simpatía. No hay absolutamente ninguna excusa.
Glenn Maynard
44
Esta explicación no tiene sentido. locales una directiva solo del compilador, no importa qué instrucciones de tiempo de ejecución se encuentren localy el uso variable, no necesita cambiar nada en el compilador para mantener el mismo comportamiento de alcance. Sí, esto podría no ser tan obvio y necesitará documentación adicional, pero, para reiterar nuevamente, requiere CERO cambios en el compilador. repeat do break end until trueejemplo en mi respuesta ya genera exactamente el mismo código de bytes que el compilador continuaría, la única diferencia es que con continueusted no necesitaría una sintaxis extra fea para usarlo.
Oleg V. Volkov
77
El hecho de que pueda probar la variable interna habla de un diseño defectuoso. La condición está fuera del alcance interno y no debe tener acceso a las variables dentro de ella. Considere el equivalente en C: do{int i=0;}while (i == 0);falla, o en C ++: do int i=0;while (i==0);también falla ("no se declaró en este ámbito"). Demasiado tarde para cambiar eso ahora en Lua, desafortunadamente.
Pedro Gimeno
47

Puede envolver el cuerpo del bucle en adicional repeat until truey luego usarlo do break enddentro para continuar. Naturalmente, necesitará configurar banderas adicionales si también tiene la intención de breaksalir del ciclo.

Esto se repetirá 5 veces, imprimiendo 1, 2 y 3 cada vez.

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

¡Esta construcción incluso se traduce en un código JMPde operación literal en 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
fuente
44
Esta respuesta es buena, pero aún requiere 3 líneas en lugar de solo una. (si "continuar" se admitió correctamente) Sin embargo, es un poco más bonito y seguro que una etiqueta Goto, ya que para ese nombre es posible que se deban evitar los conflictos para los bucles anidados.
ET
3
sin embargo, evita el problema "real" con goto en que no tiene que inventar un nuevo identificador / etiqueta para cada psuedo-continue y que es menos propenso a errores ya que el código se modifica con el tiempo. Estoy de acuerdo en que continuar sería útil , pero esta OMI es la siguiente mejor opción (y realmente requiere dos líneas para la repetición / hasta vs. un "continuar" más formal ... e incluso entonces, si le preocupa la línea cuenta que siempre se puede escribir "repetir" y "hasta el verdadero final", por ejemplo: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson
1
¡Es bueno ver que la gente realmente considera el rendimiento e incluso proporciona luacresultados en SO! Tenga un
voto
17

Directamente del diseñador del propio Lua :

Nuestra principal preocupación con "continuar" es que hay varias otras estructuras de control que (en nuestra opinión) son más o menos tan importantes como "continuar" e incluso pueden reemplazarlo. (Por ejemplo, romper con etiquetas [como en Java] o incluso un goto más genérico). "Continuar" no parece más especial que otros mecanismos de estructura de control, excepto que está presente en más idiomas. (Perl en realidad tiene dos declaraciones "continuar", "siguiente" y "rehacer". Ambas son útiles).

Stuart P. Bentley
fuente
55
Me encanta la admisión: "Ambos son útiles" justo después de una explicación de "no vamos a hacerlo"
David Ljung Madison Stellar
2
Era tener en cuenta el alcance que se busca a la dirección cuando se hicieron hacerlo, mediante la adición de una construcción "Goto" en el apartado 5.2 (que no habían sido liberados cuando esta respuesta fue escrito). Vea esta respuesta de 2012 , después del lanzamiento de 5.2.0.
Stuart P. Bentley
3
Correcto, porque 'goto' es bien reconocido como una construcción de programación decente. (Fin del sarcasmo) Ah, bueno.
David Ljung Madison Stellar
2
Pero no sonó más razonable que "Olvidé ponerlo continueen Lua, lo siento".
neoedmund
17

La primera parte se responde en las preguntas frecuentes como se señaló como asesinado .

En cuanto a una solución alternativa, puede envolver el cuerpo del bucle en una función y returndesde el principio, p. Ej.

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

O si desea ambas breaky continuefuncionalidad, haga que la función local realice la prueba, p. Ej.

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
fuente
16
Por favor no Se crea un entorno de cierre en cada iteración y esto es un ENORME desperdicio de memoria y ciclos de GC.
Oleg V. Volkov
44
verifique collectgarbage("count")incluso después de sus simples 100 intentos y luego hablaremos. Tal optimización "prematura" evitó que un proyecto de alta carga se reiniciara cada minuto la semana pasada.
Oleg V. Volkov
44
@ OlegV.Volkov, aunque este ejemplo pone una carga relativamente alta en el GC, no tiene fugas: se recopilarán todos los cierres temporales. No sé acerca de su proyecto, pero la mayoría de los reinicios de IME se deben a fugas.
finnw
10

Nunca he usado Lua antes, pero lo busqué en Google y se me ocurrió esto:

http://www.luafaq.org/

Verifique la pregunta 1.26 .

Esta es una queja común. Los autores de Lua consideraron que continuar era solo uno de una serie de posibles nuevos mecanismos de control de flujo (el hecho de que no puede funcionar con las reglas de alcance de repetir / hasta que fue un factor secundario).

En Lua 5.2, hay una instrucción goto que se puede usar fácilmente para hacer el mismo trabajo.

asesinado
fuente
8

Podemos lograrlo de la siguiente manera, saltará números pares

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

Nos encontramos con este escenario muchas veces y simplemente usamos una bandera para simular continuar. Tratamos de evitar el uso de declaraciones goto también.

Ejemplo: el código tiene la intención de imprimir las declaraciones de i = 1 a i = 10 excepto i = 3. Además, también imprime "loop start", loop end "," if start "y" if end "para simular otras instrucciones anidadas que existen en su código.

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

se logra encerrando todas las declaraciones restantes hasta el alcance final del bucle con un indicador de prueba.

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

No digo que este sea el mejor enfoque, pero nos funciona perfectamente.

winux
fuente
5

Lua es un lenguaje de script ligero que quiere ser lo más pequeño posible. Por ejemplo, muchas operaciones unarias como el incremento previo / posterior no están disponibles

En lugar de continuar, puedes usar 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
Ankit ihelper Sharma
fuente
4

De nuevo con la inversión, simplemente puede usar el siguiente código:

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
fuente
El problema con la inversión es que la mayoría de las veces hay múltiples condicionales en una serie (como para validar la entrada del usuario). Y debido a que podría necesitarse un cortocircuito en cualquier punto del camino, la inversión significa tener que anidar los condicionales continuamente (en lugar de "¿es esto malo? Luego escapar; de lo contrario, ¿es malo? Luego escapar", lo cual es muy sencillo, terminas con un código como "¿está bien? ¿Entonces está bien? ¿Entonces está bien? Entonces haz esto", lo cual es muy excesivo.
Leslie Krause
-2

¿Por qué no hay continuación?

Porque es innecesario¹. Hay muy pocas situaciones en las que un desarrollador lo necesite.

A) Cuando tiene un bucle muy simple, diga una línea de 1 o 2 líneas, entonces puede cambiar la condición del bucle y todavía es bastante legible.

B) Cuando está escribiendo un código de procedimiento simple (también conocido como cómo escribimos código en el siglo pasado), también debería estar aplicando una programación estructurada (también conocido como cómo escribimos un código mejor en el siglo pasado)

C) Si está escribiendo código orientado a objetos, su cuerpo de bucle debe consistir en no más de una o dos llamadas de método a menos que pueda expresarse en una o dos líneas (en cuyo caso, consulte A)

D) Si está escribiendo código funcional, simplemente devuelva una llamada simple para la próxima iteración.

El único caso en el que desea utilizar una continuepalabra clave es si desea codificar Lua como si fuera python, que simplemente no lo es.

¿Qué soluciones hay para ello?

A menos que se aplique A), en cuyo caso no hay necesidad de ninguna solución alternativa, debe estar haciendo una programación estructurada, orientada a objetos o funcional. Esos son los paradigmas para los que Lua fue creado, por lo que estarías luchando contra el idioma si te esfuerzas por evitar sus patrones.


Alguna aclaración:

¹ Lua es un lenguaje muy minimalista. Intenta tener la menor cantidad de características posibles, y una continuedeclaración no es una característica esencial en ese sentido.

Creo que esta filosofía del minimalismo es bien captada por Roberto Ierusalimschy en esta entrevista de 2019 :

agregue eso y eso y eso, apague eso, y al final entendemos que la conclusión final no satisfará a la mayoría de las personas y no pondremos todas las opciones que todos quieren, por lo que no ponemos nada. Al final, el modo estricto es un compromiso razonable.

² Parece que hay una gran cantidad de programadores que vienen a Lua desde otros idiomas porque cualquier programa para el que están tratando de escribir los usa, y muchos de ellos quieren no parecen querer escribir nada más que su idioma de elección, lo que lleva a muchas preguntas como "¿Por qué Lua no tiene la función X?"

Matz describió una situación similar con Ruby en una entrevista reciente :

La pregunta más popular es: "Soy de la comunidad del lenguaje X; ¿no puedes presentarle una característica del lenguaje X a Ruby?", O algo así. Y mi respuesta habitual a estas solicitudes es ... "no, no haría eso", porque tenemos un diseño de lenguaje diferente y políticas de desarrollo de lenguaje diferentes.

³ Hay algunas maneras de hackear esto; algunos usuarios han sugerido usar goto, lo cual es una aproximación lo suficientemente buena en la mayoría de los casos, pero se pone muy feo muy rápidamente y se rompe completamente con bucles anidados. El uso de gotos también lo pone en peligro de recibir una copia de SICP cada vez que muestra su código a otra persona.

DarkWiiPlayer
fuente
1
Voté en contra porque la primera oración es obviamente falsa, y el resto de la respuesta es inútil.
bfontaine
Inútil? Tal vez; Es una respuesta algo basada en la opinión. Sin embargo, la primera oración es obviamente cierta; continuepodría ser una característica conveniente, pero eso no lo hace necesario . Mucha gente usa Lua bien sin él, por lo que realmente no hay razón para que sea otra cosa que una característica ordenada que no es esencial para ningún lenguaje de programación.
DarkWiiPlayer
Eso no es un argumento: no se puede argumentar que las personas están "bien sin él" cuando no tienen otra opción.
bfontaine
Creo que entonces tenemos diferentes definiciones de "necesario".
DarkWiiPlayer