【问题标题】:Implementing "Generator" support in a custom language以自定义语言实现“生成器”支持
【发布时间】:2011-02-08 07:57:07
【问题描述】:

我对语言设计有点迷恋,目前我正在玩我自己的爱好语言。 (http://rogeralsing.com/2010/04/14/playing-with-plastic/)

真正让我流血的一件事是“发电机”和“产量”关键字。 我知道 C# 使用 AST 转换将枚举器方法转换为状态机。

但它在其他语言中是如何工作的? 有没有办法在没有 AST 转换的语言中获得生成器支持? 例如像 Python 或 Ruby 这样的语言是否会求助于 AST 转换来解决这个问题?

(问题是生成器是如何在不同语言的底层实现的,而不是如何用其中一种语言编写生成器)

【问题讨论】:

  • 如果你的语言支持一流的延续,在这些东西之上实现生成器应该不是问题。否则,您将需要转换您的代码(老实说,我无法想象一种您无法做到的语言)。

标签: dsl yield generator


【解决方案1】:

生成器基本上是具有一些令人讨厌的限制的半协同程序。因此,很明显,您可以使用半协程(当然也包括全协程)来实现它们。

如果您没有协程,您可以使用任何其他通用控制流构造。有很多控制流构造是“通用的”,因为每个控制流构造(包括所有其他通用控制流构造),包括协程,因此生成器可以(或多或少) 简单地转化为唯一的通用结构。

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

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

另一个众所周知的控制流原语是延续。延续基本上是GOTO 的一种更结构化、更易于管理且不那么邪恶的变体,在函数式语言中尤其流行。但也有一些低级语言将其控制流基于延续,例如 Parrot 虚拟机使用延续作为控制流,我相信在某个研究实验室的某个地方甚至有一些基于延续的 CPU。

C 有一种“蹩脚”形式的延续(setjmplongjmp),与“真正的”延续相比,它们的功能和易用性要低得多,但它们的功能足以实现生成器(事实上​​,可以用来实现完整的延续)。

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

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

不是通用的,但可能足够强大以实现生成器只是简单的尾调用优化。 (不过,我可能错了。很遗憾,我没有证据。)我认为您可以将生成器转换为一组相互尾递归的函数。我知道状态机可以使用尾调用来实现,所以我很确定生成器也可以,因为毕竟 C# 将生成器实现为状态机。 (我认为这与惰性求值配合得特别好。)

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

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

Lua 本身没有生成器,但它有完整的非对称协程。主要的 C 实现使用setjmp/longjmp 来实现它们。

Ruby 本身也没有生成器,但它有Enumerators,可以用作生成器。 Enumerators 不是语言的一部分,它们是库功能。 MRI 使用延续实现Enumerators,而延续又使用setjmp/longjmp 实现。 YARV 使用 Fibers 实现 Enumerators(这是 Ruby 拼写“协程”的方式),那些是使用 setjmp/longjmp 实现的。我相信 JRuby 目前使用线程实现 Enumerators,但他们希望在 JVM 获得一些更好的控制流构造后立即切换到更好的东西。

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

【讨论】:

  • 很棒的响应,我目前正在使用线程模拟 JRuby 方式,尽管实现有错误。我还发现有趣的是,事实证明可以仅使用闭包来支持类似 foreach 的块。那里不需要状态机或光纤。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-20
  • 2019-07-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多