序章
私は多くのプロジェクトで Scalaz 7の iteratees を使用していますが、主に大規模なファイルを処理するためです。iteratee パッケージを置き換えるように設計された Scalazストリームへの切り替えを開始したいと思います (率直に言って、多くの部分が欠落しており、使用するのが面倒です)。
ストリームは、Haskell にも実装されているマシン(繰り返しのアイデアの別のバリエーション) に基づいています。私は Haskell マシン ライブラリを少し使用しましたが、マシンとストリームの関係は (少なくとも私には) 完全には明らかではなく、ストリーム ライブラリのドキュメントはまだ少しまばらです。
この質問は、反復の代わりにストリームを使用して実装したい単純な解析タスクに関するものです。誰も私を打ち負かすことができなければ、私は自分で質問に答えますが、この移行を行っている (または少なくとも検討している) のは私だけではないと確信しています。公の場でやったほうがいいと思った。
仕事
トークン化され、品詞でタグ付けされた文を含むファイルがあるとします。
no UH
, ,
it PRP
was VBD
n't RB
monday NNP
. .
the DT
equity NN
market NN
was VBD
illiquid JJ
. .
1 行に 1 つのトークンがあり、単語と品詞は 1 つのスペースで区切られ、空白行は文の境界を表します。このファイルを解析して、文のリストを返したいと思います。これは、文字列のタプルのリストとして表すこともできます。
List((no,UH), (,,,), (it,PRP), (was,VBD), (n't,RB), (monday,NNP), (.,.))
List((the,DT), (equity,NN), (market,NN), (was,VBD), (illiquid,JJ), (.,.)
いつものように、無効な入力やファイル読み取りの例外が発生した場合に正常に失敗したり、手動でリソースを閉じることを心配したりしたくありません。
反復ソリューション
最初に、いくつかの一般的なファイル読み取り用のもの (実際には iteratee パッケージの一部である必要があります。現在、この高レベルのものはリモートで提供されていません):
import java.io.{ BufferedReader, File, FileReader }
import scalaz._, Scalaz._, effect.IO
import iteratee.{ Iteratee => I, _ }
type ErrorOr[A] = EitherT[IO, Throwable, A]
def tryIO[A, B](action: IO[B]) = I.iterateeT[A, ErrorOr, B](
EitherT(action.catchLeft).map(I.sdone(_, I.emptyInput))
)
def enumBuffered(r: => BufferedReader) = new EnumeratorT[String, ErrorOr] {
lazy val reader = r
def apply[A] = (s: StepT[String, ErrorOr, A]) => s.mapCont(k =>
tryIO(IO(Option(reader.readLine))).flatMap {
case None => s.pointI
case Some(line) => k(I.elInput(line)) >>== apply[A]
}
)
}
def enumFile(f: File) = new EnumeratorT[String, ErrorOr] {
def apply[A] = (s: StepT[String, ErrorOr, A]) => tryIO(
IO(new BufferedReader(new FileReader(f)))
).flatMap(reader => I.iterateeT[String, ErrorOr, A](
EitherT(
enumBuffered(reader).apply(s).value.run.ensuring(IO(reader.close()))
)
))
}
そして、センテンス リーダー:
def sentence: IterateeT[String, ErrorOr, List[(String, String)]] = {
import I._
def loop(acc: List[(String, String)])(s: Input[String]):
IterateeT[String, ErrorOr, List[(String, String)]] = s(
el = _.trim.split(" ") match {
case Array(form, pos) => cont(loop(acc :+ (form, pos)))
case Array("") => cont(done(acc, _))
case pieces =>
val throwable: Throwable = new Exception(
"Invalid line: %s!".format(pieces.mkString(" "))
)
val error: ErrorOr[List[(String, String)]] = EitherT.left(
throwable.point[IO]
)
IterateeT.IterateeTMonadTrans[String].liftM(error)
},
empty = cont(loop(acc)),
eof = done(acc, eofInput)
)
cont(loop(Nil))
}
そして最後に、解析アクション:
val action =
I.consume[List[(String, String)], ErrorOr, List] %=
sentence.sequenceI &=
enumFile(new File("example.txt"))
それが機能することを実証できます。
scala> action.run.run.unsafePerformIO().foreach(_.foreach(println))
List((no,UH), (,,,), (it,PRP), (was,VBD), (n't,RB), (monday,NNP), (.,.))
List((the,DT), (equity,NN), (market,NN), (was,VBD), (illiquid,JJ), (.,.))
これで完了です。
私が欲しいもの
iteratee の代わりに Scalaz ストリームを使用して実装されたほぼ同じプログラム。