生成器和序列
大约 2 分钟
使用带有序列的协程,可以实现生成器。生成器是一种特殊的函数,可以返回值,然后在再次调用时恢复。想想惰性的、无限的数据流,就像前面提到的斐波那契序列。
提示
注意:你可以在其他语言(比如:Python 和 Javascript)中找到生成器函数,它们带有 yield
关键字。
由于序列的的惰性求值行为和使用协程的挂起-恢复概念,创建生成器函数非常容易。
为了了解如何实现生成器,你可以在 GeneratorExample.kt 文件中查看下面关于生成无限的斐波那契数列的代码片段:
fun main() {
// 1
val sequence = generatorFib().take(8)
// 2
sequence.forEach {
println("$it")
}
}
// 3
fun generatorFib() = sequence {
// 4
print("Suspending...")
// 5
yield(0L)
var cur = 0L
var next = 1L
while (true) {
// 6
print("Suspending...")
// 7
yield(next)
val tmp = cur + next
cur = next
next = tmp
}
}
上述代码发生了很多事情:
- 通过
generatorFib
创建了一个序列但使用take(8)
限制了它只有 8 个元素。 - 迭代刚刚创建的序列并打印每个元素。
- 在
generatorFib
中返回一个序列。 - 打印 “Suspending...”。
- 生成斐波那契数列中的 0 并通过
yield
挂起。 - 打印 “Suspending...”。
- 无限生成斐波那契数列中的下一项并通过
yield
挂起。
运行上面的代码片段,输出如下:
Suspending...0
Suspending...1
Suspending...1
Suspending...2
Suspending...3
Suspending...5
Suspending...8
Suspending...13
请注意 generatorFib
中使用了 sequence
,它用于构建一个逐个延迟生成值的 序列。
当不需要值时,它将挂起,当序列不再使用或耗尽时,它将适当的结束。
注意 yield
。它是一个挂起函数,从源码的签名中可以看出:
override suspend fun yield(value: T)
这意味着每当执行点达到 yield
时,它就会挂起。这也可以从代码片段的输出中进行验证。就在调用 yield
之前,“Suspending…”被打印。接下来,当遇到 yield
函数时,该函数将挂起并返回传递给 yield
的值。该序列生成值,并在运行 forEach
时将其打印出来。然后函数会恢复,直到生成下一个斐波那契数并调用 yield
。
通常,代码会跳到 generatorFib
的中间并执行其中的一部分。这之所以有效,是因为在这种情况下,不仅会返回结果,还会返回代码的其余部分。
然后执行点随 Continuation 的实例一起移动,即:函数的结果以及代码返回的上下文。
然而,现在更重要的问题是 yield
来自哪里?下一节将找到答案!