tl; dr摘要:

特别是,该建议说,要以该语言实现此功能,将需要附加的JVM支持。

当他们说“必需”时,它们的意思是“必须以在语言之间既高效又可互操作的方式实现”。

因此,如何在没有其他支持的情况下实现此功能

有很多方法,最容易理解的方法是如何工作的(但不一定最容易实现)是在JVM之上使用自己的语义来实现自己的VM。 (请注意,这并不是实际完成的方式,这只是关于为什么可以执行的直觉。)

没有它,能否有效实施?

并不是的。

稍长的解释:

注意,Project Loom的一个目标是纯粹将这种抽象作为一个库来引入。 这具有三个优点:

引入新库比更改Java编程语言要容易得多。

可以在JVM上以每种单一语言编写的程序立即使用库,而Java语言功能只能由Java程序使用。

可以实现具有不使用新JVM功能的相同API的库,这将允许您编写具有在旧JVM上运行的代码,并且可以进行简单的重新编译(尽管性能较低)。

但是,将其实现为库可避免巧妙的编译器技巧,因为它们不涉及编译器,因此可以将协例程转换为其他东西。 没有聪明的编译器技巧,要获得良好的性能要困难得多,因此,这是对JVM支持的“要求”。

更长的解释:

通常,所有通常的“强大”控制结构在计算意义上都是等效的,并且可以彼此实现。

在那些“强大的”通用控制流结构中,最著名的是古老的Function,另一个是Continuations。 然后是线程和协程,这是人们不经常想到的,但这也等同于async:异常。

另一种可能性是重新定义调用堆栈,以便程序员可以将调用堆栈作为对象进行访问,并且可以对其进行修改和重写。 (例如,许多Smalltalk方言都这样做,这也有点像在C和汇编语言中那样。)

只要拥有其中之一,就可以通过仅在另一个之上实现而拥有所有这些。

JVM有两个:Exceptions和Function,但是JVM中的async不是通用的,它是非常有限的:它只能在单个方法内工作。 (它实际上仅用于循环。)因此,这给我们留下了异常。

因此,这是对您的问题的一种可能的答案:您可以在Exceptions之上实现协同例程。

另一种可能性是根本不使用JVM的控制流,而是实现自己的堆栈。

但是,这通常不是在JVM上实现协同例程时实际采用的路径。 最有可能实现共同例程的人会选择使用蹦床并将部分执行上下文重新定义为对象。 也就是说,例如,如何在CLI上的C♯中实现Generators(不是JVM,但是挑战是相似的)。 通过将方法的局部变量提升到上下文对象的字段中,并在每个Function语句上将该方法拆分为该对象上的多个方法,可以实现C♯中的生成器(基本上是受限的半协程序)。 状态机,并仔细地通过上下文对象上的字段线程化所有状态更改。 在async/await作为一种语言功能出现之前,一个聪明的程序员也使用相同的机器实现了异步编程。

但是,这就是您最可能提到的文章:所有这些机器都是昂贵的。 如果您实现自己的堆栈或将执行上下文提升到一个单独的对象中,或将所有方法编译为一个巨型方法并在各处使用Function(由于方法的大小限制,这甚至是不可能的),或者将Exceptions作为控件 -flow,至少这两项是正确的:

您的调用约定与其他语言期望的JVM堆栈布局不兼容,即您失去了互操作性。

JIT编译器不知道您的代码在做什么,并且呈现字节代码模式,执行流模式和使用模式(例如,抛出和捕获大量异常),这是它所不期望且也不知道如何做的 优化,即失去性能。

Rich Hickey(Clojure的设计师)曾经在一次演讲中说:“尾巴通话,表演,互操作。选择两个。” 我将其概括为我称为Hickey的Maxim:“高级控制流,性能,互操作。选择两个。”

实际上,通常甚至很难实现互操作或性能之一。

而且,您的编译器将变得更加复杂。

当该构造在JVM中本地可用时,所有这些都消失了。 想象一下,例如,如果JVM没有线程。 然后,每种语言实现都将创建自己的线程库,该库很难,复杂,缓慢且不会与任何其他语言实现的线程库互操作。

最近的一个真实示例是lambda:JVM上的许多语言实现都有lambda,例如 斯卡拉(Scala)。 然后,Java也添加了lambda,但是由于JVM不支持lambda,因此必须对其进行编码,并且Oracle选择的编码与Scala之前选择的编码不同,这意味着您无法传递Java lambda。 期望使用Scala Function的Scala方法。这种情况下的解决方案是Scala开发人员完全重新编写了他们的lambda编码,以与Oracle选择的编码兼容。 实际上,这在某些地方破坏了向后兼容性。