Почему в Lua нет оператора continue?


152

Я много работал с Lua в последние несколько месяцев, и мне действительно нравится большинство функций, но я все еще упускаю кое-что из них:

  • Почему нет continue?
  • Какие есть обходные пути для этого?

12
Поскольку этот вопрос был задан, Lua получил gotoинструкцию, которую можно использовать для реализации continue. См. Ответы ниже.
lhf

Ответы:


79

В Lua 5.2 лучший обходной путь - использовать goto:

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

Это поддерживается в LuaJIT с версии 2.0.1.


49
Я надеюсь, что они включают настоящий continueдень. gotoЗамена не выглядит очень красиво и нуждается в большем количестве линий. Кроме того, не было бы проблем, если бы у вас было несколько циклов, выполняющих это в одной функции, оба с ::continue::? Придумывать название для каждого цикла - не самое приличное занятие.
ET

69

То, как язык управляет лексической областью, создает проблемы с включением как gotoи continue. Например,

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

Объявление local aвнутри тела цикла маскирует внешнюю переменную с именем a, а область действия этой локальной расширяется по условию untilоператора, поэтому условие проверяет самое внутреннее a.

Если бы он continueсуществовал, его нужно было бы ограничить семантически, чтобы он был действительным только после того, как все переменные, используемые в условии, попали в область видимости. Это сложное условие для документирования для пользователя и выполнения компилятором. Обсуждались различные предложения по этой проблеме, включая простой ответ о запрете continueс помощью repeat ... untilстиля цикла. Пока ни у одного из них не было достаточно убедительного варианта использования, чтобы включить их в язык.

Обычно обходной путь заключается в том, чтобы инвертировать условие, которое могло бы вызвать continueвыполнение, и собрать остальную часть тела цикла при этом условии. Итак, следующий цикл

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

можно было бы написать

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

Это достаточно ясно и обычно не является обузой, если у вас нет серии сложных отбраковок, которые управляют работой цикла.


5
Исходя из фона Python, это запутанный ответ, потому что каждая область видимости уже знает, каковы ее локальные переменные перед запуском. Т.е. я ожидал ошибки несвязанной локальной переменной в случае достижения until....
ubershmekel 01

2
Это было много обсуждений в сообществе Lua до введения gotoв Lua 5.2. Естественно, gotoесть такая же проблема. В конце концов они решили, что независимо от затрат времени выполнения и / или генерации кода для защиты от него, стоит преимущества гибкости, gotoкоторую можно использовать для эмуляции как на continueнескольких уровнях, так и на нескольких уровнях break. Чтобы получить подробности, вам придется поискать в архивах списков Lua соответствующие темы. Поскольку они действительно представили goto, это, очевидно, не было непреодолимым.
RBerteig

75
Нет ничего «достаточно ясного» в написании кода без продолжения. Ошибка новичка заключается в том, чтобы вкладывать код в условное выражение, в котором следовало бы использовать continue, и необходимость писать такой уродливый код не должна вызывать сочувствия. Нет абсолютно никаких оправданий.
Гленн Мейнард,

4
Это объяснение не имеет смысла. localявляется директивой только для компилятора - не имеет значения, какие инструкции выполняются между localи использованием переменных - вам не нужно ничего менять в компиляторе, чтобы поддерживать такое же поведение области видимости. Да, это может быть не так очевидно и требует дополнительной документации, но, повторюсь еще раз, это требует НУЛЕВЫХ изменений в компиляторе. repeat do break end until trueпример в моем ответе уже генерирует точно такой же байт-код, что и компилятор с continue, с той лишь разницей, что continueвам не понадобится уродливый дополнительный синтаксис для его использования.
Олег В. Волков

8
То, что вы можете протестировать внутреннюю переменную, говорит о некорректном дизайне. Условие находится за пределами внутренней области видимости, и у него не должно быть доступа к переменным внутри него. Рассмотрим эквивалент в C: do{int i=0;}while (i == 0);сбой или в C ++: do int i=0;while (i==0);также сбой («не был объявлен в этой области»). К сожалению, слишком поздно менять это сейчас в Lua.
Педро Гимено

47

Вы можете дополнительно обернуть тело цикла, repeat until trueа затем использовать do break endвнутри для эффекта continue. Естественно, вам нужно будет установить дополнительные флаги, если вы также намереваетесь действительно breakвыйти из цикла.

