理解惰性求值
仅在需要时计算值。
理解惰性求值 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
什么是惰性?
在 Scala 中,大多数值都是严格求值的:定义它们的瞬间就会计算。惰性求值则相反。惰性值只有在第一次需要时才会计算,在此之前绝不会计算。
当计算开销很大、可能失败,或根本不会被使用时,这一点非常重要。没人要求的工作,为什么要提前付出代价呢?
严格的 val
普通的 val 会立即计算其右侧表达式,即使您之后从未再次读取它。
运行这段代码,并注意 “computing” 会在 “after” 之前打印。工作发生在定义时。
object Demo extends App {
val x = { println("computing"); 42 }
println("after")
println(x)
}lazy val
在 val 前加上 lazy,即可将计算延迟到第一次访问时。
这里会先打印 “after”,只有读取 x 时才打印 “computing”。结果还会被缓存:主体最多只运行一次。
object Demo extends App {
lazy val x = { println("computing"); 42 }
println("after")
println(x)
println(x)
}按名称传递的参数
声明为 => A 的参数会按名称传递:参数只有在方法内部被使用时才会计算。
这是实现惰性求值的基础。LazyList 使用按名称传递的参数,因此只有在请求尾部时才会计算尾部。
def maybe(cond: Boolean, value: => Int): Int =
if (cond) value else 0
val r = maybe(false, { println("side"); 99 })
// "side" never prints; value was unused认识 LazyList
LazyList 是 Scala 的惰性序列(在 2.13 及更高版本中取代了旧的 Stream)。它的头部会立即计算,但尾部是惰性的。
元素会按需生成,并在计算后缓存,因此同一个单元不会被重复计算。
val ll = LazyList(1, 2, 3)
println(ll)
// prints LazyList(<not computed>) until forced强制求值
LazyList 会保持未求值状态,直到您强制求值。调用 toList、foreach,或打印每个元素,都会强制计算这些单元。
REPL 会用 <not computed> 表示尚未计算的惰性尾部。强制求值后才能看到实际值。
object Demo extends App {
val ll = LazyList(1, 2, 3)
println(ll.toList)
}#:: 运算符
您可以使用 #:: 将元素添加到 LazyList 的头部。与列表的 :: 不同,#:: 的右操作数按名称传递,因此尾部要到需要时才会构建。
这样,您就可以描述尾部会惰性计算的序列。
val ll = 1 #:: 2 #:: 3 #:: LazyList.empty
println(ll.head)记忆化
LazyList 单元一旦计算完成,其值就会被存储。再次读取时无需付出计算代价,生成该值的表达式也不会再次运行。
运行这段代码:每个被强制计算的元素只会在第一次访问时打印 “calc”,重复读取时不会打印。
object Demo extends App {
val ll = LazyList(1, 2).map { n =>
println("calc " + n); n * 10
}
println(ll.head)
println(ll.head)
}惰性尾部的实际效果
由于尾部是惰性的,您可以对 LazyList 使用 map,只有实际访问的元素才会被计算。
这里即使列表有三个元素,“map” 也只会打印一次,因为只有头部被强制计算。
object Demo extends App {
val r = LazyList(1, 2, 3).map { n =>
println("map " + n); n + 1
}
println(r.head)
}惰性求值的优点
惰性求值带来三个好处:避免无用的工作、构建无限序列,以及将计算的描述方式与实际执行的数量解耦。
您只需编写一次完整的处理流程;使用者决定要提取多少个元素。
注意事项
惰性求值也有代价。LazyList 会进行记忆化,因此持有其头部的引用会让所有已强制计算的元素都留在内存中。对于巨大或无限的数据,请避免保留头部引用。
此外,惰性代码中的副作用可能在意料之外的时间运行,从而给调试带来困惑。
快速检查
请检验您对惰性求值的理解。
回顾
严格求值会立即执行工作;惰性求值会将工作延迟到需要时。lazy val 和按名称传递的参数(=> A)是实现这一机制的基本构件。
LazyList 建立在这些机制之上:头部立即计算,尾部惰性计算并进行记忆化,可通过 toList 或访问元素来强制求值。接下来,我们将手动构建 LazyList。
常见问题解答
「理解惰性求值」课时是免费的吗?
是的 — 「理解惰性求值」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 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 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「理解惰性求值」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?
能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 理解惰性求值
- 构建 LazyList
- 无限流
- 惰性获取与过滤