tl;dr Résumé :
Cette proposition indique notamment que pour mettre en œuvre cette fonctionnalité dans le langage, un support JVM supplémentaire sera nécessaire.
Quand ils disent "requis", ils veulent dire "requis pour être implémenté de manière à être à la fois performant et interopérable entre les langages".
Ainsi, comment cette fonctionnalité est mise en œuvre sans support supplémentaire
Il y a de nombreuses façons de procéder, la plus simple pour comprendre comment cela peut fonctionner (mais pas nécessairement la plus facile à mettre en œuvre) est de mettre en œuvre votre propre VM avec votre propre sémantique au-dessus de la JVM. (Notez qu'est pas comment cela se passe réellement, il s'agit seulement d'une intuition quant à por qué c'est possible).
et peut-il être mis en œuvre efficacement sans lui ?
Pas vraiment.
Une explication un peu plus longue :
Notez que l'un des objectifs du projet Loom est d'introduire cette abstraction. purement comme une bibliothèque. Cela présente trois avantages :
- Il est beaucoup plus facile d'introduire une nouvelle bibliothèque que de modifier le langage de programmation Java.
- Les bibliothèques peuvent être utilisées immédiatement par les programmes écrits dans tous les langages de la JVM, alors qu'une fonctionnalité du langage Java ne peut être utilisée que par les programmes Java.
- Une bibliothèque avec la même API qui n'utilise pas les nouvelles fonctionnalités de la JVM peut être implémentée, ce qui vous permettra d'écrire du code qui fonctionne sur les anciennes JVM avec une simple recompilation (bien qu'avec moins de performance).
Cependant, le fait de l'implémenter en tant que bibliothèque empêche les compilateurs astucieux de transformer les co-routines en quelque chose d'autre. il n'y a pas de compilateur impliqué . En l'absence d'astuces de compilation, il est beaucoup plus difficile d'obtenir de bonnes performances, d'où la "nécessité" d'un support JVM.
Explication plus longue :
En général, toutes les structures de contrôle "puissantes" habituelles sont équivalentes sur le plan informatique et peuvent être mises en œuvre les unes avec les autres.
La plus connue de ces "puissantes" structures universelles de flux de contrôle est la vénérable GOTO
et d'autres sont des Continuations. Ensuite, il y a les Threads et les Coroutines, et un élément auquel les gens ne pensent pas souvent, mais qui est également équivalent à GOTO
: Exceptions.
Une autre possibilité est une pile d'appels réifiée, de sorte que la pile d'appels est accessible en tant qu'objet pour le programmeur et peut être modifiée et réécrite. (De nombreux dialectes de Smalltalk font cela, par exemple, et c'est aussi un peu comme la façon dont cela est fait en C et en assembleur).
Tant que vous avez un de ceux-là, vous pouvez avoir tous en les mettant en place l'un après l'autre.
La JVM en a deux : les exceptions et GOTO
mais le GOTO
dans la JVM est pas universelle, elle est extrêmement limitée : elle ne fonctionne que à l'intérieur de une seule méthode. (Elle n'est essentiellement destinée qu'aux boucles.) Il nous reste donc les Exceptions.
Voilà donc une réponse possible à votre question : vous pouvez implémenter des co-routines au-dessus des exceptions.
Une autre possibilité est de ne pas utiliser le flux de contrôle de la JVM. du tout et mettez en place votre propre pile.
Cependant, ce n'est généralement pas le chemin qui est réellement emprunté lors de l'implémentation de co-routines sur la JVM. Le plus souvent, quelqu'un qui implémente des co-routines choisira d'utiliser des Trampolines et de réifier partiellement le contexte d'exécution en tant qu'objet. C'est, par exemple, la façon dont les générateurs sont implémentés en C♯ sur la CLI (pas sur la JVM, mais les défis sont similaires). Les générateurs (qui sont en fait des semi-co-routines restreintes) en C♯ sont mis en œuvre en transformant les variables locales de la méthode en champs d'un objet de contexte et en divisant la méthode en plusieurs méthodes sur cet objet à chaque étape du processus. yield
en les convertissant en une machine d'état, et en faisant soigneusement passer tous les changements d'état par les champs de l'objet contextuel. Et avant que async
/ await
est apparu comme une fonctionnalité du langage, un programmeur astucieux a mis en œuvre la programmation asynchrone en utilisant également la même machinerie.
CEPENDANT, et c'est ce à quoi l'article que vous avez cité faisait très probablement référence : toutes ces machines sont coûteuses. Si vous implémentez votre propre pile ou si vous soulevez le contexte d'exécution dans un objet séparé, ou si vous compilez toutes vos méthodes en une seule géant méthode et utilisation GOTO
partout (ce qui n'est même pas possible à cause de la limite de taille des méthodes), ou utiliser des exceptions comme flux de contrôle, au moins une de ces deux choses sera vraie :
- Vos conventions d'appel deviennent incompatibles avec la disposition de la pile de la JVM que les autres langages attendent, c'est-à-dire que vous perdez interopérabilité .
- Le compilateur JIT n'a aucune idée de ce que fait votre code, et il est confronté à des modèles de code d'octet, des modèles de flux d'exécution et des modèles d'utilisation (par exemple, lancer et attraper gigantesque d'exceptions) qu'il n'attend pas et qu'il ne sait pas comment optimiser, c'est-à-dire que vous perdez performance .
Rich Hickey (le concepteur de Clojure) a dit un jour dans une conférence : "Tail Calls, Performance, Interop. Choisissez-en deux." J'ai généralisé cela à ce que j'appelle Hickey's Maxim : "Advanced Control-Flow, Performance, Interop. Choisissez-en deux."
En fait, il est généralement difficile de réaliser même l'un des l'interopérabilité ou les performances.
En outre, votre compilateur deviendra plus complexe.
Tout cela disparaît lorsque la construction est disponible nativement dans la JVM. Imaginez, par exemple, que la JVM ne dispose pas de Threads. Dans ce cas, chaque implémentation de langage devrait créer sa propre bibliothèque de Threading, ce qui est difficile, complexe, lent, et n'interagit avec aucune autre bibliothèque de Threading. autre la bibliothèque Threading de l'implémentation du langage.
Un exemple récent et concret est celui des lambdas : de nombreuses implémentations de langage sur la JVM disposent de lambdas, par exemple Scala. Ensuite, Java a également ajouté des lambdas, mais comme la JVM ne prend pas en charge les lambdas, ils doivent être utilisés en tant qu'éléments de base. encodé d'une manière ou d'une autre, et l'encodage choisi par Oracle était différent de celui que Scala avait choisi auparavant, ce qui signifiait que vous ne pouviez pas passer un lambda Java à une méthode Scala en attendant un code Scala Function
. Dans ce cas, les développeurs de Scala ont complètement réécrit leur codage des lambdas pour qu'il soit compatible avec le codage choisi par Oracle. Cela a en fait rompu la rétrocompatibilité à certains endroits.