生成器基本上是半协程,有一些恼人的限制。因此,显然,您可以使用半协程(当然还有完整协程)来实现它们。
如果您没有协程,则可以使用任何其他通用控制流结构。有很多控制流结构是“通用的”,即every控制流构造(包括所有其他通用控制流构造),包括协程,因此生成器可以(或多或少)简单地转换为仅该通用构造。
其中最著名的可能是GOTO
。只要GOTO
,你可以构建any其他控制流结构:IF-THEN-ELSE
, WHILE
, FOR
, REPEAT-UNTIL
, FOREACH
、异常、线程、子程序调用、方法调用、函数调用等等,当然还有协程和生成器。
几乎所有CPU都支持GOTO
(尽管在CPU中,他们通常称之为jmp
)。事实上,在许多CPU中,GOTO
is the only控制流构造,尽管今天至少本地支持子例程调用(call
)并且通常还内置了某种原始形式的异常处理和/或并发原语(比较和交换)。
另一个众所周知的控制流原语是延续。延续基本上是一种更结构化、更易于管理且不那么邪恶的变体GOTO
,在函数式语言中尤其流行。但也有一些低级语言将其控制流基于延续,例如 Parrot 虚拟机使用延续来控制流,我相信在某些研究实验室中甚至有一些基于延续的 CPU。
C 有一种“蹩脚”的延续形式(setjmp
and longjmp
),它们比“真正的”延续功能要弱得多,也不太容易使用,但它们足够强大,足以实现生成器(事实上,可以用来实现完整的延续)。
在 Unix 平台上,setcontext
可以用作更强大和更高级别的替代品setjmp
/longjmp
.
另一种众所周知的控制流构造,但可能不会作为低级底层构建而浮现在脑海中other之上的控制流结构是例外。有一篇论文表明异常可以比延续更强大,因此使得异常本质上等同于GOTO
因而具有普遍的力量。事实上,也有例外are有时用作通用控制流结构:Microsoft Volta 项目将 .NET 字节码编译为 JavaScript,使用 JavaScript 异常来实现 .NET 线程和生成器。
不通用,但可能强大到足以实现生成器,这只是简单的尾调用优化。 (不过,我可能是错的。不幸的是,我没有证据。)我think您可以将生成器转换为一组相互尾递归的函数。我知道状态机可以使用尾部调用来实现,所以我很确定生成器也可以,因为毕竟 C# 将生成器实现为状态机。 (我认为这与惰性求值结合起来效果特别好。)
最后但并非最不重要的一点是,在具有具体化调用堆栈的语言中(例如大多数 Smalltalks),您可以构建几乎任何类型的您想要的控制流结构。 (事实上,具体化的调用堆栈基本上是相当于功能性高级延续的程序低级。)
所以呢do生成器的其他实现是什么样的?
Lua 本身没有生成器,但它有完整的非对称协程。主要的C实现使用setjmp
/longjmp
来实施它们。
Ruby 本身也没有生成器,但它有Enumerator
s,可以用作发电机。Enumerator
s 不是语言的一部分,它们是库功能。 MRI 工具Enumerator
s 使用 Continuations,而 Continuations 又是使用setjmp
/longjmp
。 YARV 实施Enumerator
s using Fiber
s(Ruby 拼写“协程”的方式),以及those是使用实现的setjmp
/longjmp
。我相信 JRuby 目前实现了Enumerator
使用线程,但一旦 JVM 获得一些更好的控制流结构,他们就希望切换到更好的东西。
Python 的生成器实际上或多或少是成熟的协程。 CPython 使用以下方式实现它们setjmp
/longjmp
.