Por que Lua não tem uma declaração "continue"?

143

Tenho lidado muito com Lua nos últimos meses e realmente gosto da maioria dos recursos, mas ainda estou perdendo algo entre eles:

  • Por que não existe continue?
  • Quais soluções alternativas existem para isso?
Dant
fonte
12
Desde que essa pergunta foi feita, Lua obteve uma gotodeclaração que pode ser usada para implementar continue. Veja as respostas abaixo.
Lhf 11/12/12

Respostas:

70

No Lua 5.2, a melhor solução alternativa é 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

Isso é suportado no LuaJIT desde a versão 2.0.1

catwell
fonte
47
Espero que eles incluam continueum dia real . A gotosubstituição não parece muito agradável e precisa de mais linhas. Além disso, isso não criaria problemas se você tivesse mais de um loop fazendo isso em uma função, ambos com ::continue::? Criar um nome por loop não parece uma coisa decente.
ET
65

A maneira como o idioma gerencia o escopo lexical cria problemas com a inclusão de ambos gotoe continue. Por exemplo,

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

A declaração de local adentro do corpo do loop mascara a variável externa denominada ae o escopo desse local se estende pela condição da untilinstrução, para que a condição esteja testando o mais interno a.

Se continueexistisse, teria que ser restrito semanticamente para ser válido somente depois que todas as variáveis ​​usadas na condição entrassem em escopo. Essa é uma condição difícil de documentar para o usuário e impor no compilador. Várias propostas em torno dessa questão foram discutidas, incluindo a resposta simples de desaprovar continueo repeat ... untilestilo do loop. Até o momento, nenhum teve um caso de uso suficientemente atraente para incluí-los no idioma.

A solução alternativa é geralmente inverter a condição que causaria continuea execução de um e coletar o restante do corpo do loop sob essa condição. Então, o seguinte loop

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

poderia 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

É claro o suficiente, e geralmente não é um fardo, a menos que você tenha uma série de abates elaborados que controlam a operação do loop.

RBerteig
fonte
5
Vindo de um fundo python, essa é uma resposta confusa, pois todos os escopos já sabem quais são suas variáveis ​​locais antes da execução. Ou seja, eu esperava um erro variável local ilimitado no caso de alcançar until....
Ubershmekel #
2
Houve muita discussão sobre isso na comunidade Lua antes da introdução gotono Lua 5.2. Naturalmente, gototem o mesmo problema. Eles finalmente decidiram que, independentemente do custo de tempo de execução e / ou de geração de código, os benefícios valiam por ter um sistema flexível gotoque pode ser usado para emular os dois continuee os vários níveis break. Você precisaria procurar nos arquivos da lista Lua os segmentos relevantes para obter os detalhes. Desde que eles introduziram goto, obviamente não era intransponível.
RBerteig
72
Não há nada "claro o suficiente" em escrever código sem continuar. É um erro principiante aninhar código dentro de uma condição em que uma continuação deveria ter sido usada, e a necessidade de escrever um código feio como esse não deve receber simpatia. Não há absolutamente nenhuma desculpa.
Glenn Maynard
4
Essa explicação não faz sentido. localé uma diretiva apenas para compilador - não importa quais são as instruções de tempo de execução locale o uso variável - você não precisa alterar nada no compilador para manter o mesmo comportamento de escopo. Sim, isso pode não ser tão óbvio e precisa de alguma documentação adicional, mas, para reiterar novamente, requer ZERO alterações no compilador. repeat do break end until truePor exemplo, na minha resposta já gera exatamente o mesmo bytecode que o compilador continuaria, a única diferença é que continuevocê não precisaria de uma sintaxe extra feia para usá-lo.
Oleg V. Volkov
7
O fato de você poder testar a variável interna fala sobre um projeto defeituoso. A condição está fora do escopo interno e não deve ter acesso às variáveis ​​dentro dele. Considere o equivalente em C: do{int i=0;}while (i == 0);falha ou em C ++: do int i=0;while (i==0);também falha ("não foi declarado neste escopo"). Tarde demais para mudar isso agora em Lua, infelizmente.
Pedro Gimeno
46

Você pode envolver o corpo do loop em adicional repeat until truee, em seguida, usá-lo do break endpara efeito de continuar. Naturalmente, você precisará configurar sinalizadores adicionais se também quiser realmente breakficar fora de loop.

Isso repetirá 5 vezes, imprimindo 1, 2 e 3 a 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

