Генераторы
Каждый раз писать coroutine.resume и ловить true быстро надоедает. Если вам нужны только значения, которые отдаёт сопрограмма, используйте coroutine.wrap. Она тоже создаёт сопрограмму, но возвращает вам функцию: каждый вызов этой функции возобновляет сопрограмму и возвращает только значения из yield, без true:
-- Мешок с семенами у Эммы
local nextSeed = coroutine.wrap(function()
coroutine.yield("морковь")
coroutine.yield("картофель")
coroutine.yield("тыква")
end)
print(nextSeed())
print(nextSeed())
print("Напоследок: " .. nextSeed())Он выводит:
морковь
картофель
Напоследок: тыкваФункция для сопрограммы здесь анонимная, она написана прямо в скобках, как вы знаете из урока о функциях. Функция, которая при каждом вызове отдаёт следующее значение, называется генератором.
Генераторы в for. Вы уже пользовались одним из них: for word in text:gmatch("%a+") do из урока о строках. gmatch возвращает функцию, а for … in вызывает её перед каждым оборотом цикла. То, что она возвращает, попадает в переменные цикла; когда она возвращает nil или ничего, цикл заканчивается. Генератор из coroutine.wrap сюда прекрасно подходит: когда его функция доходит до конца, вызов ничего не возвращает, и цикл останавливается.
Обычно вы пишете функцию, которая принимает какие-то настройки и возвращает новый генератор. Этот обходит грядки поля, ряд за рядом:
-- Все грядки поля
local function plots(rows, columns)
return coroutine.wrap(function()
for row = 1, rows do
for column = 1, columns do
coroutine.yield(row, column)
end
end
end)
end
for row, column in plots(2, 2) do
print("Грядка " .. row .. "-" .. column)
endОн выводит:
Грядка 1-1
Грядка 1-2
Грядка 2-1
Грядка 2-2Вложенные циклы спрятаны внутри генератора, и цикл, который им пользуется, так же прост, как цикл с ipairs. Между вызовами сопрограмма помнит, где она находится в обоих циклах. Каждый вызов plots(2, 2) создаёт новый генератор, который снова начинает с грядки 1-1.
Будьте внимательны:
- Генератор исчерпан, когда его функция закончилась. Если вызвать его ещё раз, программа остановится с ошибкой
cannot resume dead coroutine. Для нового прохода создайте новый генератор. - Обычная функция с
yieldвнутри, созданная не черезcoroutine.wrap, вforне работает: цикл запускает её как обычную функцию, и еёyieldпадает с ошибкойattempt to yield from outside a coroutine. - Сопрограмма из
coroutine.create— не функция, поэтому вforона тоже не работает:attempt to call a thread value. Дляforиспользуйтеwrap.
Задание
- 1
Функция
findReadyотдаёт (через yield) культуру каждой грядки, которая росла достаточно долго. Сделайте из неё генератор,local nextReady = coroutine.wrap(findReady), вызовите его дважды и выведите то, что он возвращает. Программа выводит:морковь пшеница - 2
Теперь соберите урожай со всего поля. Пройдите циклом по новому генератору,
for crop in coroutine.wrap(findReady) do, и для каждой готовой грядки выведитеУрожай:и её культуру. (Почему новый?nextReadyуже отдал две культуры и продолжил бы с третьей.) Программа выводит:морковь пшеница Урожай: морковь Урожай: пшеница Урожай: клубника