Почему это не допустимый итератор таблицы?


Я видел этот пример в уроке lua:

function fromto(a, b)
  return
    function(state, seed)
      if (seed >= state) then return nil
      else return seed+1 end
    end, b, a-1
end

Он возвращает следующие целочисленные значения от a до b включительно. Поэтому я попытался применить ту же логику, написав следующий итератор таблицы:

function values(t) -- t is a table
  return
    function(state, seed)
      return state[seed+1]
    end, t, 0
end

Он возвращает первое значение правильно, но затем выдает ошибку, говоря, что я не могу делать арифметику в строковом значении (seed). Но разве seed не получает значение 0, которое является целым числом? Что происходит?

Поскольку нет процесса инкрементирования на первом например (например, a = a + 1), я думаю, что Луа может справиться с этим за кулисами... но если это не так, возможно, это и есть причина моего замешательства.

2 2

2 ответа:

Прочтите это .

seed это плохое имя для второго параметра вашей итерационной функции. На самом деле это управляющая переменная. Вы "затравляете" его, когда возвращаете 0 из values, но после этого его значение изменяется во время цикла: оно принимает первое значение, возвращенное из функции итератора.

Вы вернули строку из итератора (return state[seed+1]), поэтому при следующем вызове функции итератора эта строка была передана. Вы пытались сделать математику на нем, и... БАМ.

Универсальный for принимает три параметра: функцию итератора, инвариантное состояние и начальное управляющее значение. Итератор вызывается с состоянием и значением элемента управления. Затем итератор возвращает следующее управляющее значение или nil, чтобы указать, что итерация выполнена.

t = {"foo","bar","zip","zap"}

local function iteratorFunction (state, index)
  index = index + 1
  local val = state[index]
  if val == nil then return nil end
  return index, val
end

for k, v in iteratorFunction, t, 0 do
    print(k,v)
end
Таким образом, первый вызов iteratorFunction получает t и 0 в качестве параметров. следующий вызов iteratorFunction получает t и первое значение, возвращенное из iteratorFunction, ну и так далее и так далее.

Когда вы пишете функцию "генератор", такую как values, Вы просто возвращаете три начальных значения, требуемые циклом generic for, чтобы ваш код был более лаконичным при использовании этого итератора:

function values(t)
    local function iteratorFunction (state, index)
      index = index + 1
      local val = state[index]
      if val == nil then return nil end
      return index, val
    end
    return iteratorFunction, t, 0 -- the same three values used in the for loop above
end

for k, v in values(t) do
    print(k,v)
end

Единственным обязательным параметром для generic for является функция итератора. Инвариантное состояние и управляющая переменная могут быть равны нулю, что можно сделать, если ваша итерационная логика выполняется в закрытии:

function values(t)
    local index = 0
    -- our iterator function is a closure bound to `index` and `t`
    local function iteratorFunction()
      index = index + 1
      local val = t[index]
      if val == nil then return nil end
      return index, val
    end
    return iteratorFunction, t, 0 -- the same three values used in the for loop above
end

for k, v in values(t) do
    print(k,v)
end

Если вы измените return index, val на просто return val, values(t) будет сейчас перебирайте только значения в t. Мы не могли сделать этого раньше, потому что нам нужно было вернуть управляющую переменную для следующей итерации цикла. При закрытии мы поддерживаем управляющую переменную через переменную, связанную с закрытием (она же "upvalue").

Можно использовать другую форму итераторов с замыканиями:

local function values(t)
    local i = 0
    return function()
        i = i + 1
        return t[i]
    end
end

for x in values({1, 2, 3}) do
    print(x)
end

1 2 3

Итератор продолжается до тех пор, пока не будет возвращен nil. Недопустимое поле таблицы (за последним) всегда равно нулю, поэтому вам просто нужно продолжать идти.

Форма с передаваемыми значениями счетчика в вашем примере использует возвращаемое значение для обновления последнего значения счетчика и обычно не используется. Вы обновляете seed с вашей записью таблицы, которая является строкой, поэтому следующий проход должен завершиться ошибкой.

Кстати.: твой значения функция делает то же самое, что и ipairs.