我正在通过RúnarBjarnason阅读本文Stackless Scala with Free Monad来学习Scala中的蹦床技巧。但我在第4.3节“容易出错”中陷入困境。
有一件事让我感到困惑的是f(x)
如何在给定FlatMap(x, f)
的情况下直接触发另一个内部调用。 resume
已经是尾递归,所以它必须在一个resume
调用中发生。但是resume
中的所有包装函数都应该生成Trampoline实例。所以我找不到堆栈可能仍然被炸毁的情况。
任何人都可以举一个例子来解释“角落案例”吗?感谢。
=============
如果你没有看过这篇很棒的论文,这就是背景资料:
Scala编译器只能在本地/最终尾部位置自调用函数上应用TCO。因此Trampoline
用于将堆栈转换为堆。因此,在本文中,针对不同的用法声明了Trampoline
的三个实例。 Done
用于包装最终结果。 More
表示计算的下一步。并且FlatMap
表示在下一步计算之后还有一件事要做。因此,在为其定义bind
操作后,Trampoline
成为Monad。请参阅下面的代码(来自论文):
```
sealed trait Trampoline[+A] {
final def resume: A = this match {
case Done(v) => v
case More(k) => k().resume
case FlatMap(a, f) => a match {
case Done(v) => f(v).resume
case More(k) => (FlatMap(k(), f): Trampoline[A]).resume
case FlatMap(b, g) => (FlatMap(b, (x: Any) => FlatMap(g(x), f)): Trampoline[A]).resume
}
}
}
case class More[+A](k: () => Trampoline[A])
extends Trampoline[A]
case class Done[+A](v: A)
extends Trampoline[A]
case class FlatMap[A, +B](sub: Trampoline[A], k: A => Trampoline[B]) extends Trampoline[B]
```
一切都有意义,直到它说嵌套的FlatMap
仍然可以炸毁堆栈。这就是我在这里问的原因。
答案 0 :(得分:0)
它给出了答案:
此处还有一个需要考虑的案例。现在可以了 如果FlatMaps的左倾塔是,则恢复溢出堆栈 比调用堆栈高。然后呼叫f(v)将进行呼叫 g(x),它将进行另一次内部调用等......
注意构造函数FlatMap (b , x => FlatMap (g( x), f ))
立即调用函数