生成器(generator) 是一种能够生成潜在无限值序列的函数。该函数每生成一个值后,会暂停自身执行,等待调用方请求下一个值。
iter-defun 用于定义一个生成器函数。生成器函数的签名与普通函数一致,但执行逻辑不同:调用生成器函数时,并不会立即执行 body(函数体),而是返回一个迭代器对象。这个迭代器会运行 body 来生成值 —— 每当执行到 iter-yield 或 iter-yield-from 处,迭代器会输出一个值并暂停执行。当 body 正常返回时,iter-next 会触发 iter-end-of-sequence 信号,并将 body 的返回结果作为该信号的条件数据。
body 中可以编写任意合法的 Lisp 代码,但 iter-yield 和 iter-yield-from 不能出现在 unwind-protect 形式内部。
iter-lambda 用于创建一个匿名生成器函数,其工作方式与通过 iter-defun 创建的生成器函数完全一致。
当 iter-yield 出现在生成器函数内部时,它会指示当前迭代器暂停执行,并将 value 作为 iter-next 的返回值。而 iter-yield 本身的求值结果,则是下一次调用 iter-next 时传入的 value 参数。
iter-yield-from 会产出 iterator(迭代器)生成的所有值,其自身的求值结果为 iterator 对应的生成器函数正常返回的值。在 iter-yield-from 掌控执行流程期间,iterator 会接收所有通过 iter-next 发送给该迭代器的值。
使用生成器函数的步骤:首先以常规方式调用生成器函数,得到一个 迭代器(iterator) 对象(迭代器是生成器的一个具体实例);然后通过 iter-next 从该迭代器中获取值。当迭代器中没有更多值可供获取时,iter-next 会抛出 iter-end-of-sequence 条件异常,并将迭代器的最终值作为该异常的附加数据。
需要特别注意的是,生成器函数体仅会在调用 iter-next 时执行。调用通过 iter-defun 定义的函数会生成一个迭代器;只有通过 iter-next 驱动这个迭代器,才会触发有实际意义的执行逻辑。
每次调用生成器函数,都会生成一个 独立 的迭代器,且每个迭代器都拥有自身的状态。
从 iterator(迭代器)中获取下一个值。若已无更多可生成的值(即迭代器对应的生成器函数已返回),iter-next 会触发 iter-end-of-sequence 条件异常;该异常关联的附加数据,即为迭代器 iterator 对应的生成器函数的返回值。
value 会被传入迭代器,并成为 iter-yield 的求值结果。对于某个迭代器的第一次 iter-next 调用,value 会被忽略 ——因为在迭代器对应的生成器函数启动之初,生成器函数尚未执行到任何 iter-yield 形式。
若 iterator(迭代器)暂停于 unwind-protect 的 bodyform(主体代码)内部,且变得无法被访问,Emacs 会在一次垃圾回收过程后最终执行 unwind 处理函数。(注意:iter-yield 在 unwind-protect 的 unwindforms(清理代码)内部是不合法的。)如需确保这些处理函数在此之前执行,可调用 iter-close。
Emacs 提供了一些便捷函数来简化迭代器的使用:
依次将 var 绑定为 iterator 生成的每个值,并执行 body(代码体)。
Common Lisp 循环工具也包含用于操作迭代器的特性,see Loop Facility in Common Lisp Extensions。
以下代码片段演示了使用迭代器的一些核心原则。
(require 'generator)
(iter-defun my-iter (x)
(iter-yield (1+ (iter-yield (1+ x))))
;; Return normally
-1)
(let* ((iter (my-iter 5))
(iter2 (my-iter 0)))
;; Prints 6
(print (iter-next iter))
;; Prints 9
(print (iter-next iter 8))
;; Prints 1; iter and iter2 have distinct states
(print (iter-next iter2 nil))
;; We expect the iter sequence to end now
(condition-case x
(iter-next iter)
(iter-end-of-sequence
;; Prints -1, which my-iter returned normally
(print (cdr x)))))