Scala で Python の Generator みたいな何か
- 2011/01/13 [twitter:@okomok] さんに Generator を使ったあとに hasNext を呼ぶとブロックするという点を指摘していただいたので修正
- ではなくて このように hasNext が副作用があるから next の呼び出し回数以上に hasNext を呼ぶとデータをすっ飛ばしてしまう、ということでした。その通りなのでそのうち直します。
Scala で Python の Generator 関数みたいなものが欲しかったので試しに作ってみた。
Generator 関数
Python における generator 関数とは、協調スレッドと呼ばれるものの一種で、処理の途中で値を返すことのできる関数、といった感じである。
def generatorTest(n): for i in range(n): yield i for i in generatorTest(10): print i
と、このように関数の途中で yield 文を使用することで値を返し、返した値があたかも iterable であるかのように振る舞う、というものである。
この例ではあまり意味がないが、 generatorTest の結果 1 から 9 までの値が返される。
こういうものがあると、 Twitter のメッセージ取得の際の複数回呼び出しなどを一回の Iteration に抽象化できるので、使う側としては非常に扱いやすくなる。
こんなことが scala でできたらいいなと思ったので作ってみた。
Generator Object
とりあえずソース
import scala.actors object StopIteration class Yielder[T](postto: actors.Actor) { def ! (v: T) { postto ! v } } class Generator[T](genfunc: (Yielder[T])=>Unit) extends Iterator[T] { private val self = actors.Actor.self private var finished = false private val actor = actors.Actor.actor { val msger = new Yielder[T](this.self) genfunc(msger) this.self ! StopIteration } private var nextValue: Option[T] = None def iterate() { this.nextValue = this.self.receive { case StopIteration => { this.finished = true None } case v: T => Some(v) case x => None } } def hasNext() = { if (this.finished) { false } else { this.iterate this.nextValue != None } } def next() = this.nextValue.get } object Generator { def apply[T](func: Yielder[T]=>Unit) = new Generator[T](func) }
で、使い方
val gen = Generator[Int]{x=> 0 to 10 foreach {y=> x ! y}} println(gen.toList)
Generator object の apply に引数を一つとる関数を渡すと Generator が作られる。これが Iterable になっているので、あとは collection と同様に扱える。
中身は actor を使って協調スレッドっぽいことをしているだけなので大したことはしていない、はず。
Iterable[T] は next と hasNext メソッドを定義してあげれば良いだけなので、これは非常に役に立つテクニック。
これからもどんどん使えそう。
気になるところ
・ receive するスレッドのメッセージプールに既にメッセージが溜まっていたらどうなるか
・ apply の型引数を明示しない方法があるかどうか