2012年6月1日金曜日

Scalaz7でIterateeとIO

いつものようにTwitterでScalazを検索すると


この様なつぶやきが。

コードはこんな感じ。

import java.io.{ BufferedReader, FileReader }
import scalaz._, Scalaz._
import scalaz.effects._
import IterV._
object App {
def main(args: Array[String]) {
val br = new BufferedReader(new FileReader("test2.txt"))
val io = byEnumerator(br)
io.unsafePerformIO
}
def byEnumerator(br: BufferedReader): IO[Unit] = {
val lineIter: IterV[String, Int] = head[String] map {
lineOpt => (lineOpt map (_.length)) | 0
}
val iter: IterV[String, Stream[Int]] = repeat[String, Int, Stream](lineIter)
val enumerator: EnumeratorM[IO, String] = getReaderLines(br)
val read: IO[Int] = enumerator(iter) map { iter =>
iter.run reduce (_ + _)
}
read map (_.toString) flatMap (putStrLn _)
}
}
view raw App.scala hosted with ❤ by GitHub

fmfm.
先頭行をとって長さをとって行数分ぬりつぶして合計をとっているようですね。

このコードはScalaz6なのですが、Scalaz7でも書けるかなーと思い、ちょっと書いてみました。

import java.io.{ Reader, FileReader }
import scalaz._, Scalaz._
import effect._, IO._
import iteratee._, Iteratee._
object App extends SafeApp{
val separator = sys.props("line.separator").head
override def run(args: ImmutableArray[String]): IO[Unit] = {
val r = new FileReader("test2.txt")
byEnumerator(r)
}
type IEOC = IoExceptionOr[Char]
def byEnumerator(r: Reader): IO[Unit] = {
val lineIter = takeWhile[IEOC, Stream](_ map (_ =/= separator) valueOr false) map (_.length)
val iter = repeatBuild[IEOC, Int, Stream](lineIter).up[IO] map (_.sum)
val enum = enumReader(r)
val read = iter &= enum
(read.run >>= putOut) >> putStrLn("")
}
}
view raw App.scala hosted with ❤ by GitHub

違いを1つ1つみていきます。

まず、Scalaz7ではgetReaderLinesがないので、BufferedReaderではなくReaderを使うようにしました。
なので、1行ずつではなく1文字ずつしかみれなくなります。
そこで、改行コードをみるようにします。
改行コードはシステムプロパティーのline.separatorで取得できるとのことなので、sys.propsから取得します。

もとのコードでは、先頭行を取得する為にheadを使っていますが、このコードではtakeWhileを使います。
ここでは、述語として改行コードかどうかの判定をとります。

さて、肝心のIterateeの要素ですが、IoExceptionOr[Char]となっています。
これはその名の通り、IoExceptionとの和で、どちらかが内包されています。
ここではvalueOrを使い、IoExceptionだった場合の値を決め、値を取得しています。

repeatBuildはScalaz6のscalaz.IterV.repeatと同じです。
on[IO]は、Iteratee[E, A]から、IterateeT[E, IO, A]に変換しています。

enumReaderはReaderからEnumeratorを作り、&=はEnumeratorをIterateeに流し込みます。

最後に実行の部分ですが、mainを定義しているわけではありません。
SafeAppはunsafePerformIOを明示的に呼び出さない仕組みで、runをオーバーライドすることで実行できます。

・・・・・とまあこんな感じなのですが、Scalaz6と比べて、かなり違うことが分かりますね。
とくにIteratee。
IterVとIterateeで別々のものになっていましたが、いまはIterateeTのみで大分使いやすくなった気がします。(Iteratee[E, A]はIteratee[E, Id, A]の別名)
ただ、enumReaderLinesのようなものは欲しかった。

IOはMonadIOとかMonadControlIOとかいろいろ高機能になっているのですが、基本的な機能が少ないので、相変わらず残念です。
せめてSourceとReaderとWriterのインスタンスくらい作ってくれればいいのに。
あと入出力系。

超便利とまでは言えないけれど、将来性はありそうなiterateeとeffect。
一度さわってみてはどうでしょう?

0 件のコメント:

コメントを投稿