Essa construção é traduzida literalmente em um opcode JMPno 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
fonte
4
Esta resposta é boa, mas ainda requer 3 linhas em vez de apenas uma. (se "continue" foi suportado adequadamente) É um pouco mais bonito e mais seguro do que um rótulo de goto, já que, para esse nome, talvez seja necessário evitar conflitos para loops aninhados.
ET
3
no entanto, evita o problema "real" do goto, pois você não precisa inventar um novo identificador / rótulo para cada psuedo-continue e é menos suscetível a erros à medida que o código é modificado ao longo do tempo. Concordo que continuar seria útil , mas esta IMO é a próxima melhor coisa (e realmente requer duas linhas para repetir / até vs. um "continuar" mais formal; .. e mesmo assim, se você estava preocupado com a linha contagem você pode sempre escrever "fazer repeat" e "até verdadeiro fim", por exemplo: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson
1
É bom ver que as pessoas realmente consideram o desempenho e até fornecem luacresultados no SO! Tenha um
voto
16

Diretamente do designer do próprio Lua :

Nossa principal preocupação com "continuar" é que existem várias outras estruturas de controle que (a nosso ver) são mais ou menos tão importantes quanto "continuar" e podem até substituí-lo. (Por exemplo, quebre os rótulos [como em Java] ou mesmo um goto mais genérico.) "Continue" não parece mais especial do que outros mecanismos da estrutura de controle, exceto que ele está presente em mais idiomas. (O Perl realmente tem duas instruções "continue", "next" e "refazer". Ambas são úteis.)

Stuart P. Bentley
fonte
5
Adoro a admissão: "Ambos são úteis" logo após uma explicação de "não vamos fazer isso"
David Ljung Madison Stellar
2
Era para observar o escopo que eles estavam procurando abordar quando o fizeram , adicionando uma construção "goto" na versão 5.2 (que não havia sido liberada quando esta resposta foi escrita). Veja esta resposta de 2012 , após o lançamento do 5.2.0.
Stuart P. Bentley
3
Certo - porque 'goto' é bem reconhecido como uma construção de programação decente. (sarcasmo final) Ah, bem.
David Ljung Madison Stellar
2
Mas não parecia mais razoável do que "eu esqueci de colocar continueLua, desculpe".
Neoedmund
16

A primeira parte é respondida no FAQ como morto fora pontudo.

Quanto a uma solução alternativa, você pode agrupar o corpo do loop em uma função e returndesde o início, por exemplo,

-- 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, se você deseja ambos breake continuefuncionalidade, faça com que a função local realize o teste, por exemplo

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
fonte
16
Por favor não. Você cria um ambiente de fechamento em cada iteração e isso é um enorme desperdício de memória e ciclos de GC.
Oleg V. Volkov
4
vá checar collectgarbage("count")mesmo após suas 100 tentativas simples e depois conversaremos. Essa otimização "prematura" salvou um projeto de alta carga da reinicialização a cada minuto na semana passada.
Oleg V. Volkov
4
@ OlegV.Volkov, enquanto este exemplo coloca uma carga relativamente alta no GC, ele não vaza - Todos os fechamentos temporários serão coletados. Não sei sobre o seu projeto, mas as reinicializações mais repetidas do IME são devido a vazamentos.
finnw
9

Eu nunca usei Lua antes, mas pesquisei no Google e criei isso:

http://www.luafaq.org/

Verifique a pergunta 1.26 .

Esta é uma reclamação comum. Os autores de Lua acharam que continue era apenas um dos vários possíveis mecanismos de controle de fluxo (o fato de não poder trabalhar com as regras de escopo de repetição / até era um fator secundário).

No Lua 5.2, há uma instrução goto que pode ser facilmente usada para fazer o mesmo trabalho.

morto
fonte
7

Podemos alcançá-lo como abaixo, pulará 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
fonte
5

Encontramos esse cenário muitas vezes e simplesmente usamos um sinalizador para simular a continuação. Tentamos evitar o uso de instruções goto também.

Exemplo: O código pretende imprimir as instruções de i = 1 a i = 10, exceto i = 3. Além disso, ele também imprime "loop start", loop end "," if start "e" if end "para simular outras instruções aninhadas que existem no seu 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

é alcançado colocando-se todas as instruções restantes até o escopo final do loop com um sinalizador de teste.

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

Não estou dizendo que essa é a melhor abordagem, mas funciona perfeitamente para nós.

winux
fonte
4

Lua é uma linguagem de script leve que deseja menor quanto possível. Por exemplo, muitas operações unárias, como pré / pós incremento, não estão disponíveis

Em vez de continuar, você pode usar ir como

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

Novamente com a inversão, você pode simplesmente usar o seguinte 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
fonte
O problema da inversão é que, na maioria das vezes, existem vários condicionais em uma série (como para validar a entrada do usuário). E, como pode haver um curto-circuito em qualquer ponto do caminho, inversão significa ter que aninhar as condicionais continuamente (em vez de "isso é ruim? Depois escapar; mais isso é ruim? Então escapar", o que é muito direto, você acaba com um código como "é esta bem então é este problema, então, é esta bem então fazer isso???", que é muito excessivo.
Leslie Krause
-3

Por que não há como continuar?

Porque é desnecessário¹. Existem muito poucas situações em que um desenvolvedor precisaria.

A) Quando você tem um loop muito simples, digamos um liner de 1 ou 2, você pode simplesmente mudar a condição do loop e ainda assim é legível.

B) Ao escrever código processual simples (também conhecido como como escrevemos código no século passado), você também deve aplicar programação estruturada (também conhecido como como escrevemos melhor código no século passado)

