首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在不溢出堆栈的情况下将IO与Scalaz7迭代器一起使用?

如何在不溢出堆栈的情况下将IO与Scalaz7迭代器一起使用?
EN

Stack Overflow用户
提问于 2013-04-22 09:26:49
回答 1查看 593关注 0票数 11

考虑这段代码(取自here,并修改为使用字节而不是字符行)。

代码语言:javascript
复制
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 transformer目前有点超出我的水平)。我想我可以围绕创建、运行和返回迭代器的结果来包装一个大的IO操作,但这感觉更像是一种变通而不是解决方案。

大概有些monad无法转换为monad转换器,所以我想知道是否有可能在不丢弃IO或使堆栈溢出的情况下处理大文件,如果可能,如何进行转换?

额外的问题:我想不出任何方法让迭代者在处理过程中遇到错误,除了让它返回之外,这使得编写它们变得不那么容易。上面的代码展示了如何使用EitherT来处理枚举器中的错误,但是这对于迭代器是如何工作的呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-04-25 21:52:30

在创建异常并在代码的不同位置打印它们的堆栈长度之后,我觉得您的代码并没有溢出。所有这些似乎都以恒定的堆栈大小运行。所以我找了别的地方。最后,我复制了consume的实现,并添加了一些堆栈深度打印,并确认它在那里溢出。

所以这会溢出:

代码语言:javascript
复制
(I.consume[Int, Id, List] &= EnumeratorT.enumStream(Stream.fill(10000)(1))).run

但是,我随后发现这不是:

代码语言:javascript
复制
(I.putStrTo[Int](System.out) &= EnumeratorT.enumStream(Stream.fill(10000)(1)))
  .run.unsafePerformIO()

putStrTo使用foldM并且以某种方式不会导致溢出。所以我想知道是否可以用foldM来实现consume。我只是从消费中复制了一些东西,并进行了调整,直到它被编译:

代码语言:javascript
复制
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]
  }
}

它成功了!打印一个很长的int列表。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16138096

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档