-->

如何使用IO与Scalaz7 Iteratees不会溢出堆栈?(How to use IO with

2019-08-31 17:50发布

考虑下面的代码(取自这里和修改以使用字节,而不是字符行)。

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。 一些搜索打开了该异常可以通过使用蹦床单子,而不是IO是可以避免的,但是这似乎并不像一个很好的解决方案 - 牺牲功能纯度让程序在所有完成。 解决这个问题最显而易见的方法是使用IO或蹦床作为一个单子转换包住对方,但我无法找到的任一方的变压器版本的实现,我没有足够的功能,编程高手来知道怎么写我自己的(了解更多关于FP是这个项目的目的之一,但我怀疑创造新的单子变压器是有点以上我目前的水平)。 我想我可能只是环绕创建,运行和返回我iteratees结果的大IO动作,但感觉更多的是解决办法比的解决方案。

想必一些单子不能转换到单子变压器,所以我想知道是否有可能与大文件的工作而不会丢失IO或溢出堆栈,如果是这样,怎么样?

奖金的问题:我想不出任何方式的iteratee信号,它的时出错处理,除了有它在回报,这使得它不太容易撰写他们。 上面的代码演示了如何使用EitherT来处理枚举的错误,而是如何做的iteratees工作?

Answer 1:

创建异常并打印在不同的地方你的代码的程序堆栈长度之后,我觉得你的代码是不是溢出。 一切似乎都在不断的堆栈大小运行。 所以我找了别的地方。 最后,我复制的执行consume和增加了一些堆栈深度印刷并确认其溢出那里。

所以这个溢出:

(I.consume[Int, Id, List] &= EnumeratorT.enumStream(Stream.fill(10000)(1))).run

但是 ,我然后发现,这并不:

(I.putStrTo[Int](System.out) &= EnumeratorT.enumStream(Stream.fill(10000)(1)))
  .run.unsafePerformIO()

putStrTo使用foldM并以某种方式不会导致溢出。 所以,我想知道是否consume可以在以下方面实现foldM 。 我刚刚从消费复制的几件事情在和调整,直到它编译:

def consume1[E, F[_]:Monad, A[_]:PlusEmpty:Applicative]: IterateeT[E, F, A[E]] = {
  I.foldM[E, F, A[E]](PlusEmpty[A].empty){ (acc: A[E], e: E) =>
    (Applicative[A].point(e) <+> acc).point[F]
  }
}

和它的工作! 打印整数一长串。



文章来源: How to use IO with Scalaz7 Iteratees without overflowing the stack?