跳至主要內容

生成器和序列

guodongAndroid大约 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
	}
}

上述代码发生了很多事情:

  1. 通过 generatorFib 创建了一个序列但使用 take(8) 限制了它只有 8 个元素。
  2. 迭代刚刚创建的序列并打印每个元素。
  3. generatorFib 中返回一个序列。
  4. 打印 “Suspending...”。
  5. 生成斐波那契数列中的 0 并通过 yield 挂起。
  6. 打印 “Suspending...”。
  7. 无限生成斐波那契数列中的下一项并通过 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 来自哪里?下一节将找到答案!