Luaにおけるイテレータとジェネリックforの使い方を徹底解説
ジェネリックフォーの原則
イテレータとは、コレクション内のすべての要素に対して反復処理を行う仕組みのことです。Luaでは、イテレータは通常、関数として表現され、関数が呼ばれるたびにコレクション内の「次の」要素を返します。各イテレータは、呼び出しが成功するまでの間、自分がどこにいて、どのように次の位置に移動するかを知るために何らかの状態を維持する必要があり、クロージャはこれを可能にする。次の例は、リストに対する単純なイテレータである。
function values(t)
local i = 0
return function() i = i + 1; return t[i] end
end
ループの呼び出し
t = {10, 20, 30}
iter = values(t)
while true do
local el = iter()
if el == nil then break end
print(el)
end
一般的な呼び方
for el in values(t) do print(el) end
generic forは反復ループのためのすべての帳簿を作成する。イテレータ関数を内部で保持し、反復ごとにイテレータを呼び出し、イテレータがnilを返したらループを終了させる。実際、forはイテレータ関数f、定数状態s、制御変数aの3つの値を保持します。forが最初に行うことは、inの後の式を評価して、forが保持すべき3つの値を返すことです; 次にfをsとaで呼びます。
まず、コードの一部から見てみましょう。
for element in list_iter(t) do
print(element)
end
これ以上調べない前に、すでに知っていることの構造に基づいて、このコードを理解しようとすることができます。もしそうなら、list_iter(t) はコレクションのようなものを返すはずですが、実際には無名関数であるイテレータだけを返していることが既に分かっています。もちろん、イテレータを呼び出すたびに1つの要素が得られ、イテレータの結果はすべてコレクションと見なすことができます。すべての要素が揃ったところで、論理的な説明が必要であり、その論理がgeneric forのセマンティクスである。
まず文法を見てみよう、それにはこうある。
for <var-list> in <exp-list> do
<body>
end
全体の流れはこのようになります。
まず、in以降の式の値を初期化して計算し、generic forに必要な3つの値:反復関数、状態定数、制御変数を返すようにする。多値代入と同様に、式が返す結果が3つ以下なら、自己起動するようにする。
nilで埋め、余った分は無視されます。
次に、状態定数と制御変数を引数としてイテレータ関数を呼び出します(注:for構造体の場合、状態定数は無意味なので、初期化時にその値を取得してイテレータ関数に渡せばOKです)。
3つ目は、イテレータ関数が返す値を変数リストに代入することです。
四つ目は、最初に返された値がnilの場合はループが終了し、そうでない場合はループ本体が実行される。
5つ目は、ステップ2に戻って、もう一度イテレータ関数を呼び出すことです。
具体的には
for var_1, ... , var_n in explist do block end
と同じです。
do
local _f, _s, _var = explist
while true do
local var_1, ... , var_n = _f(_s, _var)
_var = var_1
if _var == nil then break end
block
end
end
generic forは、反復関数、状態定数、制御変数の3つの値を自身の中に保持する。
イテレータの状態
ステートレスイテレータは、それ自身は状態を保持せず、forループは一定の状態と制御変数を持つイテレータ関数を呼び出すだけである。このタイプのイテレータの典型的な例は ipairs です。以下は、Luaによるipairsの実装です。
local function iter(s, i)
i = i + 1
local v = s[i]
if v then return i, v end
end
function ipairs(s)
return iter, s, 0
end
forループでipairs(list)を呼び出すと、3つの値を取得し、Luaはiter(list, 0)を呼び出してlist, list[1]、iter(list, 1)でlist, list[2]、をnilになるまで取得します。
generic forは状態保持のために1つの定数状態と1つの制御変数しか提供しないが、時には他の多くの状態を保持することが必要である。これはクロージャを使うか、必要な状態をテーブルに詰め込んで定常状態に保存することで実現できる。
クロージャ、イテレータ、ジェネリックフォー
さて、Luaにはクロージャ、ジェネリック・フォー、イテレータという3つの構成要素があります。ループの場合、クロージャとイテレータを使うこともできるし、ジェネリックforとイテレータを使うこともできる。では、どのようなトレードオフが必要なのでしょうか。
のアドバイスがあります。
function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function ipairs (a)
return iter, a, 0
end
for i, v in ipairs(a) do
print(i, v)
end
Luaではこのケースが最も推奨されます。イテレータはupvalueに依存せず、クロージャも生成されず、状態定数と制御変数はイテレータの引数で渡されるgeneric forの助けによって保存されます。
この本からもう一つ例を挙げると
local iterator -- to be defined later
function allwords()
local state = {line = io.read(), pos = 1}
return iterator, state
end
function iterator (state)
while state.line do -- repeat while there are lines
-- search for next word
local s, e = string.find(state.line, "%w+", state.pos)
if s then -- found a word?
-- update next position (after this word)
state.pos = e + 1
return string.sub(state.line, s, e)
else -- word not found
state.line = io.read() -- try next line...
state.pos = 1 -- ... from first position
end
end
return nil -- no more lines: end loop
end
これは良いアイデアなのでしょうか?Luaが出す答えは「ノー」です。この本の中に、すべてを物語る一節があります。
ループ時に状態を保存してオブジェクトを生成しない方がコストがかからないため、可能な限りステートレスイテレータを書くべきです。ステートレスイテレータを実装できない場合は、可能な限りクロージャを使用すべきです。
ステートレス・イテレータが使えない場合は、可能な限りクロージャを使うべきです。クロージャを作るコストはテーブルを作るよりも小さく、Luaはテーブルよりもクロージャを高速に処理できるため、可能な限りテーブルを使うべきではありません。
関連
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン