非Trampolined IO Monad为何触发StackOverflowError?手动推演存疑
为什么手动推演
IO.forever的执行过程时,看不到栈溢出的原因? 我最近在学习《Functional Programming in Scala》,看到第13章「外部效应与I/O」的13.3节「避免StackOverflowError」(第237页)时,遇到了一个困惑:书中的示例代码IO.forever(IO { println("Still going...") }).run会触发StackOverflowError,但我手动模拟Scala运行时展开求值表达式时,却始终看不到调用栈堆积的情况。
核心代码定义
先给大家看下相关的核心实现:forever的定义很直观:
def forever[A,B](a: F[A]): F[B] = { lazy val t: F[B] = a.flatMap(_ => t) t }
对应书中的简单IO特质及flatMap实现大致是这样:
trait IO[+A] { def run: A def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run } } object IO { def apply[A](a: => A): IO[A] = new IO[A] { def run = a } }
我的手动推演过程
我一步步展开表达式,试图还原执行流程:
- 初始调用:
IO.forever(IO { println("Still going...") }).run forever返回的是lazy val t,所以第一步等价于t.run- 而
t的定义是a.flatMap(_ => t),替换后得到(a.flatMap(_ => t)).run - 展开
flatMap的实现:new IO[Unit] { def run = (_ => t)(self.run).run }.run - 执行匿名
IO的run方法:先调用self.run(也就是a.run),这会打印"Still going..."并返回() - 接着执行
(_ => t)(()).run,也就是t.run - 流程回到步骤2,进入循环
按照这个推演,看起来应该是无限循环,但不会堆积调用栈啊?可实际运行却会触发栈溢出,我的推演哪里出错了?是不是Scala对lazy val t的求值时机和我脑中模拟的不一样?
内容的提问来源于stack exchange,提问作者mgryszko
相关产品推荐
相关产品推荐

