152 votes

Coroutine vs Continuation vs Générateur

Quelle est la différence entre une coroutine, une continuation et un générateur ?

2 votes

Je me demande si les coroutines et les continuations sont effectivement équivalentes. Je sais qu'il est possible de modéliser des coroutines avec des continuations, mais est-il possible de modéliser des continuations avec des coroutines ou non parce que les continuations sont strictement plus puissantes ?

130voto

Keith Gaughan Points 5314

Je commencerai par les générateurs, car c'est le cas le plus simple. Comme @zvolkov l'a mentionné, ce sont des fonctions/objets qui peuvent être appelés à plusieurs reprises sans retourner, mais qui, lorsqu'ils sont appelés, retournent une valeur et suspendent leur exécution. Lorsqu'ils sont appelés à nouveau, ils reprennent l'exécution là où elle a été suspendue pour la dernière fois et recommencent leur travail.

Un générateur est essentiellement une coroutine réduite (asymétrique). La différence entre une coroutine et un générateur est qu'une coroutine peut accepter des arguments après avoir été appelée initialement, alors qu'un générateur ne le peut pas.

Il est un peu difficile de trouver un exemple trivial d'utilisation des coroutines, mais voici ma meilleure tentative. Prenons l'exemple de ce code Python (inventé).

def my_coroutine_body(*args):
    while True:
        # Do some funky stuff
        *args = yield value_im_returning
        # Do some more funky stuff

my_coro = make_coroutine(my_coroutine_body)

x = 0
while True:
   # The coroutine does some funky stuff to x, and returns a new value.
   x = my_coro(x)
   print x

Les lexeurs et les analyseurs syntaxiques sont un exemple d'utilisation des coroutines. Sans coroutines dans le langage ou émulées d'une manière ou d'une autre, les codes de lexage et d'analyse syntaxique doivent être mélangés alors qu'il s'agit en réalité de deux préoccupations distinctes. Mais en utilisant une coroutine, vous pouvez séparer le code de lexage et le code d'analyse syntaxique.

(Je vais passer en revue la différence entre les coroutines symétriques et asymétriques. Il suffit de dire qu'elles sont équivalentes, que l'on peut passer de l'une à l'autre, et que les coroutines asymétriques - qui ressemblent le plus à des générateurs - sont les plus faciles à comprendre. Je décrivais comment on pouvait mettre en œuvre des coroutines asymétriques en Python).

Les continuations sont en fait des choses assez simples. Il s'agit simplement de fonctions représentant un autre point du programme qui, si vous l'appelez, fera basculer automatiquement l'exécution au point que la fonction représente. Vous utilisez tous les jours des versions très restreintes de ces fonctions sans même vous en rendre compte. Les exceptions, par exemple, peuvent être considérées comme une sorte de continuation interne. Je vais vous donner un exemple de continuation en pseudo-code basé sur Python.

Supposons que Python dispose d'une fonction appelée callcc() Cette fonction prend deux arguments, le premier étant une fonction et le second une liste d'arguments pour l'appeler. La seule restriction sur cette fonction serait que le dernier argument qu'elle prend soit une fonction (qui sera notre continuation actuelle).

def foo(x, y, cc):
   cc(max(x, y))

biggest = callcc(foo, [23, 42])
print biggest

Ce qui se passerait, c'est que callcc() appelle à son tour foo() avec la suite actuelle ( cc ), c'est-à-dire une référence à l'endroit du programme où se trouve le callcc() a été appelé. Quand foo() appelle la continuation en cours, cela revient essentiellement à dire à callcc() pour revenir avec la valeur avec laquelle vous appelez la continuation actuelle, et lorsqu'il le fait, il remonte la pile jusqu'à l'endroit où la continuation actuelle a été créée, c'est-à-dire lorsque vous avez appelé callcc() .

Le résultat de tout ceci serait que notre hypothétique variante Python imprimerait '42' .

J'espère que cela vous aidera, et je suis sûr que mon explication peut être améliorée dans une large mesure !

6 votes

Un seul nit : délimité les continuations sont des fonctions, mais non délimité les continuations ne le sont pas : okmij.org/ftp/continuations/undelimited.html#delim-vs-undelim

2 votes

C'est un bon point. Cela dit, dans la plupart des applications pratiques, lorsque les gens parlent de "continuation", ils parlent de continuations partielles/délimitées. L'introduction des divers autres types de continuations aurait quelque peu brouillé l'explication.

1 votes

Les continuités ne sont pas des fonctions, bien qu'elles puissent être réifiées en fonctions. Cela dit, dans la plupart des applications pratiques, lorsque les gens parlent de "continuation", ils parlent de continuations partielles/délimitées. Pouvez-vous citer un tel usage du terme "continuation" ? Je n'ai jamais rencontré un tel usage. Vous avez également donné un exemple de continuation non délimitée, en utilisant call/cc. Les opérateurs pour les continuations délimitées sont généralement "reset" et "shift" (ils peuvent avoir d'autres noms).

33voto

zvolkov Points 9673

La coroutine est une procédure parmi d'autres qui se relaient pour effectuer leur travail et s'arrêtent ensuite pour donner le contrôle aux autres coroutines du groupe.

La continuation est un "pointeur vers une fonction" que vous passez à une procédure, pour qu'elle soit exécutée ("continuée") lorsque cette procédure est terminée.

Le générateur (en .NET) est une construction linguistique qui peut cracher une valeur, "mettre en pause" l'exécution de la méthode et reprendre au même point lorsqu'on lui demande la valeur suivante.

0 votes

Je me rends compte que la réponse n'est peut-être pas exacte, mais à ce niveau de question, j'ai essayé de rester simple. En outre, je ne comprends pas vraiment tout cela moi-même :)

0 votes

Un générateur en python est similaire à la version C#, mais il est implémenté comme une syntaxe spéciale pour créer une instance d'un objet itérateur, qui renvoie les valeurs renvoyées par la définition de la "fonction" que vous fournissez.

2 votes

Une petite correction : "...y compris la pile d'appels et toutes les variables MAIS PAS LEURS VALEURS" (ou laissez tomber "toutes les variables"). Les continuations ne préservent pas les valeurs, elles contiennent simplement la pile d'appels.

10voto

Yichuan Wang Points 343

Dans les versions plus récentes de Python, vous pouvez envoyer des valeurs aux générateurs avec la commande generator.send() qui fait des générateurs python de véritables coroutines.

La principale différence entre le générateur python et les autres générateurs, par exemple greenlet, est qu'en python, votre yield value ne peut que retourner à l'appelant. Pendant qu'il est dans le greenlet, target.switch(value) peut vous amener à une coroutine cible spécifique et vous donner une valeur pour laquelle l'option target continuerait à fonctionner.

3 votes

Mais en Python, tous les yield doivent se trouver dans la même fonction, appelée "générateur". Vous ne pouvez pas yield à partir d'une sous-fonction, c'est pourquoi les fonctions Python sont appelées semi-coroutines tandis que Lua a coroutines asymétriques . (Il existe des propositions visant à propager les rendements, mais je pense qu'elles ne font que brouiller les pistes).

7 votes

@cdunn2001 : (commentaire de Winston) Python3.3 a introduit l'expression "yield from" qui permet de faire un yield à partir d'un sous-générateur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X