Scalaz 7のiterateeライブラリjava.io.BufferedReader
を使用して、ファイルを1行ずつ読み取るための列挙子を作成しようとしています。このライブラリは、現在、の(非常に遅い)列挙子のみを提供します。java.io.Reader
私が遭遇している問題は、私が使用した他のすべてのiterateeライブラリ(たとえば、Play2.0やHaskellのJohnMillikin)が、その型のコンストラクターの1つとしてエラー状態になっているという事実とScalaz7に関連していenumerator
Step
ます。そうではありません。
私の現在の実装
これが私が現在持っているものです。まず、いくつかのインポートとIO
ラッパーについて:
import java.io.{ BufferedReader, File, FileReader }
import scalaz._, Scalaz._, effect.IO, iteratee.{ Iteratee => I, _ }
def openFile(f: File) = IO(new BufferedReader(new FileReader(f)))
def readLine(r: BufferedReader) = IO(Option(r.readLine))
def closeReader(r: BufferedReader) = IO(r.close())
そして、物事を少しクリーンアップするためのタイプエイリアス:
type ErrorOr[A] = Either[Throwable, A]
そして今、tryIO
ヘルパーは、次のものをモデルにしています(大まかに、そしておそらく間違っています)enumerator
:
def tryIO[A, B](action: IO[B]) = I.iterateeT[A, IO, ErrorOr[B]](
action.catchLeft.map(
r => I.sdone(r, r.fold(_ => I.eofInput, _ => I.emptyInput))
)
)
BufferedReader
それ自体の列挙子:
def enumBuffered(r: => BufferedReader) = new EnumeratorT[ErrorOr[String], IO] {
lazy val reader = r
def apply[A] = (s: StepT[ErrorOr[String], IO, A]) => s.mapCont(k =>
tryIO(readLine(reader)) flatMap {
case Right(None) => s.pointI
case Right(Some(line)) => k(I.elInput(Right(line))) >>== apply[A]
case Left(e) => k(I.elInput(Left(e)))
}
)
}
そして最後に、リーダーの開閉を担当する列挙子:
def enumFile(f: File) = new EnumeratorT[ErrorOr[String], IO] {
def apply[A] = (s: StepT[ErrorOr[String], IO, A]) => s.mapCont(k =>
tryIO(openFile(f)) flatMap {
case Right(reader) => I.iterateeT(
enumBuffered(reader).apply(s).value.ensuring(closeReader(reader))
)
case Left(e) => k(I.elInput(Left(e)))
}
)
}
たとえば、少なくとも25'0'
文字を含むファイル内のすべての行をリストにまとめたいとします。私は書くことができます:
val action: IO[ErrorOr[List[String]]] = (
I.consume[ErrorOr[String], IO, List] %=
I.filter(_.fold(_ => true, _.count(_ == '0') >= 25)) &=
enumFile(new File("big.txt"))
).run.map(_.sequence)
多くの点で、これは見事に機能しているように見えます。アクションを開始するunsafePerformIO
と、数千万行とギガバイトのデータを数分で、一定のメモリ内で、スタックを爆破することなくチャンクし、リーダーを閉じます。終わったら。存在しないファイルの名前を付けると、。でラップされた例外が忠実に返され、読み取り中に例外が発生した場合でもLeft
、enumBuffered
少なくとも適切に動作するように見えます。
潜在的な問題
ただし、特に。の実装について懸念がありtryIO
ます。たとえば、いくつかの反復を作成しようとしたとします。
val it = for {
_ <- tryIO[Unit, Unit](IO(println("a")))
_ <- tryIO[Unit, Unit](IO(throw new Exception("!")))
r <- tryIO[Unit, Unit](IO(println("b")))
} yield r
これを実行すると、次のようになります。
scala> it.run.unsafePerformIO()
a
b
res11: ErrorOr[Unit] = Right(())
GHCiで同じことを試してみるとenumerator
、結果は私が期待するものに似ています。
...> run $ tryIO (putStrLn "a") >> tryIO (error "!") >> tryIO (putStrLn "b")
a
Left !
iterateeライブラリ自体にエラー状態がない状態でこの動作を取得する方法がわかりません。
私の質問
私は反復の専門家であるとは言いませんが、いくつかのプロジェクトでさまざまなHaskellの実装を使用し、基本的な概念を多かれ少なかれ理解しているように感じ、Olegと一度コーヒーを飲みました。しかし、私はここで途方に暮れています。これは、エラー状態がない場合に例外を処理するための合理的な方法ですか?バージョンtryIO
のように動作する実装方法はありますか?enumerator
私の実装の動作が異なるという事実で、私を待っているある種の時限爆弾はありますか?