K.Maebashi's BBS 削除ページ

以下の投稿を削除します。

[717] Re:イテレータ(とCPS)
返信
投稿者:(ぱ)
2007/02/20 02:13:25

土曜はとあるイベントにて大阪で飲んだくれておりまして、ずいぶん返事が遅くなりまして すみません。 それプラス、私自身、クロージャに慣れておらず継続もわかっておらず、今回のNykRさんの サンプルはずいぶん荷が重いものでした。とはいえ放り出していては勉強にならんので、 私なりに解釈してみました。間違い等ありましたらご指摘ください。 さて、単に木をイテレートするだけなら、 tree.each(closure(item) { # このitemについてなんかする }); という形のeachメソッドをツリーに装備するのは簡単で、ツリーの中で再帰して、 要素ごとに、渡されたクロージャを呼び出せばよいのですが、こういう形式(内部イテレータ) だと、あるツリーについて、「ここまで走査した」という状態を保存しておけないのが 問題になるわけですよね。その点、NykRさんの提示されたイテレータでは、こういう 書き方も許される。 # ふたつのイテレータでツリーを並列に走査する。 for (ite1 = iterator_GoF(foreachTree_CPS(tr)), ite2 = iterator_GoF(foreachTree_CPS(tr)); !ite1.isDone() && !ite2.isDone(); ite1.next(), ite2.next()) { print(" " + ite1.currentItem()); print(" " + ite2.currentItem()); } これができるのが外部イテレータの強みです。 Cなどで、再帰を使ってツリーを辿る場合、「ここまで走査した」という情報がスタック上に ありますから、スタックが1本しかない以上、ふたつのイテレータを並行して使うことは できないわけです(片方が片方の繰り返しに完全に含まれるなら可能)。 tr = {13,{5,{3,{},{}},{13,{12,{},{}},{}}},{16,{16,{15,{},{}},{}},{17,{},{}}}}; この例では、nodeの[0]にそのノードの値が、[1]に左の子が、[2]に右の子が 入っていますから、再帰で間順で走査するなら以下のようになります。 function internalIterate(node) { if (node.size() == 0) { } else { internalIterate(node[1]); # ...(a) print(" " + node[0]); # とりあえず表示しておく internalIterate(node[2]); } } (a)の箇所で左の子について再帰呼び出しを行い、それが戻ってきてから続きの処理を やっています。「戻ってきてから続きの処理をやる」ことができるのが再帰の嬉しい ところですが、それを期待していては、外部イテレータは作れない。 そこで、「戻ってきてから続きの処理をする」のではなく、「戻ってきてから やってほしい続きの処理をクロージャとして渡す」とすれば、関数から戻ってこなくても よくなる。その変換をしたのがこれ。 function internalIterate(node, cont) { if (node.size() == 0) { cont(); } else { internalIterate(node[1], closure () { # 続きをクロージャで渡す。 print(" " + node[0]); internalIterate(node[2], cont); }); } } # 第2引数には「処理の続きを渡す」のだから、最後に「おしまい」と表示される。 internalIterate(tr, closure() {print("おしまい");}); さて、この状態では、要素ごとに行う処理がprint()に固定されていますから 汎用性がありません。では引数でなにか関数を渡せば、とこうしても、 internalIterate(node[1], closure () { # 続きをクロージャで渡す。 proc(node[0]); # 処理を引数procで渡す internalIterate(node[2], cont); }); これでは内部イテレータと同じですから意味がありません。 そこで、このproc()にも、続きの処理をクロージャで渡してやることにします。 function foreachTree_CPS(proc) { return closure internalIterate(node, cont) { if (node.size() == 0) { cont(); } else { internalIterate(node[1], closure () { proc(node[0], closure() { internalIterate(node[2], cont); }); }); } }; } 外部イテレータの場合、このprocの第2引数をnext()の際に呼び出せばよいわけですね。 なぜならそれが「処理の続き」だから。 イテレータはこうなります。 function iterator_GoF(foreach_CPS, collection) { this = new_object(); this.first = closure () { this.isDone = closure () { return false; }; foreach_CPS(closure (item, cont) { this.currentItem = closure () { return item; }; this.next = closure () { cont(); }; })(collection, closure(){ # ..(b) this.isDone = closure () { return true; }; }); }; this.first(); return this; } イテレータのfirst()が呼び出されたときに、foreach_CPS()を呼び出して、 internalIetrate()を取得し、それに対しcollectionとクロージャを渡しています(b)。 これがつまり元のソースの internalIterate(tr, closure() {print("おしまい");}); に相当するわけですが、「おしまい」と表示する代わりに、isDoneについて、 trueを返すよう関数を差し替えています。 んで、internalIterate()に渡しているproc()では、 this.currentItem = closure () { return item; }; this.next = closure () { cont(); }; currentItemを設定後、nextに対し、contすなわち「処理の続き」をセットしています。 これで、次に利用者がnext()を呼び出したとき、internalIteratorの続きが実行される、と。 NykRさんのソースとは微妙に変わっていますが、こんな解釈でよいでしょうか? # いやあ、実に勉強になりました。
パスワード:

管理者削除