
Итераторы
Итераторы, или их ещё могут называть счётчики, используются для перечисления элементов произвольных последовательностей:
for v_1, v_2, ..., v_n in explist do ... end |
Число переменных в списке v_1, ..., v_n может быть произвольным и не обязано соответствовать числу выражений в списке explist. В роли explist обычно выступает вызов фунции-фабрики итераторов. Такая функция возвращает функцию-итератор, состояние и начальное значение управляющей переменной цикла. Итератор интерпретируется следующим образом:
do local f, s, v_1 = explist local v_2, ... , v_n while true do v_1, ..., v_n = f(s, v_1) if v_1 == nil then break end ... end end |
На каждом шаге значения всех переменных v_k вычисляются путем вызова функции-итератора. Значение управляющей переменной v_1 управляет завершением цикла — цикл завершается как только функция-итератор возвратит nil как значение для переменной var_1.
Фактически итерациями управляет переменная v_1, а остальные переменные можно рассматривать как «хвост», возвращаемый функцией-итератором:
do local f, s, v = explist while true do v = f(s, v) if v == nil then break end ... end end |
Оператор, в котором вызывается функция-фабрика, интерпретируется как обычный оператор присваивания т.е. функция-фабрика может возвращать произвольное количество значений.
Итераторы без внутреннего состояния
Итератор без внутреннего состояния не хранит никакой внутренней информации, позволяющей ему определить свое положение в итерируемом контейнере. Следующее значение управляющей переменной вычисляется непосредственно по ее предыдущему значению, а состояние используется для хранения ссылки на итерируемый контейнер. Вот пример простого итератора без внутреннего состояния:
function iter( a, i ) i = i + 1 local v = a[i] if v then return i, v else return nil end end
function ipairs( a ) return iter, a, 0 end |
Итераторы, хранящие состояние в замыкании
Если итератору для обхода контейнера необходимо внутреннее состояние, то проще всего хранить его в замыкании, создаваемом по контексту функции-фабрики. Вот простой пример:
function ipairs( a ) local i = 0 local t = a
local function iter() i = i + 1 local v = t[i] if v then return i, v else return nil end end
return iter end |
Здесь итератор хранит весь контекст в замыкании и не нуждается в состоянии и текущем значении управляющей переменной. Соответственно, итератор не принимает состояние и управляющую переменную, а фабрика не возвращает значение состояния и стартовое значение управляющей переменной.
Стандартные итераторы
Чаще всего итераторы применяются для обхода элементов таблиц. Для этого существует несколько предопределенных функций-фабрик итераторов. Фабрика pairs(t) возвращает итератор, дающий на каждом шаге индекс в таблице и размещенное по этому индексу значение:
for idx, val in pairs(tbl) do ... end |
На самом деле этот итератор легко определить, используя стандартную функцию next(tbl, idx):
function pairs(tbl) return next, tbl, nil end |
Функция next(tbl, idx) возвращает следующее за idx значение индекса при некотором обходе таблицы tbl (вызов next(tbl, nil) возвращает начальное значение индекса; после исчерпания элементов таблицы возвращается nil).
Фабрика ipairs(tbl) возвращает итератор, работающий совершенно аналогично описанному выше, но предназначенный для обхода таблиц, проиндексированные целыми числами начиная с 1.