この初版はLua 5.0向けに執筆されました。以降のバージョンでも大部分の関連性は残っていますが、相違点もいくつかあります。
第4版はLua 5.3を対象とし、Amazonやその他の書店で購入できます。
この本を購入することで、Luaプロジェクトを支援することもできます。


7.1 - イテレータとクロージャー

イテレータとは、コレクションの要素を反復処理できる構造です。Luaでは、通常、イテレータは関数で表されます。その関数を呼び出すたびに、コレクションの「次の」要素が返されます。

どのイテレータも、その状態を連続する呼び出し間で維持する必要があります。これによって、どこにいるのか、そこからどのように処理を進めればよいかを認識できます。クロージャーは、このタスクに優れたメカニズムを提供します。クロージャーは、囲む関数内の1つまたは複数のローカル変数にアクセスする関数であることを思い出してください。これらの変数は連続するクロージャ呼び出し間でもその値を保持するため、クロージャはトラバーサルにおいてどこにいるのかを記憶できます。もちろん、新しいクロージャを作成するには、その外部ローカル変数も作成する必要があります。したがって、クロージャの作成には通常、2つの関数が関係します。クロージャ自体と、クロージャを作成するファクトリ(関数)です。

単純な例として、リストの単純なイテレータを作成してみましょう。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がファクトリです。それを呼び出すたびに、新しいクロージャ(イテレータそのもの)が作成されます。このクロージャは、その外部変数(tin)内に状態を格納するため、呼び出すたびにリストtから次の値を返します。リストに値がなくなると、イテレータはnilを返します。

そのようなイテレータはwhileと一緒に使用できます。

    t = {10, 20, 30}
    iter = list_iter(t)    -- creates the iterator
    while true do
      local element = iter()   -- calls the iterator
      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は実際にはそれ以上のことを行うことがわかります。)

より高度な例として、現在の入力ファイルからすべての単語をトラバースするイテレータを作成します。このトラバーサルを行うには、2つの値を保持する必要があります。現在の行と、その行内の位置です。このデータがあれば、いつでも次の単語を生成できます。それを保持するために、2つの外部ローカル変数lineposを使用します。

    function allwords ()
      local line = io.read()  -- current line
      local pos = 1           -- current position in the line
      return function ()      -- iterator function
        while line do         -- repeat while there are lines
          local s, e = string.find(line, "%w+", pos)
          if s then           -- found a word?
            pos = e + 1       -- next position is after this word
            return string.sub(line, s, e)     -- return the word
          else
            line = io.read()  -- word not found; try next line
            pos = 1           -- restart from first position
          end
        end
        return nil            -- no more lines: end of traversal
      end
    end
イテレータ関数の主要部分はstring.findを呼び出すことです。この呼び出しは、現在の行内の、現在の位置から始まる単語を検索します。パターン「%w+」を使用して「単語」を記述し、1つ以上の英数字に一致します。単語が見つかると、関数はその単語の後の最初の文字に現在位置を更新し、その単語を返します。(string.sub呼び出しは、指定された位置の間のlineからサブ文字列を抽出します)。それ以外の場合、イテレータは新しい行を読み取って検索を繰り返します。行がなくなると、反復処理の終了を知らせるためにnilを返します。

その複雑さにもかかわらず、allwordsの使い方は簡単です。

    for word in allwords() do
      print(word)
    end
これはイテレータに共通する状況です。書くのは難しいかもしれませんが、使うのは簡単です。大きな問題ではありません。多くの場合、Luaでプログラミングするエンドユーザーはイテレータを定義するのではなく、アプリケーションによって提供されるイテレータのみを使用しています。