Чому Lua не має "продовження" заяви?


144

Протягом останніх місяців я багато мав справу з Lua, і мені дуже подобається більшість функцій, але я все одно чогось серед них не вистачає:

  • Чому немає continue?
  • Які обхідні шляхи існують для цього?

12
Оскільки це питання було задано, Lua отримав gotoзаяву, яку можна використовувати для продовження. Відповіді дивіться нижче.
lhf

Відповіді:


71

У Луа 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


47
Я сподіваюся, що вони включають фактичний continueодин день. gotoЗаміна не виглядає дуже красиво і потребує в більшій кількості ліній. Крім того, чи не створить це проблеми, якби у вас було більше одного циклу, який виконує це в одній функції, обидва з ::continue::? Створення імені за циклом не здається гідною справою.
ET

66

Спосіб управління мовою лексичної сфери створює проблеми із включенням обох 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

2
Про це було багато дискусій у спільноті Луа перед введенням gotoу Lua 5.2. Природно, gotoмає те саме питання. Зрештою, вони вирішили, що незалежно від витрат на виконання та / або створення коду, для захисту від цього варто скористатися перевагами, що мають гнучкість, gotoяку можна використовувати для емуляції як на continueбагаторівневому рівні break. Вам потрібно буде шукати в архівах списків Lua для відповідних тем, щоб отримати детальну інформацію. Оскільки вони все-таки представили goto, це, очевидно, було непереборним.
RBerteig

72
Немає нічого «достатньо чіткого» щодо написання коду без продовження. Початкова помилка вкладати код в умовно, коли слід було б використовувати продовження, і необхідність писати такий некрасивий код не повинна отримувати жодної симпатії. Виправдання абсолютно немає.
Glenn Maynard

4
Це пояснення не має сенсу. local- це директива, що стосується лише компілятора - не важливо, між якими порушеннями виконання localта перемінним використанням є порушення - вам не потрібно нічого змінювати в компіляторі, щоб підтримувати таку ж поведінку в масштабі. Так, це може бути не так очевидно і потребує додаткової документації, але, щоб ще раз зазначити, для зміни компілятора потрібні зміни ZERO. repeat do break end until trueПриклад в моїй відповіді вже генерує абсолютно той самий байт-код, який би продовжував компілятор, лише відмінність полягає в тому, що з continueвами для його використання не знадобиться потворний зайвий синтаксис.
Олег Васильович Волков

7
Те, що ви можете перевірити внутрішню змінну, говорить про недосконалий дизайн. Умова знаходиться поза внутрішньою областю, і вона не повинна мати доступу до змінних всередині неї. Розглянемо еквівалент у C: do{int i=0;}while (i == 0);fails або у C ++: do int i=0;while (i==0);також fails ("не було оголошено в цій області"). На жаль, занадто пізно, щоб змінити це в Луа, на жаль.
Педро Гімено

47

Ви можете загортати корпус петлі додатково, repeat until trueа потім використовувати do break endвсередині для продовження ефекту. Природно, вам потрібно буде встановити додаткові прапори, якщо ви також маєте намір 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у байт-коді Луа!

$ 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 рядків замість лише одного. (якщо функція "продовження" була належним чином підтримана) Це трохи красивіше та безпечніше, ніж мітка goto, оскільки для цього вкладеного циклу, можливо, потрібно уникати сутичок.
ET

3
Однак, уникнути "реальної" проблеми з goto є те, що вам не доведеться вигадувати новий ідентифікатор / мітку для кожного psuedo-продовження, і що це менше схильність помилок, оскільки код змінюється з часом. я погоджуюся, що продовження буде корисним , але цей ІМО - це найкраща штука (і справді потрібні два рядки для повторення / до порівняння з більш офіційним "продовженням" ..), і навіть тоді, якщо ви були пов'язані з рядком підраховує, що ви завжди можете написати "зробити повторення" та "до справжнього кінця", наприклад: gist.github.com/wilson0x4d/f8410719033d1e0ef771 )
Shaun Wilson

1
Приємно бачити, що люди насправді розглядають продуктивність і навіть забезпечують luacвихід на SO! Майте заслужений результат :)
DarkWiiPlayer

17

Прямо від самого дизайнера Луа :

Наша основна проблема "продовжувати" полягає в тому, що існує кілька інших структур управління, які (на наш погляд) мають більш-менш важливе значення, як "продовжувати", і навіть можуть замінити його. (Наприклад, перерва з мітками [як у Java] або навіть більш загальним goto.) "Продовження" не здається більш особливим, ніж інші механізми структури управління, за винятком того, що він присутній у більшості мов. (Perl насправді має два заяви "продовжити", "наступний" і "повторно". Обидва корисні.)


5
Мені подобається визнання: "Обидва корисні" відразу після пояснення "ми не збираємось цього робити"
Девід Люнг Медісон Зоряний

2
Потрібно було відзначити сферу, яку вони намагалися вирішити, коли вони це зробили, додавши конструкцію "goto" в 5.2 (яка не була випущена під час написання цієї відповіді). Дивіться цю відповідь з 2012 року після виходу 5.2.0.
Стюарт П. Бентлі