Это будет повторяться 5 раз, каждый раз печатая 1, 2 и 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

Эта конструкция даже переводится в буквальный код операции JMPв байт- код 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
Этот ответ хорош, но по-прежнему требует 3 строки вместо одной. (если "continue" поддерживался должным образом) Это немного красивее и безопаснее, чем метка goto, поскольку для этого имени может потребоваться избегать конфликтов во вложенных циклах.
ET

3
однако он позволяет избежать "реальной" проблемы с goto в том, что вам не нужно изобретать новый идентификатор / метку для каждого псевдо-продолжения и что он менее подвержен ошибкам, поскольку код изменяется с течением времени. Я согласен, что continue было бы полезно , но этот IMO - следующая лучшая вещь (и на самом деле требуется две строки для повторения / до по сравнению с более формальным «continue;» ... и даже тогда, если вы были озабочены строкой подсчитывает, что вы всегда можете написать «повторить» и «до конца», например: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Шон Уилсон

1
Приятно видеть, что люди действительно думают о производительности и даже предоставляют luacрезультаты на SO! Желаем заслуженного голоса :)
DarkWiiPlayer

18

Первая часть ответа в FAQ , как закланного указывал.

Что касается обходного пути, вы можете обернуть тело цикла функцией и на returnраннем этапе, например

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

Или, если вы хотите и того, breakи другого continue, попросите локальную функцию выполнить тест, например

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
Пожалуйста, не надо. Вы создаете среду закрытия на каждой итерации, и это ОГРОМНАЯ трата памяти и циклов сборки мусора.
Олег В. Волков

4
пойдите и проверьте collectgarbage("count")даже после ваших простых 100 попыток, и тогда мы поговорим. Такая «преждевременная» оптимизация спасла один высоконагруженный проект от перезагрузки каждую минуту на прошлой неделе.
Олег В. Волков

4
@ OlegV.Volkov хотя этот пример действительно создает относительно высокую нагрузку на сборщик мусора, он не протекает - все временные закрытия будут собраны. Я не знаю о вашем проекте, но большинство повторяющихся перезагрузок IME происходит из-за утечек.
finnw

17

Прямо от дизайнера Lua :

Наша главная проблема с «продолжением» заключается в том, что существует несколько других структур управления, которые (на наш взгляд) более или менее важны, чем «продолжить», и могут даже заменить его. (Например, разрыв с метками [как в Java] или даже более общий goto.) «Continue» не кажется более особенным, чем другие механизмы структуры управления, за исключением того, что он присутствует в большем количестве языков. (На самом деле Perl имеет два оператора «continue», «next» и «redo». Оба они полезны.)


5
Мне нравится признание: «И то, и другое полезно» сразу после объяснения «мы не собираемся этого делать»
Дэвид Люнг Мэдисон Стеллар

2
Это было отметить сферу , что они были , глядя на адрес , когда они сделали это сделать, добавляя «Готы» конструкцию в 5.2 (который не был выпущен , когда этот ответ был написан). См. Этот ответ от 2012 года после выпуска 5.2.0.
Стюарт П. Бентли

4
Верно - потому что goto признано хорошей конструкцией программирования. (конец сарказма) Ну что ж.
Дэвид Люнг Мэдисон Стеллар

2
Но это не звучало более разумно, чем «Я просто забыл вставить continueLua, извините».
neoedmund

10

Я никогда раньше не использовал Lua, но я погуглил и придумал следующее:

http://www.luafaq.org/

Проверьте вопрос 1.26 .

Это обычная жалоба. Авторы Lua считали, что continue было только одним из ряда возможных новых механизмов потока управления (тот факт, что он не может работать с правилами области действия repeat / until, был второстепенным фактором).

В Lua 5.2 есть оператор goto, который можно легко использовать для выполнения той же работы.


8

Мы можем добиться этого, как показано ниже, он пропустит четные числа

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

6

Мы сталкивались с этим сценарием много раз, и мы просто используем флаг для имитации продолжения. Мы также стараемся избегать использования операторов goto.

Пример: код намеревается распечатать операторы от i = 1 до i = 10, кроме i = 3. Вдобавок он также печатает «начало цикла», «конец цикла», «если начало» и «если конец» для имитации других вложенных операторов, существующих в вашем коде.

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

достигается заключением всех оставшихся операторов до конца цикла с тестовым флагом.

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

