Я много работал с Lua в последние несколько месяцев, и мне действительно нравится большинство функций, но я все еще упускаю кое-что из них:
- Почему нет
continue
? - Какие есть обходные пути для этого?
Ответы:
В 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.
continue
день. goto
Замена не выглядит очень красиво и нуждается в большем количестве линий. Кроме того, не было бы проблем, если бы у вас было несколько циклов, выполняющих это в одной функции, оба с ::continue::
? Придумывать название для каждого цикла - не самое приличное занятие.
То, как язык управляет лексической областью, создает проблемы с включением как 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
Это достаточно ясно и обычно не является обузой, если у вас нет серии сложных отбраковок, которые управляют работой цикла.
until...
.
goto
в Lua 5.2. Естественно, goto
есть такая же проблема. В конце концов они решили, что независимо от затрат времени выполнения и / или генерации кода для защиты от него, стоит преимущества гибкости, goto
которую можно использовать для эмуляции как на continue
нескольких уровнях, так и на нескольких уровнях break
. Чтобы получить подробности, вам придется поискать в архивах списков Lua соответствующие темы. Поскольку они действительно представили goto
, это, очевидно, не было непреодолимым.
local
является директивой только для компилятора - не имеет значения, какие инструкции выполняются между local
и использованием переменных - вам не нужно ничего менять в компиляторе, чтобы поддерживать такое же поведение области видимости. Да, это может быть не так очевидно и требует дополнительной документации, но, повторюсь еще раз, это требует НУЛЕВЫХ изменений в компиляторе. repeat do break end until true
пример в моем ответе уже генерирует точно такой же байт-код, что и компилятор с continue, с той лишь разницей, что continue
вам не понадобится уродливый дополнительный синтаксис для его использования.
do{int i=0;}while (i == 0);
сбой или в C ++: do int i=0;while (i==0);
также сбой («не был объявлен в этой области»). К сожалению, слишком поздно менять это сейчас в Lua.
Вы можете дополнительно обернуть тело цикла, 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
luac
результаты на SO! Желаем заслуженного голоса :)
Первая часть ответа в 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
collectgarbage("count")
даже после ваших простых 100 попыток, и тогда мы поговорим. Такая «преждевременная» оптимизация спасла один высоконагруженный проект от перезагрузки каждую минуту на прошлой неделе.
Наша главная проблема с «продолжением» заключается в том, что существует несколько других структур управления, которые (на наш взгляд) более или менее важны, чем «продолжить», и могут даже заменить его. (Например, разрыв с метками [как в Java] или даже более общий goto.) «Continue» не кажется более особенным, чем другие механизмы структуры управления, за исключением того, что он присутствует в большем количестве языков. (На самом деле Perl имеет два оператора «continue», «next» и «redo». Оба они полезны.)
continue
Lua, извините».
Я никогда раньше не использовал Lua, но я погуглил и придумал следующее:
Проверьте вопрос 1.26 .
Это обычная жалоба. Авторы Lua считали, что continue было только одним из ряда возможных новых механизмов потока управления (тот факт, что он не может работать с правилами области действия repeat / until, был второстепенным фактором).
В Lua 5.2 есть оператор goto, который можно легко использовать для выполнения той же работы.
Мы сталкивались с этим сценарием много раз, и мы просто используем флаг для имитации продолжения. Мы также стараемся избегать использования операторов 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
Я не говорю, что это лучший подход, но он отлично работает для нас.
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
Опять же, при инвертировании вы можете просто использовать следующий код:
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end
Потому что в этом нет необходимости¹. Очень мало ситуаций, когда это может понадобиться разработчику.
A) Когда у вас очень простой цикл, скажем, с 1 или 2 строками, вы можете просто изменить условие цикла, и он все еще будет хорошо читаемым.
Б) Когда вы пишете простой процедурный код (он же, как мы писали код в прошлом веке), вы также должны применять структурированное программирование (иначе, как мы писали лучший код в прошлом веке).
C) Если вы пишете объектно-ориентированный код, тело вашего цикла должно состоять не более чем из одного или двух вызовов методов, если только это не может быть выражено в одно- или двухстрочном формате (в этом случае см. A)
D) Если вы пишете функциональный код, просто верните простой хвостовой вызов для следующей итерации.
Единственный случай, когда вы захотите использовать continue
ключевое слово, - это если вы хотите закодировать Lua как python, а это не так. ²
Если не применяется пункт A), и в этом случае нет необходимости в каких-либо обходных путях, вы должны выполнять структурированное, объектно-ориентированное или функциональное программирование. Это парадигмы, для которых был создан Lua, поэтому вам придется бороться с языком, если вы изо всех сил стараетесь избегать их шаблонов.
Некоторые пояснения:
¹ Lua - очень минималистичный язык. Он пытается иметь как можно меньше функций, и continue
утверждение в этом смысле не является важной функцией.
Я думаю, эта философия минимализма хорошо отражена Роберто Иерусалимши в этом интервью 2019 года :
добавьте то, то и то, выложите это, и, в конце концов, мы поймем, что окончательный вывод не удовлетворит большинство людей, и мы не будем предлагать все варианты, которые все хотят, поэтому мы ничего не ставим. В конце концов, строгий режим - это разумный компромисс.
² Похоже, что большое количество программистов приходят в Lua с других языков, потому что какая бы программа они ни пытались написать сценарий, случайно использует ее, и многие из них, похоже, не хотят писать ничего, кроме своего языка choice, что приводит к множеству вопросов, таких как «Почему в Lua нет функции X?»
Мац описал похожую ситуацию с Руби в недавнем интервью :
Самый популярный вопрос: «Я из сообщества языка X; не могли бы вы представить функцию языка X в Ruby?» Или что-то в этом роде. И мой обычный ответ на эти запросы: «Нет, я бы не стал этого делать», потому что у нас другой дизайн языка и разные политики языковой разработки.
³ Есть несколько способов обойти это; некоторые пользователи предложили использовать goto
, что в большинстве случаев является достаточно хорошим приближением, но очень быстро становится очень уродливым и полностью ломается из-за вложенных циклов. Использование goto
s также подвергает вас опасности получить копию SICP, брошенную вам всякий раз, когда вы показываете свой код кому-либо еще.
continue
может быть удобной функцией, но в этом нет необходимости . Многие люди прекрасно используют Lua и без него, так что на самом деле нет никаких оснований полагать, что это что-то еще, кроме изящной функции, которая не является существенной для любого языка программирования.
goto
инструкцию, которую можно использовать для реализации continue. См. Ответы ниже.