Coroutine vs Fortsetzung vs Generator

147

Was ist der Unterschied zwischen einer Coroutine und einer Fortsetzung und einem Generator?

Mehdi Asgari
quelle
2
Ich frage mich, ob Coroutinen und Fortsetzungen tatsächlich gleichwertig sind. Ich weiß, dass es möglich ist, Coroutinen mit Fortsetzungen zu modellieren, aber ist es möglich, Fortsetzungen mit Coroutinen zu modellieren oder nicht, weil Fortsetzungen streng leistungsfähiger sind?
Nalply

Antworten:

127

Ich werde mit Generatoren beginnen, da dies der einfachste Fall ist. Wie @zvolkov erwähnt hat, handelt es sich um Funktionen / Objekte, die wiederholt aufgerufen werden können, ohne dass sie zurückgegeben werden. Wenn sie jedoch aufgerufen werden, wird ein Wert zurückgegeben (erhalten) und die Ausführung wird dann ausgesetzt. Wenn sie erneut angerufen werden, starten sie dort, wo sie die Hinrichtung zuletzt ausgesetzt haben, und machen ihr Ding erneut.

Ein Generator ist im Wesentlichen eine abgeschnittene (asymmetrische) Coroutine. Der Unterschied zwischen einer Coroutine und einem Generator besteht darin, dass eine Coroutine Argumente akzeptieren kann, nachdem sie ursprünglich aufgerufen wurde, während ein Generator dies nicht kann.

Es ist ein bisschen schwierig, ein triviales Beispiel dafür zu finden, wo Sie Coroutinen verwenden würden, aber hier ist mein bester Versuch. Nehmen Sie diesen (erfundenen) Python-Code als Beispiel.

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

Ein Beispiel für die Verwendung von Coroutinen sind Lexer und Parser. Ohne Coroutinen in der Sprache oder irgendwie emuliert, müssen Lexing- und Parsing-Code miteinander gemischt werden, obwohl es sich tatsächlich um zwei separate Probleme handelt. Mit einer Coroutine können Sie den Lexing- und Parsing-Code jedoch trennen.

(Ich werde den Unterschied zwischen symmetrischen und asymmetrischen Coroutinen überarbeiten. Es genügt zu sagen, dass sie gleichwertig sind, Sie können von einem zum anderen konvertieren, und asymmetrische Coroutinen - die den Generatoren am ähnlichsten sind - sind die leichter zu verstehen. Ich habe skizziert, wie man asymmetrische Coroutinen in Python implementieren könnte.)

Fortsetzungen sind eigentlich ganz einfache Bestien. Alles, was sie sind, sind Funktionen, die einen anderen Punkt im Programm darstellen. Wenn Sie ihn aufrufen, wechselt die Ausführung automatisch zu dem Punkt, den diese Funktion darstellt. Sie verwenden jeden Tag sehr eingeschränkte Versionen davon, ohne es zu merken. Ausnahmen können zum Beispiel als eine Art Inside-Out-Fortsetzung betrachtet werden. Ich werde Ihnen ein Python-basiertes Pseudocode-Beispiel für eine Fortsetzung geben.

Angenommen, Python hat eine Funktion aufgerufen callcc(), und diese Funktion hat zwei Argumente verwendet, wobei das erste eine Funktion und das zweite eine Liste von Argumenten ist, mit denen sie aufgerufen werden soll. Die einzige Einschränkung für diese Funktion wäre, dass das letzte Argument eine Funktion ist (die unsere aktuelle Fortsetzung sein wird).

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

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

Was passieren würde, wäre, dass callcc()dies wiederum foo()mit der aktuellen Fortsetzung ( cc) aufgerufen würde, dh einem Verweis auf den Punkt im Programm, an dem callcc()aufgerufen wurde. Wenn Sie foo()die aktuelle Fortsetzung callcc()aufrufen , entspricht dies im Wesentlichen der Aufforderung, mit dem Wert zurückzukehren, mit dem Sie die aktuelle Fortsetzung aufrufen. Wenn dies der Fall ist, wird der Stapel an den Ort zurückgesetzt, an dem die aktuelle Fortsetzung erstellt wurde, dh wenn Sie aufgerufen haben callcc().

Das Ergebnis all dessen wäre, dass unsere hypothetische Python-Variante gedruckt würde '42'.

Ich hoffe, das hilft, und ich bin sicher, dass meine Erklärung einiges verbessert werden kann!