3
Правильно - адже «goto» добре визнаний гідною програмою для програмування. (закінчення сарказму) Ну добре.
Девід Люнг Медісон Зоряний

2
Але це не звучало більш розумно, ніж "я просто забув покластись continueу Луа, вибач".
neoedmund

17

Перша частина відповідає в 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
Будь ласка, не варто. Ви створюєте середовище закриття на кожній ітерації, і це ВЕЛИЧЕЗА витрата пам'яті та циклів GC.
Олег Вікторович Волков

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

4
@ Олег В.Волков, хоча цей приклад накладає відносно високе навантаження на GC, він не протікає - всі тимчасові закриття будуть зібрані. Я не знаю про ваш проект, але найбільш повторювані перезавантаження IME - через витоки.
finnw

10

Я ніколи раніше не використовував Lua, але я погуглив його і придумав таке:

http://www.luafaq.org/

Перевірте питання 1.26 .

Це звичайна скарга. Автори Луа вважали, що продовження є лише одним із низки можливих нових механізмів управління потоком (той факт, що він не може працювати з правилами застосування повтору / поки не був вторинним фактором).

У 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

Я не кажу, що це найкращий підхід, але він прекрасно працює для нас.


5

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

Замість того, щоб продовжувати, ви можете використовувати 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

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

Проблема з інверсією полягає в тому, що найчастіше в серії є декілька умовних умов (наприклад, для перевірки введення користувача). І тому, що може виникнути коротке замикання в будь-якій точці шляху, інверсія означає, що потрібно постійно гніздовати умови (замість "це погано? Тоді втекти; інакше це погано? Тоді втекти", що дуже прямо, ви закінчите з кодом на кшталт "це добре? тоді це добре? тоді це добре? тоді зробіть це", що дуже надмірно.
Леслі Краузе

-2

Чому немає продовження?

Тому що це зайве¹. Дуже мало ситуацій, коли потрібен був би девіз.

A) Якщо у вас дуже простий цикл, скажімо, 1- або 2-вкладиш, ви можете просто повернути стан циклу, і він ще досить читабельний.

B) Коли ви пишете простий процедурний код (ака. Як ми писали код у минулому столітті), ви також повинні застосовувати структуроване програмування (ака. Як ми писали кращий код у минулому столітті)

C) Якщо ви пишете об'єктно-орієнтований код, тіло циклу має складатися не більше ніж з одного або двох викликів методів, якщо його не можна виразити в одно- або двовкладинці (у цьому випадку див. A)

Г) Якщо ви пишете функціональний код, просто поверніть звичайний дзвінок у відповідь на наступну ітерацію.

Єдиний випадок, коли ви хочете скористатися continueключовим словом, це якщо ви хочете кодувати Lua як би python, а це просто немає.

Які обхідні шляхи існують для цього?

Якщо не застосовується A), то в цьому випадку немає потреби в жодних обхідних шляхах, вам слід робити структуроване, об'єктно-орієнтоване або функціональне програмування. Це парадигми, для яких була побудована Луа, тож ви б'єтесь проти мови, якщо ви вийдете зі свого шляху, щоб уникнути їхніх зразків.³


Деякі роз’яснення:

¹ Lua - дуже мінімалістична мова. Намагається мати якомога менше функцій, як це може відійти, і continueтвердження не є істотною особливістю в цьому сенсі.

Я думаю, що ця філософія мінімалізму добре захоплена Роберто Ієрусалімщі в інтерв'ю 2019 року :

Додайте те і те, і це, викладіть це, і врешті-решт ми зрозуміли, що остаточний висновок не задовольнить більшість людей, і ми не будемо ставити всі варіанти, які всі бажають, тому ми нічого не ставимо. Зрештою, суворий режим - це розумний компроміс.

² Схоже, існує велика кількість програмістів, які приїжджають до Lua з інших мов, тому що, яку б програму вони не намагалися скриптувати, це трапляється використовувати її, і багато хто з них, схоже, не хочуть писати нічого, крім своєї мови вибір, який призводить до багатьох питань, таких як "Чому у Lua немає функції X?"

У недавньому інтерв'ю Мац описав подібну ситуацію з Рубі :

Найпопулярніше питання: "Я з мовної спільноти X; чи не можете ви ввести функцію від мови X до Ruby?", Або щось подібне. І моя звичайна відповідь на ці запити - "ні, я б цього не робив", тому що у нас різний дизайн мови та різна політика розвитку мови.

³ Є кілька способів зламати ваш шлях; деякі користувачі пропонують використовувати goto, що в більшості випадків є досить хорошим, але стає дуже некрасивим і швидко розривається з вкладеними петлями. Використання gotos також створює небезпеку того, що копія SICP буде кинута на вас, коли ви показуєте свій код комусь іншому.


1
Я поклонився тому, що перше речення, очевидно, помилкове, а решта відповіді не є корисною.
bfontaine

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

Це не аргумент: ви не можете стверджувати, що люди "добре без цього", коли у них немає вибору.
bfontaine

Я думаю, у нас тоді просто різні визначення "необхідного".
DarkWiiPlayer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.