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 !
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 ?