Luaチュートリアル(V)。イテレータとジェネリック・フォー
1. イテレータとクロージャ。
Luaでは、イテレータは通常、関数であり、関数を呼び出すたびにコレクション内の"next"要素を返します。各イテレータは、呼び出しが成功するまでの間に何らかの状態を保持する必要があり、自分がどこにいて、次に走査されたときにどこにいるかが分かるようになっています。この観点から、Luaのクロージャ・メカニズムは、次の例に示すように、この問題に対する言語的な安全策を提供します。
function values(t)
local i = 0
return function()
i = i + 1
return t[i]
end
end
t = {10, 20, 30}
it = values(t)
while true do
local element = it()
if element == nil then
break
end
print(element)
end
--another foreach-based call (generic for)
t2 = {15, 25, 35}
for element in values(t2) do
print(element)
end
-- The output is.
--10
--20
--30
--15
--25
--35
上記の応用例から、whileアプローチよりもgeneric forアプローチの方が、より明確な実装ロジックを提供します。これは、Luaがイテレータ関数を内部に保持し、イテレータがnilを返してループが終了するまで、各反復でその暗黙の内部イテレータを呼び出すからです。
2. 汎用forのセマンティクス
上の例のイテレータは、ループするたびに新しいクロージャ変数を作成する必要があり、そうしないと、最初の反復が成功した後に新しいforループで使われたときに、クロージャが単に終了してしまうという明らかな欠点があるのです。
ここでは、Luaにおけるジェネリック(for)の仕組みを詳しく説明し、その後にステートレスイテレータの例を挙げて理解を深めていきます。イテレータの実装がステートレス・イテレータであれば、ジェネリック(for)ごとに新しいイテレータ変数を再宣言する必要はありません。
generic (for) 型の構文は以下の通りです。
for <var-list> in <exp-list> do
<body>
end
実用的なアプリケーションである <exp-list> では、通常は式(expr)を含むだけなので、ここでは簡単のために、式のリストではなく、式を含むだけの記述にします。ここでは、まず、以下のような式のプロトタイプと例を示します。
function ipairs2(a)
return iter,a,0
end
この関数は3つの値を返します。1つ目は実際のイテレータ関数変数、2つ目は定数オブジェクトで、ここではトラバースされるコンテナと解釈できます。3つ目の変数はiter()関数が呼ばれたときに渡される初期値です。
ここで、再びiter()関数の実装を見てみると、次のようになります。
local function iter(a, i)
i = i + 1
local v = a[i]
if v then
return i, v
else
return nil, nil
end
end
イテレータ関数iter()では、テーブルのキーと値に対応する2つの値を返します。キー(返されたi)がnilの場合、汎用(for)は、この反復が終了したと見なします。次のような実用的なユースケースを見てみましょう。
function ipairs2(a)
return iter,a,0
end
local function iter(a, i)
i = i + 1
local v = a[i]
if v then
return i, v
else
return nil, nil
end
end
a = {"one","two","three"}
for k,v in ipairs2(a) do
print(k, v)
end
-- The output is.
-1 one
-2 two
-3 three
この例での汎用的な(for)書き方は、例えば次のようなwhileループを使った方法に拡張することができる。
local function iter(a, i)
i = i + 1
local v = a[i]
if v then
return i, v
else
return nil, nil
end
end
function ipairs2(a)
return iter,a,0
end
a = {"one","two","three"}
do
local _it,_s,_var = ipairs2(a)
while true do
local var_1,var_2 = _it(_s,_var)
_var = var_1
if _var == nil then -- Note that here only the first one returned by the iterator function is determined if it is nil.
break
end
print(var_1,var_2)
end
end
-- Output is as above.
3. ステートレスイテレータの例
この例では、チェーンテーブルをトラバースするイテレータを実装します。
local function getnext(list, node) -- Iterator function.
if not node then
return list
else
return node.next
end
end
function traverse(list) --extension of generic(for)
return getnext,list,nil
end
--Initialize the data in the chain table.
list = nil
for line in io.lines() do
line = { val = line, next = list}
end
-- Iterate through the chain table as a generic (for).
for node in traverse(list) do
print(node.val)
end
ここで使われているトリックは、チェーンの先頭のノードを定数状態(トラバースによって返される2番目の値)、現在のノードを制御変数として使うことです。イテレータ関数 getnext() が最初に呼ばれたとき、node は nil なので、この関数は list を最初のノードとして返します。それ以降の呼び出しでは、nodeはもはやnilではないので、イテレータはチェーンの最後にnilノードを返すまでnode.nextを返し、その時点でジェネリック(for)はイテレータ・トラバーサルが終了したことを判断する。
最後の注意点として、traverse()関数とリスト変数は、新しいクロージャ変数を作成することなく、繰り返し呼び出すことが可能です。これは主にイテレータ関数(getnext)がステートレスイテレータとして実装されているためです。
4. 複雑な状態を持つイテレータ
前述のイテレータの実装では、イテレータは多くの状態を保持する必要がありますが、ジェネリック(for)型は状態を保持するための定数状態と制御変数しか提供しません。最も簡単な方法はクロージャを使うことですが、もちろん、すべての情報をテーブルにカプセル化して、それを定状態オブジェクトとしてイテレータに渡すことも可能です。定常状態変数自体は一定、つまり反復中に別のオブジェクトに変化することはありませんが、そのオブジェクトに含まれるデータが変化するかどうかは、完全にイテレータの実装に依存します。とりあえず、table型の定数オブジェクトはイテレータが依存するすべての情報をすでに含んでいるので、イテレータは総称(for)が提供する第2引数を完全に無視することができる。次のコードに見られるように、そのようなイテレータの例を以下に示します。
local iterator
function allwords()
local state { line = io.read(), pos = 1 }
return iterator, state
end
The --iterator function will be the real iterator
function iterator(state)
while state.line do
local s,e = string.find(state.line,"%w+",state.pos)
if s then
state.pos = e + 1
return string.sub(state.line,s,e)
else
state.line = io.read()
state.pos = 1
end
end
return nil
end
関連
最新
-
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 実装 サイバーパンク風ボタン