Statistical Programming

Iterator, Iteratee, そしてEnumerator

Play FrameworkのIteratee, Enumeratorが最高に謎なんで現状の理解をここに記します。間違っている点を指摘してもらえると助かります。

参考にしたのは

http://qiita.com/sunny4381/items/a711fa72db26c9263b3f

http://mandubian.com/2012/08/27/understanding-play2-iteratees-for-normal-humans/

Play FrameworkにおけるIterateeとは繰り返す処理の中身のことで、ある型E
から別の型Aを生成する処理内容のこと。言わばforeachに引数として与える高階関数みたいなものでありループそのものでもあるのかな。となるとforeachそれ自体って感じ?そしてEnumeratorはコレクションに近い。
いわばEnumeratorがデータの生産者ならIterateeはその消費者ということ。
Scalaで通常使うコレクションやIteratorと何が違うかというと、まずEnumeratorはStreamだという点。StreamはListとかと違って遅延評価。
遅延評価とはその名の通りすぐさま評価されるのではなく実際に計算が必要になってから評価を行う手法。下の例ではStream(1,2,3)を作りそれをfilterにかけたり別のListと統合したりしてるわけだけど実際にその値が評価されているのはforeach以下を実行したときであってそれまでそのStreamは評価されていない。

scala> val s=1#::2#::3#::Stream.empty
s: scala.collection.immutable.Stream[Int] = Stream(1, ?)

scala> val f=s.filter(_>2)
f: scala.collection.immutable.Stream[Int] = Stream(3, ?)

scala> val x=f++List(10,20)
x: scala.collection.immutable.Stream[Int] = Stream(3, ?)

scala> x
res0: scala.collection.immutable.Stream[Int] = Stream(3, ?)

scala> x.foreach(println)
3
10
20

scala> x
res2: scala.collection.immutable.Stream[Int] = Stream(3, 10, 20)

scala> f
res3: scala.collection.immutable.Stream[Int] = Stream(3)

scala> s
res4: scala.collection.immutable.Stream[Int] = Stream(1, 2, 3)

この遅延評価のおかげでいくらでもStreamに値を列挙することができるらしい。このEnumerator[E]は(Eは型)3つケースを保持できる。

Input[E] ; 型Eのデータの集まり
Input.Empty ; データが空
Input.EOF ; ファイルから読み込む際にファイルの終わりにきたケース?

val pizza=Pizza("Domino Pizza")
val enumerator:Enumerator[Pizza]
        =Enumerator.enumInput(Input.el(pizza))

Enumeratorはただの生産者であり誰かが使ってくれるまで何もしない。さらに誰かが使うまで生産もしない。言わばレストランを予約した際に注文も事前にしておいたのに行ってみたら予約自体はできてたけど何も作ってなくて今から作りますね〜 ニコっ
みたいなものか。

次にIterateeとIteratorの違いだけど、Iteratorコレクションから生成される

※ご指摘によると、コレクションから生成されることが多いものの必ずしもコレクションから生成されるとは限らないらしい。Playの方ではどうなってるかはちょっとわからないですけど少なくともscala標準装備の方はそんな感じとのこと。

コレクションから生成されることは多いけれど、scala標準のIteratorは、hasNextとnextを定義するだけである程度簡単に独自のIterator定義できるし、他にもIterator.continuallyやIterator.iterateというメソッドがコンパニオンに存在したりと、必ずしも「Iteratorはコレクションから生成される」とは限らないと思います

一方でIterateeはより汎用的に使うことが出来る。また、Iterateeは不変(immutable)で、非ブロック非同期処理で、入力の型 E と出力の型 A が静的に定義されているという特長に加え、結果が必要になるまで何ら実行されないという特長があるとのこと。

Iterateeがループをするにあたって以前のIterateeの状態を知らなければいかんということで(例えば各要素を足し合わした合計を求めたい時に1ステップ前の状態を知らねば単にループするだけになる)Iterateeは3つの状態を持つ。Cont,Done,Error。ContとDoneは似ていて、ContはKeep goingな感じだけどDoneはもうそろ終わるよ!みたいな。参考にしたサイトではそういう意味を込めてIterateeは単純に状態を宣言する機械だ!っていってたな。

これまでIterateeとEnumeratorはセット販売ですよ〜みたいなことを書き連ねてきたけどEnumeratorは単にあると便利なヘルパーさんで実際Iterateeさえあれば一応おっk−なんだよね。
Iteratee[E, A].feed() メソッドを利用したら実際Enumeratorを使わずに同じことが可能。下の例のようにひとつひとつIterateeに要素をつかしていけばまあEnumeratorは必要ないっちゃない。

var iter: Iteratee[Int, Int] = Iteratee.fold(0) { (total, e) => total + e }

var futureiter: Future[Iteratee[Int, Int]] = null

futureiter = iter.feed(Input.El(1))

EnumeratorはIterateeというコンセントに差し込むように使うわけだけどrunメソッドとapplyメソッドという二つの差し込み方がある。この2つはどうやら返り値が異なり、
run ; Promise[ Iteratee[E,A] ]
apply ; Promise[A]
となっている。

val itera=Iteratee.fold(0)(_+_)
val enu=Enumerator(1,2,3,4,5)
val appresult:Promise[Iteratee[Int,Int]]=enu apply itera
val runresult:Promise[Int]=enu run itera

ちなみにPromise[Iteratee]とIterateeは可逆性が保証されている。遅延にしたかったらPromise、その必要がなかったらIterateeのまんまみたいな。

大まかな輪郭を掴めたんじゃないかと信じている。