Pourquoi Lua n'a-t-il pas de déclaration «continue»?

144

J'ai beaucoup travaillé avec Lua ces derniers mois, et j'aime vraiment la plupart des fonctionnalités, mais il me manque encore quelque chose parmi celles-ci:

  • Pourquoi n'y a-t-il pas continue?
  • Quelles solutions de contournement y a-t-il?
Dant
la source
12
Depuis que cette question a été posée, Lua a obtenu une gotodéclaration qui peut être utilisée pour implémenter continue. Voir les réponses ci-dessous.
lhf

Réponses:

71

Dans Lua 5.2, la meilleure solution de contournement est d'utiliser goto:

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

Ceci est pris en charge dans LuaJIT depuis la version 2.0.1

Catwell
la source
47
J'espère qu'ils incluent un continuejour réel . Le gotoremplacement n'a pas l'air très beau et a besoin de plus de lignes. De plus, cela ne créerait-il pas de problèmes si vous aviez plus d'une boucle faisant cela dans une fonction, les deux avec ::continue::? Créer un nom par boucle ne semble pas être une bonne chose à faire.
ET
66

La façon dont le langage gère la portée lexicale crée des problèmes avec l'inclusion à la fois gotoet continue. Par exemple,

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 déclaration de l' local aintérieur du corps de la boucle masque la variable externe nommée a, et la portée de ce local s'étend à travers la condition de l' untilinstruction afin que la condition teste la plus interne a.

S'il continueexistait, il devrait être restreint sémantiquement pour n'être valide qu'une fois que toutes les variables utilisées dans la condition sont entrées dans la portée. C'est une condition difficile à documenter pour l'utilisateur et à appliquer dans le compilateur. Diverses propositions autour de cette question ont été discutées, y compris la réponse simple de refuser continueavec le repeat ... untilstyle de boucle. Jusqu'à présent, aucun n'a eu de cas d'utilisation suffisamment convaincant pour les inclure dans le langage.

Le contournement consiste généralement à inverser la condition qui entraînerait continuel'exécution d'un et à collecter le reste du corps de la boucle dans cette condition. Donc, la boucle suivante

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

pourrait être écrit

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

C'est assez clair et généralement pas un fardeau à moins que vous n'ayez une série d'abattages élaborés qui contrôlent l'opération de boucle.

RBerteig
la source
5
Venant d'un arrière-plan python, c'est une réponse déroutante car chaque portée sait déjà quelles sont ses variables locales avant de s'exécuter. Ie je m'attendais à une erreur de variable locale non liée dans le cas de l'atteinte until....
ubershmekel
2
Il y a eu beaucoup de discussions à ce sujet dans la communauté Lua avant l'introduction de gotodans Lua 5.2. Naturellement, gotoa le même problème. Ils ont finalement décidé que quels que soient les coûts d'exécution et / ou de génération de code à protéger, cela valait la peine d'avoir un flexible gotoqui peut être utilisé pour émuler à la fois continueet à plusieurs niveaux break. Vous devrez rechercher les archives de la liste Lua pour les fils pertinents pour obtenir les détails. Puisqu'ils l'ont introduit goto, ce n'était évidemment pas insurmontable.
RBerteig
72
Il n'y a rien de "suffisamment clair" sur l'écriture de code sans continuer. C'est une erreur de novice d'imbriquer du code dans un conditionnel où un continue aurait dû être utilisé, et le besoin d'écrire un code laid comme celui-ci ne devrait pas recevoir de sympathie. Il n'y a absolument aucune excuse.
Glenn Maynard
4
Cette explication n'a aucun sens. localest une directive réservée au compilateur - peu importe les insructions d'exécution entre l' localutilisation des variables et l'utilisation des variables - vous n'avez pas besoin de changer quoi que ce soit dans le compilateur pour conserver le même comportement de portée. Oui, cela n'est peut-être pas si évident et nécessite une documentation supplémentaire, mais, pour le répéter, cela nécessite des modifications ZERO dans le compilateur. repeat do break end until trueexemple dans ma réponse génère déjà exactement le même bytecode que le compilateur continuerait, la seule différence est qu'avec continuevous n'auriez pas besoin de syntaxe supplémentaire laide pour l'utiliser.
Oleg V. Volkov
7
Le fait que vous puissiez tester la variable interne parle d'un design défectueux. La condition est en dehors de la portée interne et elle ne doit pas avoir accès aux variables qu'elle contient. Considérez l'équivalent en C: do{int i=0;}while (i == 0);échoue, ou en C ++: do int i=0;while (i==0);échoue également ("n'a pas été déclaré dans cette portée"). Trop tard pour changer cela maintenant à Lua, malheureusement.
Pedro Gimeno
47

Vous pouvez envelopper le corps de la boucle en plus repeat until true, puis l'utiliser à l' do break endintérieur pour continuer. Naturellement, vous devrez configurer des indicateurs supplémentaires si vous avez également l'intention de vraiment vous breakdéconnecter.

Cela fera une boucle 5 fois, imprimant 1, 2 et 3 à chaque fois.

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