Keith Gaughan
quelle
6
Ein Nit: begrenzt Fortsetzungen sind Funktionen, aber undefinierten oder unzureichend abgegrenzten Fortsetzungen sind nicht: okmij.org/ftp/continuations/undelimited.html#delim-vs-undelim
Frank Shearar
2
Das ist ein guter Punkt. Das heißt, in den meisten praktischen Anwendungen sprechen die Leute, wenn sie "Fortsetzung" sagen, von partiellen / begrenzten Fortsetzungen. Das Einbringen der verschiedenen anderen Arten von Fortsetzungen hätte die Erklärung etwas durcheinander gebracht.
Keith Gaughan
1
Fortsetzungen sind keine Funktionen, obwohl sie in Funktionen umgewandelt werden können. "Das heißt, in den meisten praktischen Anwendungen sprechen die Leute, wenn sie 'Fortsetzung' sagen, über teilweise / begrenzte Fortsetzungen." Würden Sie auf eine solche Verwendung des Begriffs "Fortsetzung" hinweisen? Ich habe noch nie eine solche Verwendung getroffen. Außerdem haben Sie mit call / cc ein Beispiel für eine unbegrenzte Fortsetzung gegeben. Die Operatoren für begrenzte Fortsetzungen sind normalerweise "Zurücksetzen" und "Verschieben" (sie können andere Namen haben).
Ivancho
3
Beginnen wir mit der Tatsache, dass ich das vor fünf Jahren geschrieben habe. Du bist etwas spät zur Party. Zweitens weiß ich , dass unbegrenzte Fortsetzungen keine Funktionen sind, aber Sie versuchen zu erklären, wie sie funktionieren, ohne sie als solche zu bezeichnen, während Sie gleichzeitig die Sprache einfach halten. Aus der Sicht eines durchschnittlichen Programmierers macht die Tatsache, dass eine nicht begrenzte Fortsetzung nicht zurückkehrt, sie nur zu einer One-Shot-Funktion, die gemäß der Definition einer Funktion nicht korrekt ist, aber zumindest verständlich .
Keith Gaughan
2
Ich bin nicht zu spät für die Party, da dies das erste Ergebnis ist, das ich bei Google bekomme, wenn ich nach "Coroutine vs Generator" suche. Ich hatte gehofft, einige gute Informationen über ihre Unterschiede zu finden. Jedenfalls habe ich es woanders gefunden. Und ich bin nicht der erste, der darauf hinweist, dass Ihre Erklärung zu Fortsetzungen falsch ist. Das Problem ist, dass jemand etwas falsch macht und möglicherweise später verwirrt wird, wenn er oder sie dasselbe Wort trifft, das für etwas anderes verwendet wird.
Ivancho
33

Coroutine ist eine von mehreren Prozeduren, die abwechselnd ihre Arbeit erledigen und dann pausieren, um den anderen Coroutinen in der Gruppe die Kontrolle zu geben.

Die Fortsetzung ist ein "Zeiger auf eine Funktion", die Sie an eine Prozedur übergeben, die ausgeführt wird ("Fortsetzung mit"), wenn diese Prozedur abgeschlossen ist.

Generator (in .NET) ist ein Sprachkonstrukt, das einen Wert ausspucken, die Ausführung der Methode "pausieren" und dann an derselben Stelle fortfahren kann, wenn Sie nach dem nächsten Wert gefragt werden.

zvolkov
quelle
Mir ist klar, dass die Antwort möglicherweise nicht korrekt ist, aber auf dieser Ebene habe ich versucht, sie einfach zu halten. Außerdem verstehe ich das alles selbst nicht wirklich :)
zvolkov
Ein Generator in Python ähnelt der C # -Version, ist jedoch als spezielle Syntax zum Erstellen einer Instanz eines Iteratorobjekts implementiert, die die von der von Ihnen angegebenen "Funktions" -Definition zurückgegebenen Werte zurückgibt.
Benson
2
Eine kleine Korrektur: "... einschließlich Aufrufstapel und aller Variablen, ABER NICHT IHRER WERTE" (oder einfach "alle Variablen" löschen). Fortsetzungen behalten die Werte nicht bei, sondern enthalten nur den Aufrufstapel.
Nalply
Nein, Fortsetzungen sind kein "Zeiger auf eine Funktion". In der naivsten Implementierung enthält es einen Zeiger auf die Funktion und eine Umgebung enthält die lokalen Variablen. Und es wird nie zurückgegeben, es sei denn, Sie verwenden call / cc, um es mit einem Rückgabewert zu erfassen.
NalaGinrut
9

In einer neueren Version von Python können Sie Werte an Generatoren mit senden generator.send(), wodurch Python-Generatoren effektiv Coroutinen erstellen.

Der Hauptunterschied zwischen dem Python-Generator und einem anderen Generator, z. B. Greenlet, besteht darin, dass Sie in Python yield valuenur zum Aufrufer zurückkehren können. Während Sie sich im Greenlet befinden, target.switch(value)können Sie zu einer bestimmten Zielkoroutine gelangen und einen Wert erhalten, bei dem targetdie weiterhin ausgeführt wird.

Yichuan Wang
quelle
3
In Python yieldmüssen sich alle Aufrufe in derselben Funktion befinden, die als "Generator" bezeichnet wird. Sie können nicht yieldvon einer Unterfunktion, weshalb Pythons Semi-Coroutinen genannt werden , während Lua asymmetrische Coroutinen hat . (Es gibt Vorschläge, um die Erträge zu verbreiten, aber ich denke, diese
trüben
7
@ cdunn2001: (Kommentar von Winston) Python3.3 hat den Ausdruck "Ausbeute von" eingeführt, mit dem Sie vom Subgenerator nachgeben können.
Linus Caldwell