考虑这段代码(取自here并修改为使用字节而不是字符行)。
import java.io.{ File, InputStream, BufferedInputStream, FileInputStream }
import scalaz._, Scalaz._, effect._, iteratee.{ Iteratee => I, _ }
import std.list._
object IterateeIOExample {
type ErrorOr[+A] = EitherT[IO, Throwable, A]
def openStream(f: File) = IO(new BufferedInputStream(new FileInputStream(f)))
def readByte(s: InputStream) = IO(Some(s.read()).filter(_ != -1))
def closeStream(s: InputStream) = IO(s.close())
def tryIO[A, B](action: IO[B]) = I.iterateeT[A, ErrorOr, B] {
EitherT(action.catchLeft).map(r => I.sdone(r, I.emptyInput))
}
def enumBuffered(r: => BufferedInputStream) = new EnumeratorT[Int, ErrorOr] {
lazy val reader = r
def apply[A] = (s: StepT[Int, ErrorOr, A]) => s.mapCont(k =>
tryIO(readByte(reader)) flatMap {
case None => s.pointI
case Some(byte) => k(I.elInput(byte)) >>== apply[A]
})
}
def enumFile(f: File) = new EnumeratorT[Int, ErrorOr] {
def apply[A] = (s: StepT[Int, ErrorOr, A]) =>
tryIO(openStream(f)).flatMap(stream => I.iterateeT[Int, ErrorOr, A](
EitherT(
enumBuffered(stream).apply(s).value.run.ensuring(closeStream(stream)))))
}
def main(args: Array[String]) {
val action = (
I.consume[Int, ErrorOr, List] &=
enumFile(new File(args(0)))).run.run
println(action.unsafePerformIO())
}
}
在大小合适的文件 (8kb) 上运行此代码会产生 StackOverflowException。一些搜索发现,可以通过使用 Trampoline monad 而不是 IO 来避免异常,但这似乎不是一个很好的解决方案 - 牺牲功能纯度来让程序完全完成。解决这个问题的明显方法是使用 IO 或 Trampoline 作为 Monad Transformer 来包装另一个,但我找不到它们中任何一个的 Transformer 版本的实现,而且我还没有足够的功能编程大师来解决这个问题。知道如何编写我自己的(更多地了解 FP 是这个项目的目的之一,但我怀疑创建新的 monad 变压器目前有点高于我的水平)。我想我可以围绕创建、运行和返回迭代结果进行一个大的 IO 操作,但这感觉更像是一种解决方法,而不是解决方案。
大概有些 monad 无法转换为 monad 转换器,所以我想知道是否可以在不丢失 IO 或溢出堆栈的情况下处理大文件,如果可以,如何实现?
额外问题:除了让它返回 Either 之外,我想不出任何方法可以让 iteratee 发出它在处理时遇到错误的信号,这使得组合它们变得不那么容易。上面的代码展示了如何使用 EitherT 来处理枚举器中的错误,但是这对于迭代器来说是如何工作的呢?