Я не говорю, что это лучший подход, но он отлично работает для нас.


6

Lua - это облегченный язык сценариев, который нужно сделать как можно меньше. Например, многие унарные операции, такие как приращение до / после, недоступны.

Вместо продолжения вы можете использовать goto как

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

Опять же, при инвертировании вы можете просто использовать следующий код:

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

Проблема с инверсией заключается в том, что чаще всего в серии есть несколько условных операторов (например, для проверки ввода пользователя). И поскольку в любой момент на пути может возникнуть короткое замыкание, инверсия означает необходимость непрерывного вложения условных выражений (вместо «это плохо? Затем уйти; иначе это плохо? Затем уйти», что очень просто, в конечном итоге вы получаете такой код, как «это нормально? тогда это нормально? тогда это нормально? тогда делайте это», что очень чрезмерно.
Лесли Краузе

-4

Почему нет продолжения?

Потому что в этом нет необходимости¹. Очень мало ситуаций, когда это может понадобиться разработчику.

A) Когда у вас очень простой цикл, скажем, с 1 или 2 строками, вы можете просто изменить условие цикла, и он все еще будет хорошо читаемым.

Б) Когда вы пишете простой процедурный код (он же, как мы писали код в прошлом веке), вы также должны применять структурированное программирование (иначе, как мы писали лучший код в прошлом веке).

C) Если вы пишете объектно-ориентированный код, тело вашего цикла должно состоять не более чем из одного или двух вызовов методов, если только это не может быть выражено в одно- или двухстрочном формате (в этом случае см. A)

D) Если вы пишете функциональный код, просто верните простой хвостовой вызов для следующей итерации.

Единственный случай, когда вы захотите использовать continueключевое слово, - это если вы хотите закодировать Lua как python, а это не так. ²

Какие есть обходные пути для этого?

Если не применяется пункт A), и в этом случае нет необходимости в каких-либо обходных путях, вы должны выполнять структурированное, объектно-ориентированное или функциональное программирование. Это парадигмы, для которых был создан Lua, поэтому вам придется бороться с языком, если вы изо всех сил стараетесь избегать их шаблонов.


Некоторые пояснения:

¹ Lua - очень минималистичный язык. Он пытается иметь как можно меньше функций, и continueутверждение в этом смысле не является важной функцией.

Я думаю, эта философия минимализма хорошо отражена Роберто Иерусалимши в этом интервью 2019 года :

добавьте то, то и то, выложите это, и, в конце концов, мы поймем, что окончательный вывод не удовлетворит большинство людей, и мы не будем предлагать все варианты, которые все хотят, поэтому мы ничего не ставим. В конце концов, строгий режим - это разумный компромисс.

² Похоже, что большое количество программистов приходят в Lua с других языков, потому что какая бы программа они ни пытались написать сценарий, случайно использует ее, и многие из них, похоже, не хотят писать ничего, кроме своего языка choice, что приводит к множеству вопросов, таких как «Почему в Lua нет функции X?»

Мац описал похожую ситуацию с Руби в недавнем интервью :

Самый популярный вопрос: «Я из сообщества языка X; не могли бы вы представить функцию языка X в Ruby?» Или что-то в этом роде. И мой обычный ответ на эти запросы: «Нет, я бы не стал этого делать», потому что у нас другой дизайн языка и разные политики языковой разработки.

³ Есть несколько способов обойти это; некоторые пользователи предложили использовать goto, что в большинстве случаев является достаточно хорошим приближением, но очень быстро становится очень уродливым и полностью ломается из-за вложенных циклов. Использование gotos также подвергает вас опасности получить копию SICP, брошенную вам всякий раз, когда вы показываете свой код кому-либо еще.


3
Я проголосовал против, потому что самое первое предложение явно неверно, а остальная часть ответа бесполезна.
bfontaine

Бесполезный? Может быть; это отчасти основанный на мнении ответ. Однако первое предложение очевидно верно; continueможет быть удобной функцией, но в этом нет необходимости . Многие люди прекрасно используют Lua и без него, так что на самом деле нет никаких оснований полагать, что это что-то еще, кроме изящной функции, которая не является существенной для любого языка программирования.
DarkWiiPlayer

1
Это не аргумент: нельзя утверждать, что людям «хорошо без этого», если у них нет выбора.
bfontaine,

Думаю, тогда у нас просто разные определения «необходимого».
DarkWiiPlayer
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.