Cette construction se traduit même par un opcode littéral JMPdans le 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
Oleg V. Volkov
la source
4
Cette réponse est agréable, mais nécessite toujours 3 lignes au lieu d'une seule. (si "continue" était correctement supporté) C'est un peu plus joli et plus sûr qu'une étiquette goto, car pour ce nom, les conflits doivent être évités pour les boucles imbriquées.
ET
3
cela évite cependant le "vrai" problème avec goto dans la mesure où vous n'avez pas à inventer un nouvel identifiant / étiquette pour chaque psuedo-continue et qu'il est moins sujet aux erreurs car le code est modifié au fil du temps. Je suis d'accord que continuer serait utile , mais cette IMO est la meilleure chose suivante (et elle nécessite vraiment deux lignes pour la répétition / jusqu'à ce que par rapport à un "continue" plus formel; .. et même alors, si vous étiez concerné par la ligne compte que vous pouvez toujours écrire "faire répéter" et "jusqu'à la vraie fin", par exemple: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson
1
C'est bien de voir que les gens considèrent réellement les performances et fournissent même des luacrésultats sur SO! Avoir un vote positif bien mérité :)
DarkWiiPlayer
17

Directement du créateur de Lua lui - même :

Notre principale préoccupation avec «continuer» est qu'il existe plusieurs autres structures de contrôle qui (à notre avis) sont plus ou moins aussi importantes que «continuer» et peuvent même le remplacer. (Par exemple, rompre avec les étiquettes [comme en Java] ou même un goto plus générique.) "Continuer" ne semble pas plus spécial que d'autres mécanismes de structure de contrôle, sauf qu'il est présent dans plus de langages. (Perl a en fait deux instructions "continue", "next" et "redo". Les deux sont utiles.)

Stuart P. Bentley
la source
5
J'adore l'admission: "Les deux sont utiles" juste après une explication de "nous n'allons pas le faire"
David Ljung Madison Stellar
2
Il est de noter la portée qu'ils étaient à la recherche d'adresse quand ils l'ont fait faire, en ajoutant une construction « goto » dans 5.2 (qui n'a pas été libéré lorsque cette réponse a été écrit). Voir cette réponse de 2012 , après la sortie de la version 5.2.0.
Stuart P. Bentley
3
Exact - parce que «goto» est bien connu pour être une construction de programmation décente. (Fin du sarcasme) Ah bien.
David Ljung Madison Stellar
2
Mais cela ne semblait pas plus raisonnable que "j'ai juste oublié de mettre continueen Lua, désolé."
neoedmund
17

La première partie est répondue dans la FAQ comme l'a souligné tué .

En ce qui concerne une solution de contournement, vous pouvez envelopper le corps de la boucle dans une fonction et au returndébut de cela, par exemple

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

Ou si vous voulez les deux breaket la continuefonctionnalité, faites effectuer le test par la fonction locale, par exemple

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
la source
16
Veuillez ne pas le faire. Vous créez un environnement de fermeture à chaque itération et c'est un énorme gaspillage de mémoire et de cycles GC.
Oleg V. Volkov
4
allez vérifier collectgarbage("count")même après vos simples 100 essais et ensuite nous parlerons. Une telle optimisation "prématurée" a évité à un projet à forte charge de redémarrer chaque minute la semaine dernière.
Oleg V. Volkov
4
@ OlegV.Volkov alors que cet exemple met une charge relativement élevée sur le GC, il ne fuit pas - Toutes les fermetures temporaires seront collectées. Je ne sais pas pour votre projet, mais la plupart des redémarrages répétés de IME sont dus à des fuites.
finnw
10

Je n'ai jamais utilisé Lua auparavant, mais je l'ai googlé et j'ai trouvé ceci:

http://www.luafaq.org/

Vérifiez la question 1.26 .

C'est une plainte courante. Les auteurs de Lua ont estimé que continuer n'était que l'un des nombreux nouveaux mécanismes de contrôle de flux possibles (le fait qu'il ne puisse pas fonctionner avec les règles de portée de répétition / jusqu'à ce que ce soit un facteur secondaire).

Dans Lua 5.2, il existe une instruction goto qui peut être facilement utilisée pour faire le même travail.

tué
la source
8

Nous pouvons y parvenir comme ci-dessous, cela sautera des nombres pairs

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
la source
6

Nous avons rencontré ce scénario plusieurs fois et nous utilisons simplement un drapeau pour simuler continuer. Nous essayons également d'éviter l'utilisation des instructions goto.

Exemple: Le code a l'intention d'imprimer les instructions de i = 1 à i = 10 sauf i = 3. En outre, il imprime également "début de boucle", fin de boucle "," si début "et" si fin "pour simuler d'autres instructions imbriquées qui existent dans votre code.

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

est réalisé en englobant toutes les instructions restantes jusqu'à la fin de la portée de la boucle avec un indicateur de 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

Je ne dis pas que c'est la meilleure approche, mais cela fonctionne parfaitement pour nous.

winux
la source
5

