通过阅读Rúnar Bjarnason的这篇论文Stackless Scala with Free Monad ,我正在学习 Scala 中的 Trampoline 技巧。但我陷入了第 4.3 节“容易出错的事情”。
有一件事让我感到困惑,a 如何在f(x)
给定 a 的情况下直接触发另一个内部调用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
仍然可以炸毁堆栈。这就是我在这里问它的原因。