7.1 | Итератор и замыкатели в lua

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

Report Page