Part17:イテレータとジェネレータ

穂乃果「祝!!」
ことり「μ’s!!」
ほのこと「「紅白歌合戦出場決定!!」」
海未「恥ずかしながら、ついにあの国民的歌番組のステージに立つことになりました」
穂乃果「前回の進撃の巨人みたいにお茶の間を凍り付かせないように頑張るよっ!」
ことり「穂乃果ちゃん、その発言は不穏当だよ!」


海未「さて今回はイテレータとジェネレータという概念を見てみましょう。オブジェクト指向が基礎になっていますので復習は忘れずに」


イテレータ

海未「これまで、for...ofのループで配列の要素を順に取り出せることを見てきました。文字列でも同じことができますし、オブジェクトのプロパティ列挙もできました」
穂乃果「あれ便利だよね。普通のforとか使わなくていいくらい」
海未「あれが実現できているのは、ArrayやStringがイテレータを持ったイテラブルなオブジェクトであるからなのです」
穂乃果「なんかわけのわからない単語きたよう」
海未「イテレータというのは、定められた仕様に基づいて要素の値を返すオブジェクトです」

0
1
2
3
4

ことり「これはクロージャかな」
海未「実装は普通のオブジェクトでもかまいません。重要なのはnextメソッドを持っていること、valuedoneを持つオブジェクトを返すということです」
ことり「えーっと、nextが呼ばれるたびにvalueが1ずつ大きくなるんだよね」
海未「そうです。0から4までを列挙するイテレータと考えてください」
穂乃果「定められた仕様っていうのはどういうこと?」
海未「for...ofなどの構文は、このイテレータを使って反復処理をしているんです。ですから、決まった呼び出し方と決まった戻り値になっている必要があります」
穂乃果「for...ofが認識できるようにしてるんだね」

海未「実際に何かのオブジェクトをイテラブルに、反復可能にするには、以下のようにイテレータを返すメソッドを実装します」

海未「これで、このオブジェクトはfor...of文で処理できるようになりました」

0
1
2
3
4

穂乃果「お~っ!」
海未「独自のオブジェクトで値の列挙が必要な場合、このように実装すると便利です」

海未「ここまでで紹介したArrayとStringの他に、MapやSet、argumentsオブジェクトなどもイテラブルです」
穂乃果「よーし、どんどん使おう」
海未「for...ofの他に、最初のころに紹介した分割代入ができるのもイテラブルなオブジェクトのみです」

海未「配列を作るのに使うこともできます」

穂乃果「これ、配列でも同じことできるの?」
海未「配列でこれをやっても同じ配列ができあがるだけですが・・・」
穂乃果「あ、そっか」


ジェネレータ

海未「イテレータを補完・強化するものとして、ジェネレータというものがあります」
ことり「補完とか強化っていうことは、イテレータと同じことができるのかな?」
海未「そうですね。あとは、イテレータでは実装が難しかったことが簡単に書けるようになっていたりします」

海未「でははじめに、さきほどのイテレータのコードをジェネレータで書き直してみましょう」

0
1
2
3
4

穂乃果「わ、だいぶすっきりしたね」
ことり「いきなりfor...ofとかまで使えちゃうんだ」
海未「まず、function*で定義しているのがジェネレータ関数と呼ばれるものです。この中では、yieldyield*というキーワードを使うことができます」
ことり「普通の関数とは別扱いなんだ」
海未「かなり特殊な動きをしますから・・・。単純なサンプルを出しましょう」

海未「yieldの動きはreturnと似ていますが、違いはそこで関数が終了しないことです」
穂乃果「yieldが3つあるから、3つ値を返してるの?」
海未「そうとも言えますが・・・next()の呼び出しごとに、yield1個分処理が進むと考えればよいでしょう」
ことり「関数の実行が中断される感じなの?」
海未「そうです。最初のnext()に対して”ほの”を返して、そこでジェネレータの処理は中断します。次にnext()を呼び出すと、続きから再開されて”こと”を返します。以下同じ、というわけです」
穂乃果「うん、なんとなくわかった」

ことり「でも、ちょっと不思議だね」
海未「何がですか?」
ことり「makeGenerator()ってしたときにジェネレータ関数って実行終わってるはずなのに、どうして後から中断とか再開とかできるのかなって」
海未「いいところに気付きましたね。実はジェネレータ関数は遅延評価で、makeGenerator()と書いた時点ではまだ実行されていないのです」
ことり「遅延評価?」
海未「ジェネレータ関数が返したジェネレータに対してnextを呼び出した時に、初めてジェネレータ関数内の処理が動き始めます」
穂乃果「ということは、makeGenerator()でやってることって」
海未「ジェネレータ関数に書かれた処理を内包したジェネレータオブジェクトを生成しているだけですね」
穂乃果「うーん、ややこしいことしてるなあ」
海未「関数の実行中断や遅延評価といった特殊な挙動があるので、ジェネレータ関数は通常の関数と区別されるんです」

海未「さて、yieldを使った例は見ましたが、yield*でできることを見てみましょう」

ほの
こと
うみ

海未「yield*を使うと、イテラブルなオブジェクトに対して順にyieldしていくことができます。この簡単な例ではあまり意味はないですが・・・」
ことり「ジェネレータ関数の方ではループを書かなくてもいいんだね」
海未「配列演算とは相性が良さそうです」

ほのこと
ほのうみ
ことほの
ことうみ
うみほの
うみこと

海未「2年生で取り得るカップリングをすべて列挙するものですが・・・」
ことり「その仕様ならバグがあるよ海未ちゃん」
海未「え?」
ことり「結果は”ことうみ”と”うみこと”だけだよね?」
海未(ほ、穂乃果・・・助けてください)
穂乃果(うん、それ無理)

海未「ジェネレータのnextには引数を渡すことができます。込み入った動きをするのですが、『直前のyield式の結果と置き換わる』という挙動になります」

海未「最初は引数なしですから”ほの”ですね。2回目のnextでは”こと”を渡していますから、3行目の1回目のyield result;の戻り値が”こと”になったものとして処理が進みます」
ことり「それで、最初からresultに入ってる”ほの”に、lastに代入された”こと”をくっつけて”ほのこと”なんだね」
穂乃果「えー、よくわかんない」
海未「こればかりは、あっさり理解したことりの方がすごいと思います・・・」


海未「イテレータとジェネレータについてはこれでおしまいです。特にジェネレータは、使いこなせれば強力な道具になりますから、よく復習しておいてください」


LINEで送る
Pocket


返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です