C) Se você estiver escrevendo código orientado a objeto, seu corpo do loop deve consistir em não mais de uma ou duas chamadas de método, a menos que possa ser expresso em uma ou duas linhas (nesse caso, consulte A)

D) Se você estiver escrevendo código funcional, basta retornar uma chamada final simples para a próxima iteração.

O único caso em que você deseja usar uma continuepalavra-chave é codificar Lua como se fosse python, o que simplesmente não é.²

Quais soluções alternativas existem para isso?

A menos que A) se aplique; nesse caso, não há necessidade de soluções alternativas, você deve estar fazendo programação estruturada, orientada a objetos ou funcional. Esses são os paradigmas para os quais Lua foi criada, então você estaria lutando contra a linguagem se se esforçar para evitar os padrões deles.³


Alguns esclarecimentos:

Lua é uma linguagem muito minimalista. Ele tenta ter o mínimo de recursos possível e uma continuedeclaração não é um recurso essencial nesse sentido.

Acho que essa filosofia do minimalismo é bem capturada por Roberto Ierusalimschy nesta entrevista de 2019 :

adicione isso e aquilo e aquilo, coloque isso para fora e, no final, entendemos que a conclusão final não satisfará a maioria das pessoas e não colocaremos todas as opções que todos desejarem, por isso não colocamos nada. No final, o modo estrito é um compromisso razoável.

² Parece haver uma grande quantidade de programadores chegando a Lua de outras linguagens, porque qualquer programa que eles estão tentando criar o script o usa, e muitos deles querem não parecem querer escrever outra coisa senão a linguagem deles. opção, o que leva a muitas perguntas como "Por que Lua não possui o recurso X?"

Matz descreveu uma situação semelhante com Ruby em uma entrevista recente :

A pergunta mais popular é: "Sou da comunidade do idioma X; você não pode introduzir um recurso do idioma X no Ruby?", Ou algo assim. E minha resposta usual para essas solicitações é ... "não, eu não faria isso", porque temos um design de linguagem diferente e políticas de desenvolvimento de linguagem diferentes.

³ Existem algumas maneiras de contornar isso; alguns usuários sugeriram o uso goto, o que é uma aproximação suficientemente boa na maioria dos casos, mas fica muito feio muito rapidamente e quebra completamente com loops aninhados. O uso de gotos também coloca você em risco de ter uma cópia do SICP sempre que você mostra seu código a mais alguém.

DarkWiiPlayer
fonte
1
Fiz uma votação baixa porque a primeira frase é obviamente falsa e o restante da resposta é inútil.
bfontaine 5/01
Inútil? Talvez; é uma resposta um tanto baseada em opiniões. A primeira frase é obviamente verdadeira; continuepode ser um recurso conveniente, mas isso não o torna necessário . Muitas pessoas usam Lua muito bem sem ele, então não há realmente nenhum caso de ser algo além de um recurso interessante que não é essencial para qualquer linguagem de programação.
DarkWiiPlayer
Isso não é argumento: você não pode argumentar que as pessoas estão "bem sem ela" quando não têm escolha.
bfontaine 6/01
Eu acho que temos apenas diferentes definições de "necessário" então.
DarkWiiPlayer 6/01