以自定义语言实现“生成器”支持

时间:2010-04-15 10:54:51

标签: dsl yield generator

1 个答案:

答案 0 :(得分:5)

生成器基本上是半协同程序,有一些恼人的限制。所以,显然,你可以使用半协同程序(当然还有完整的协同程序)来实现它们。

如果您没有协程,则可以使用任何其他通用控制流构造。在每个控制流构造(包括所有其他通用控制流构造)的意义上,有很多控制流构造是“通用的”,包括协同程序,因此生成器可以(或多或少) )只是简单地转化为那种普遍的结构。

其中最着名的可能是GOTO。只需GOTO,您就可以构建任何其他控制流结构:IF-THEN-ELSEWHILEFORREPEAT-UNTIL,{{ 1}},异常,线程,子程序调用,方法调用,函数调用等,当然还有协同程序和生成器。

几乎所有CPU都支持FOREACH(尽管在CPU中,它们通常称之为GOTO)。事实上,在许多CPU中,jmp唯一的控制流构造,尽管今天至少支持子程序调用(GOTO),也许还有一些原始形式的异常处理和/或并发原语(比较和交换)通常也是内置的。

另一个众所周知的控制流原语是延续。 Continuations基本上是call的更有条理,更易管理和更不邪恶的变体,特别是在函数式语言中很流行。但也有一些低级语言将其控制流基于延续,例如Parrot虚拟机使用控制流的延续,我相信在某些研究实验室中甚至有一些基于延续的CPU。

C有一种“糟糕”的延续形式(GOTOsetjmp),它们比“真实”延续功能强大得多且不易使用,但它们功能强大足以实现生成器(事实上,可以用来实现完整的继续)。

在Unix平台上,longjmp可用作setcontext / setjmp的更强大,更高级别的替代方案。

另一个众所周知的控制流构造,但是作为低层基板构建其他控制流构造,可能不会引起关注,这是例外。有一篇论文表明,异常可以比延续更强大,从而使异常基本上等同于longjmp,从而具有普遍的强大功能。事实上,异常 有时被用作通用控制流构造:Microsoft Volta项目,它将.NET字节码编译为JavaScript,使用JavaScript异常来实现.NET线程和生成器。

不通用,但可能足以实现生成器只是简单的尾调用优化。 (但我可能错了。遗憾的是我没有证据。)我认为你可以将生成器转换为一组相互尾递归的函数。我知道状态机可以使用尾调用来实现,所以我很确定生成器也可以,因为毕竟C#将生成器作为状态机实现。 (我认为这与懒惰评估一起工作特别好。)

最后但并非最不重要的是,在一个带有固定调用堆栈的语言中(例如大多数Smalltalks),您可以构建几乎任何类型的控制流构造。 (事实上​​,一个具体化的调用堆栈基本上是功能性高级延续的程序性低级别。)

那么,生成器的其他实现是什么样的?

Lua本身没有生成器,但它具有完全不对称的协同程序。主要的C实现使用GOTO / setjmp来实现它们。

Ruby本身也没有生成器,但它有longjmp个,可以用作生成器。 Enumerator不是语言的一部分,它们是图书馆功能。 MRI使用continuation实现Enumerator,而continuation又使用Enumerator / setjmp实现。 YARV使用longjmp实现Enumerator s(这是Ruby拼写“coroutines”的方式),使用Fiber / setjmp实现。我相信JRuby目前使用线程实现longjmp,但是一旦JVM获得更好的控制流结构,他们就希望切换到更好的状态。

Python的生成器实际上或多或少是完全成熟的协同程序。 CPython使用Enumerator / setjmp实现它们。