0Pricing
Scala for Backend Engineering & Functional Programming · 课时

蹦床调用

栈安全的递归

蹦床调用 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

@tailrec 的局限

@tailrec 只会优化直接调用自身的函数。它无法处理相互递归(两个函数相互调用),这种递归仍然会增长栈。蹦床机制可以解决这个问题。

相互递归问题

考虑一下相互定义的 isEven 和 isOdd。对于较大的数字,这种写法会导致栈溢出,并且两者都不能标记为 @tailrec。

object Main {
  def isEven(n: Int): Boolean = if (n == 0) true else isOdd(n - 1)
  def isOdd(n: Int): Boolean  = if (n == 0) false else isEven(n - 1)

  def main(args: Array[String]): Unit = {
    println(isEven(10))
  }
}

什么是蹦床机制

蹦床机制会将递归调用转换为数据。函数不再调用自身,而是返回对下一步操作的描述。驱动循环会反复执行这些步骤,使栈保持平坦。

标准库中的 TailRec

Scala 提供了包含 TailRec 类型的 scala.util.control.TailCalls。使用 done(x) 表示最终结果,使用 tailcall(...) 延迟下一次调用。

import scala.util.control.TailCalls._

object Main {
  def isEven(n: Int): TailRec[Boolean] =
    if (n == 0) done(true) else tailcall(isOdd(n - 1))
  def isOdd(n: Int): TailRec[Boolean] =
    if (n == 0) done(false) else tailcall(isEven(n - 1))

  def main(args: Array[String]): Unit = {
    println(isEven(100000).result)
  }
}

done 与 tailcall

两个构建块:

  • done(value) 包装最终答案。
  • tailcall(expr) 延迟一个返回 TailRec 的调用。

调用 .result 会运行蹦床循环并产生结果值。

栈安全性

由于每次 tailcall 都会将控制权交还给驱动循环,而不是嵌套 Java 调用,JVM 栈不会随着递归深度增长。上面的示例可以处理 100,000 个步骤而不会溢出。

对自身递归使用蹦床机制

当您不容易使用累加器时,蹦床机制也适用于普通的深层自身递归。这里的深度倒计时保持了栈安全性。

import scala.util.control.TailCalls._

object Main {
  def countDown(n: Int): TailRec[Int] =
    if (n == 0) done(0) else tailcall(countDown(n - 1))

  def main(args: Array[String]): Unit = {
    println(countDown(500000).result)
  }
}

使用 flatMap 组合结果

TailRec 支持 map 和 flatMap,因此您可以在延迟调用之后执行操作,同时保持栈安全性。

import scala.util.control.TailCalls._

object Main {
  def sum(n: Int): TailRec[Int] =
    if (n == 0) done(0)
    else tailcall(sum(n - 1)).map(_ + n)

  def main(args: Array[String]): Unit = {
    println(sum(100000).result)
  }
}

驱动循环的工作原理

从概念上说,.result 会运行一个循环:获取当前步骤;如果它是 done,就返回其值;如果它是延迟调用,就执行一层并继续。整个过程只使用固定的栈空间。

效果库中的蹦床机制

Cats Effect 和 ZIO 等库会在内部为它们的 flatMap 链使用蹦床机制,因此您可以构建深度嵌套的效果程序而不会发生栈溢出。蹦床机制是栈安全函数式效果的基础。

何时使用蹦床机制

在以下情况下使用蹦床:

  • 存在无法写成单个尾递归函数的相互递归。
  • 递归层次太深,会耗尽栈空间,并且不适合使用累加器。

对于简单的自身递归,请优先使用带累加器的 @tailrec。

快速检查

检查您对蹦床机制的理解。

回顾

您学习了蹦床机制:

  • 它使相互递归和非常深的递归能够安全地使用栈。
  • 使用 TailCalls:done(x) 和 tailcall(...),然后调用 .result。
  • TailRec 支持 map/flatMap。
  • 它是栈安全效果库的基础。

常见问题解答

「蹦床调用」课时是免费的吗?

是的 — 「蹦床调用」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

「蹦床调用」这节课中我会学到什么?

栈安全的递归 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「蹦床调用」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?

能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 递归基础
  2. tailrec 注解
  3. 累加器模式
  4. 蹦床调用
← 返回 Scala for Backend Engineering & Functional Programming