2011年9月6日火曜日

ScalazでIteratee その2

前の続き。

実際にファイルを読み込めるようにしました。

type EnumeratorM[E, M[_], A] = IterV[E, A] => M[IterV[E, A]]
def enumReader[A](r: BufferedReader): EnumeratorM[String, IO, A] = { iter =>
def loop: EnumeratorM[String, IO, A] = {
case i @ Done(_, _) => (i: IterV[String, A]).pure[IO]
case i @ Cont(k) => for {
o <- rReadLn(r)
i <- o match {
case None => i.pure[IO]
case Some(s) => s.pure[IO] >>= loop.compose(k.compose(El(_)))
}
} yield i
}
loop(iter)
}
def enumFile[A](f: File): EnumeratorM[String, IO, A] = i => bufferFile(f).bracket(closeReader)(enumReader[A](_)(i))
view raw f.scala hosted with ❤ by GitHub

EnumeratorMはIterVをモナドで包むための型です。

enumReaderはブログの例とは違いますが、一字ずつ読み込むのではなく、BufferReaderを用いて一行づつ読み込むようにしています。
<<<はScalazで定義されており、composeと同じです。
enumと同様に、Doneになるまで処理を繰り返します。

enumFileはファイルをenumReaderに渡す。
bracketにより、closeReaderが確実に行われることが保証されています。

実行例
val file = new File("src/main/resources/test.txt")
file.exists assert_=== true
def g[A](i: IterV[String, A]) = run(enumFile(file)(i).unsafePerformIO)
g(length) assert_=== Some(1)
g(peek[String] >|> head[String]) assert_=== Some(Some("hello"))
view raw g.scala hosted with ❤ by GitHub
hello
view raw test.txt hosted with ❤ by GitHub

現在、scalaz.effectsにあるIterateeな入出力関数はこれらと、標準入力からの読み込みだけです。

2011年9月4日日曜日

ScalazでIteratee

Scala会議でLTしてきました。


拙い発表でしたが、まじめに聞いてくれたScala会議参加者の方々には感謝です。
このLTで話せなかったIterateeについて、tanakhさんのブログを参考に書かせていただきます。

Scalazでの実装

ブログを参考に実際に実装してみます。

sealed trait StreamG[+E]
case object Empty extends StreamG[Nothing]
case class El[E](el: E) extends StreamG[E]
case object EOF extends StreamG[Nothing]
sealed trait IterV[+E, +A]
case class Done[E, A](x: A, str: StreamG[E]) extends IterV[E, A]
case class Cont[E, A](k: StreamG[E] => IterV[E, A]) extends IterV[E, A]
view raw a.scala hosted with ❤ by GitHub


定義は簡単。
Haskellのコードとあまり変わりがないですね。(ぇ
ストリームはList(El(1), El(2), El(3), EOF())みたいなイメージです。

def enum[E, A]: (IterV[E, A], List[E]) => IterV[E, A] = {
case (i, Nil) => i
case (i@Done(_, _), _) => i
case (Cont(k), x :: xs) => enum(k(El(x)), xs)
}
def run[E, A]: IterV[E, A] => Option[A] = {
case Done(x, _) => Some(x)
case Cont(k) => k(EOF) match {
case Done(x, _) => Some(x)
case _ => None
}
}
view raw b.scala hosted with ❤ by GitHub


enum,runの実装。
enumはfoldl(むしろreduce?)そのままで、IterVを関数と初期値にみたたて、Doneの場合でも結果を返すものと思えばいいです。
runは結果を取り出すだけ。初期値に当たるものがない場合があるので、結果はOptionで返します。

def head[E]: IterV[E, Option[E]] = {
def step: StreamG[E] => IterV[E, Option[E]] = {
case El(el) => Done(Some(el), Empty)
case Empty => Cont(step)
case EOF => Done(None, EOF)
}
Cont(step)
}
def peek[E]: IterV[E, Option[E]] = {
def step: StreamG[E] => IterV[E, Option[E]] = {
case c@El(el) => Done(Some(el), c)
case Empty => Cont(step)
case EOF => Done(None, EOF)
}
Cont(step)
}
def drop[E]: Int => IterV[E, Unit] = {
case 0 => Done((), Empty)
case n => {
def step: StreamG[E] => IterV[E, Unit] = {
case El(_) => drop(n - 1)
case Empty => Cont(step)
case EOF => Done((), EOF)
}
Cont(step)
}
}
def length[E]: IterV[E, Int] = {
def step: (Int, StreamG[E]) => IterV[E, Int] = {
case (acc, El(_)) => Cont(step.curried(acc + 1))
case (acc, Empty) => Cont(step.curried(acc))
case (acc, EOF) => Done(acc, EOF)
}
Cont(step.curried(0))
}
view raw c.scala hosted with ❤ by GitHub


Iterateeたち。
これらをストリームに適用し、結果を得ます。curriedとか初めて使った気がします。
型が消えてしまうので、match式のところはwarningがいっぱいです。
この対処法がないのが残念なところ。(あったら教えてください

implicit def IterVMonad[E] = new Monad[({type X[A] = IterV[E, A]})#X] {
def pure[A](x: => A): IterV[E, A] = Done(x, Empty)
def bind[A, B](m: IterV[E, A], f: A => IterV[E, B]): IterV[E, B] = m match {
case Done(x, str) => f(x) match {
case Done(x, _) => Done(x, str)
case Cont(k) => k(str)
}
case Cont(k) => Cont((str: StreamG[E]) => bind(k(str), f))
}
}
view raw d.scala hosted with ❤ by GitHub


モナド。
PartialApplyを使おうとして嵌りました。定義のところで使っちゃダメなんですね・・・・
このインスタンスを作ることで、合成(>>=や>|>)を使うことができます。
Haskellと違って、Monadを定義すればFunctorやApplicativeがついてくるのでちょっと楽。

実行例

def f[E](l: List[E]) = run(enum(drop(2) >|> length, l))
f(List(1, 2, 3)) assert_=== Some(1)
f(List(1, 2, 3, 4, 5)) assert_=== Some(3)
f(List(1)) assert_=== Some(0)
view raw e.scala hosted with ❤ by GitHub


Haskellのコードそのままに書けるのは感動ですね。
次はIOを実装しようと思います。