Lua est un langage de script léger qui se veut le plus petit possible. Par exemple, de nombreuses opérations unaires telles que l'incrément pré / post ne sont pas disponibles

Au lieu de continuer, vous pouvez utiliser goto comme

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
la source
4

Encore une fois avec l'inversion, vous pouvez simplement utiliser le code suivant:

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
la source
Le problème avec l'inversion est que le plus souvent, il y a plusieurs conditions dans une série (comme pour valider l'entrée de l'utilisateur). Et comme il peut y avoir un court-circuit à tout moment sur le chemin, l'inversion signifie qu'il faut imbriquer les conditions en continu (au lieu de "est-ce mauvais? Puis s'échapper; sinon est-ce mauvais? Puis s'échapper", ce qui est très simple, vous vous retrouvez avec un code comme "est-ce que ça va? alors est-ce que ça va? alors est-ce que ça va? alors faites ceci" qui est très excessif.
Leslie Krause
-2

Pourquoi n'y a-t-il pas de suite?

Parce que c'est inutile¹. Il y a très peu de situations où un développeur en aurait besoin.

A) Lorsque vous avez une boucle très simple, disons un 1 ou 2 lignes, vous pouvez simplement inverser la condition de la boucle et elle est toujours très lisible.

B) Lorsque vous écrivez un code procédural simple (c'est-à-dire comment nous avons écrit du code au siècle dernier), vous devriez également appliquer une programmation structurée (c'est-à-dire comment nous avons écrit un meilleur code au siècle dernier)

C) Si vous écrivez du code orienté objet, le corps de votre boucle ne doit pas comporter plus d'un ou deux appels de méthode à moins qu'il ne puisse être exprimé en une ou deux lignes (dans ce cas, voir A)

D) Si vous écrivez du code fonctionnel, retournez simplement un appel de fin pour la prochaine itération.

Le seul cas où vous voudriez utiliser un continuemot-clé est si vous voulez coder Lua comme si c'était python, ce qui n'est tout simplement pas²².

Quelles solutions de contournement y a-t-il?

À moins que A) s'applique, auquel cas il n'y a pas besoin de solutions de contournement, vous devriez faire de la programmation structurée, orientée objet ou fonctionnelle. Ce sont les paradigmes pour lesquels Lua a été conçu, donc vous vous battrez contre le langage si vous faites tout votre possible pour éviter leurs modèles .³


Quelques précisions:

¹ Lua est un langage très minimaliste. Il essaie d'avoir aussi peu de fonctionnalités que possible, et une continuedéclaration n'est pas une caractéristique essentielle dans ce sens.

Je pense que cette philosophie du minimalisme est bien capturée par Roberto Ierusalimschy dans cette interview de 2019 :

ajoutez ceci et cela et cela, mettez cela dehors, et à la fin nous comprenons que la conclusion finale ne satisfera pas la plupart des gens et nous ne mettrons pas toutes les options que tout le monde veut, donc nous ne mettons rien. Au final, le mode strict est un compromis raisonnable.

² Il semble y avoir un grand nombre de programmeurs qui arrivent à Lua à partir d'autres langues parce que quel que soit le programme pour lequel ils essaient de créer un script, il l'utilise, et beaucoup d'entre eux ne semblent pas vouloir écrire autre chose que leur langue de choix, ce qui conduit à de nombreuses questions telles que "Pourquoi Lua n'a-t-il pas la fonctionnalité X?"

Matz a décrit une situation similaire avec Ruby dans une récente interview :

La question la plus populaire est: "Je suis de la communauté du langage X; ne pouvez-vous pas introduire une fonctionnalité du langage X à Ruby?", Ou quelque chose comme ça. Et ma réponse habituelle à ces demandes est… "non, je ne ferais pas ça", parce que nous avons différentes conceptions linguistiques et différentes politiques de développement linguistique.

³ Il existe plusieurs façons de contourner ce problème; certains utilisateurs ont suggéré d'utiliser goto, ce qui est une approximation assez bonne dans la plupart des cas, mais devient très moche très rapidement et se rompt complètement avec des boucles imbriquées. L'utilisation de gotos vous expose également au risque de voir une copie de SICP vous être envoyée chaque fois que vous montrez votre code à quelqu'un d'autre.

DarkWiiPlayer
la source
1
J'ai voté contre parce que la toute première phrase est manifestement fausse, et le reste de la réponse est inutile.
bfontaine le
Peu serviable? Peut être; c'est une réponse quelque peu basée sur l'opinion. La première phrase est évidemment vraie cependant; continuepeut être une fonctionnalité pratique, mais cela ne le rend pas nécessaire . Beaucoup de gens utilisent très bien Lua sans lui, donc il n'y a vraiment aucune raison pour que ce soit autre chose qu'une fonctionnalité intéressante qui n'est essentielle à aucun langage de programmation.
DarkWiiPlayer
Ce n'est pas un argument: vous ne pouvez pas prétendre que les gens «vont bien sans cela» lorsqu'ils n'ont pas le choix.
bfontaine le
Je pense que nous avons juste différentes définitions de «nécessaire» alors.
DarkWiiPlayer