无限流
安全地建模无穷数据。
无限流 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
安全地处理无限序列
LazyList 可以描述无限序列,因为只有在需要时才会计算尾部。您实际构建的始终只是所消费的前缀。
这样,您无需预先设定边界,就能表示自然数、素数或传感器读数。
所有自然数
LazyList.from(1) 是无限序列 1、2、3、……。您可以提取任意有限前缀。
强制计算整个序列永远不会结束,因此请始终使用 take 对它进行切片,或使用谓词停止。
object Demo extends App {
val nats = LazyList.from(1)
println(nats.take(5).toList)
}带步长的 from
LazyList.from(start, step) 会按固定间隔计数。您可以用它生成偶数、计时信号或任何等差数列。
该序列是无限的,但每次调用 take 只会强制计算您所请求的部分。
object Demo extends App {
val evens = LazyList.from(0, 2)
println(evens.take(5).toList)
}自引用流
一种著名技巧是:用 LazyList 自身来定义它。斐波那契序列可以通过将一个流与它自己的尾部进行 zip 来表示。
这之所以可行,是因为在提取每个单元之前,尾部始终保持未求值状态。
lazy val fibs: LazyList[Int] =
0 #:: 1 #:: fibs.zip(fibs.tail).map { case (a, b) => a + b }
// fibs(0)=0, fibs(1)=1, fibs(2)=1 ...运行斐波那契序列
让我们强制计算这个自引用斐波那契流的前缀。
每个新元素都根据更早且已经记忆化的元素计算,因此随着提取的值越来越多,整个过程仍然高效。
object Demo extends App {
lazy val fibs: LazyList[Int] =
0 #:: 1 #:: fibs.zip(fibs.tail).map { case (a, b) => a + b }
println(fibs.take(10).toList)
}使用 iterate 生成序列
LazyList.iterate 会通过步进函数生成无限序列。幂、几何增长以及状态机都很适合用它表示。
这里的每个元素永远都是前一个元素的三倍。
object Demo extends App {
val triples = LazyList.iterate(1)(_ * 3)
println(triples.take(6).toList)
}素数筛法
无限流非常适合实现埃拉托斯特尼筛法。取出头部素数,从剩余部分中过滤掉它的倍数,然后递归执行。
filter 是惰性的,因此素数会在您消费时逐个生成。
def sieve(s: LazyList[Int]): LazyList[Int] =
s.head #:: sieve(s.tail.filter(_ % s.head != 0))
val primes = sieve(LazyList.from(2))运行筛法
现在,从这个无限筛法中提取前十个素数。
底层数字流只会被强制计算到足以产生十个素数的程度,这展示了按需计算。
object Demo extends App {
def sieve(s: LazyList[Int]): LazyList[Int] =
s.head #:: sieve(s.tail.filter(_ % s.head != 0))
val primes = sieve(LazyList.from(2))
println(primes.take(10).toList)
}永远不要强制计算整个序列
需要完整序列的方法,例如 length、对无界流调用 toList,或不设置停止条件的 foreach,都会在无限 LazyList 上一直运行。
请始终先使用 take、takeWhile 或 find 设置边界。
// DON'T: LazyList.from(1).toList // hangs forever
val ok = LazyList.from(1).take(3).toList持有头部的陷阱
如果一个 val 持有无限且会记忆化的 LazyList 的头部,并且您一直消费到很后面,那么每个已强制计算的单元都会保持存活,造成内存泄漏。
进行长时间遍历时,请通过不绑定头部的方法来消费,或使用 Iterator。
无限流为何重要
无限流让您可以将生成与消费分离。生产者描述一条无尽的规则;消费者决定实际生成多少内容。
这是适用于处理流程、模拟以及惰性数据源的强大函数式模式。
快速检查
请检验您对无限 LazyList 的理解。
回顾
无限 LazyList 之所以可行,是因为尾部由需求驱动:from、iterate、自引用的 fibs 以及素数筛选器都能无限生成元素,但只会实现您实际消费的部分。
请避免对整个序列执行操作,也不要持有头部。接下来,我们将掌握如何以惰性方式获取和筛选元素。
常见问题解答
「无限流」课时是免费的吗?
是的 — 「无限流」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 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 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「无限流」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?
能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。