7.1 | Итератор и замыкатели в lua
fulcanellyДобро пожаловать, в этом переводе статьи из оффициального туториала lua вы узнаете как использовать и определять собственные итераторы и что же такое замыкатели (closure) и с чем их едят.

Итератор - это конструкция которая позволяет вам выполнить итерацию над элементами какого-то списка. В lua итераторы обычно представляют с помощью функций: каждый раз когда мы вызываем функцию она возвращает следущий элемент списка.
Любой итератор должен хранить свое состояние между последующими вызовами для того чтобы он знал где он сейчас и как действовать дальше. Замыкатели предоставляют превосходный механизм для этой задачи. Помните, что замыкатель (closure) - это функция которая имеет доступ к переменным функции в кторой она была задана.
Конечно для создания нового замыкания мы должны создать её внешние переменные. Поэтому замыкание обычно состоит из двух функций: замыкания и фабрики — функция которая создает замыкания. Как простой пример давайте напишем итератор для списка. В отличии от ipairs этот итератор не будет возвращать индекс каждого элемента, только значение:
function list_iter (t)
local i = 0
local n = table.getn(t)
return function ()
i = i + 1
if i <= n then return t[i] end
end
end
В этом примере list_iter — фабрика. каждый раз мы вызываем ее и она создает новое замыкание (сам итератор). Это замыкание хранит свое состояние в внешних переменных (i, n и t) для того чтобы каждый раз когда мы его вызываем оно возвращало следущее значение из списка t. Когда в списке больше нет значений итератор возвращает nil.
Мы можем использовать такой итератор с оператором while:
t = {10, 20, 30}
iter = list_iter(t) -- создаем итератор
while true do
local element = iter() -- вызываем итератор
if element == nil then break end
print(element)
end
Однако, будет легче использовать дженерик for. В конце кноцов он был разработан для такого рода итераций:
t = {10, 20, 30}
for element in list_iter(t) do
print(element)
end
Дженерик for делает всю грязную работу в цикле итераций: он вызывает фабрику итераторов; хранит функцию итератора внутри; так что мы не теперь не нуждаемся в перемнной iter; вызывать итератор перед каждой новой итерацией и останавливать цикл когда итератор возвращает nil. (Позже мы увидим что дженерик for делает больше чем это.)
В качестве более прдвинутого примера мы напишем итератор для прохода по каждому слову из текущего входного файла. Для того чтобы выполнить этот проход мы должны хранить два значения : текущая строка и где именно мы на этой строке. С этими сведениями, мы всегда сможем сгенерировать следущее слово. Для хранения этого мы будем использовать две внешние локальные переменные, line и pos:
function allwords ()
local line = io.read() -- текущая строка
local pos = 1 -- текущая позиция на строке
return function () -- функция-итератор
while line do -- повторяем пока есть строки
local s, e = string.find(line, "%w+", pos)
if s then -- найдено слово?
pos = e + 1 -- следущая позиция после этого слова
return string.sub(line, s, e) -- возвращаем слово
else
line = io.read() -- слово не найдено; пробуем следущую строку
pos = 1 -- перезаупскаем с первой позиции
end
end
return nil -- нет больше строк; завершаем проход
end
end
Главная часть итератора это вызов функции string.find. Которая ищет слово в текущей строке начиная с текущей позиции. Здесь описывано слово используя патерн "%w+" который соответствует нескольким буквенно-цифровым символам. Если она находит слово, функция обновляет текущую позицию на первый символ после слова и возвращает это слово. (Вызов string.sub извлекает подстроки с строки между задаными позициями.) В противном случае, итератор читает новую строку и продолжает поиск, если строк больше нет возвращает nil индицируя конец итерации.
Несмотря на всю сложность итератора, использование простое:
for word in allwords() do
print(word)
end
Это распространенная ситуация с итераторами: они могут быть сложны в написании, но легки в использовании. Но это не большая проблема, так как чаще всего программисты lua не определяют итераторы, а используют стандартные.
Спасибо за прочтение, если вы словили кайф от чтения этой статьи тогда подписывайтесь @picsbtw