Qual è la differenza tra una coroutine e una continuazione e un generatore?
Qual è la differenza tra una coroutine e una continuazione e un generatore?
Risposte:
Inizierò con i generatori, visto che sono il caso più semplice. Come accennato da @zvolkov, sono funzioni / oggetti che possono essere ripetutamente richiamati senza tornare, ma quando vengono richiamati restituiscono (producono) un valore e quindi sospendono la loro esecuzione. Quando vengono chiamati di nuovo, si avviano da dove hanno sospeso l'ultima esecuzione e fanno di nuovo le loro cose.
Un generatore è essenzialmente una coroutine ridotta (asimmetrica). La differenza tra una coroutine e un generatore è che una coroutine può accettare argomenti dopo essere stata inizialmente chiamata, mentre un generatore non può.
È un po 'difficile trovare un esempio banale di dove useresti le coroutine, ma ecco il mio miglior tentativo. Prendi questo (inventato) codice Python come esempio.
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
Un esempio di utilizzo delle coroutine sono i lexer e i parser. Senza coroutine nella lingua o emulati in qualche modo, il lexing e il codice di analisi devono essere mescolati insieme anche se in realtà sono due preoccupazioni separate. Ma usando una coroutine, puoi separare il codice lexing e di analisi.
(Vado a ripassare la differenza tra coroutine simmetriche e asimmetriche. Basti dire che sono equivalenti, puoi convertire da una all'altra, e le coroutine asimmetriche - che sono i generatori più simili - sono le più facile da capire. Stavo delineando come si potrebbero implementare coroutine asimmetriche in Python.)
Le continuazioni sono in realtà bestie piuttosto semplici. Tutto ciò che sono, sono funzioni che rappresentano un altro punto nel programma che, se lo chiamate, farà sì che l'esecuzione passi automaticamente al punto rappresentato dalla funzione. Ne usi versioni molto limitate ogni giorno senza nemmeno accorgertene. Le eccezioni, ad esempio, possono essere pensate come una sorta di continuazione dentro e fuori. Ti darò un esempio di pseudocodice basato su Python di una continuazione.
Supponiamo che Python abbia una funzione chiamata callcc()
, e questa funzione prende due argomenti, il primo è una funzione e il secondo è un elenco di argomenti con cui chiamarlo. L'unica restrizione a quella funzione sarebbe che l'ultimo argomento che prenderà sarà una funzione (che sarà la nostra continuazione attuale).
def foo(x, y, cc):
cc(max(x, y))
biggest = callcc(foo, [23, 42])
print biggest
Ciò che accadrebbe è che callcc()
a sua volta chiamerebbe foo()
con l'attuale continuazione ( cc
), cioè un riferimento al punto nel programma in cui è callcc()
stato chiamato. Quando foo()
chiama la continuazione corrente, è essenzialmente lo stesso che dire callcc()
di restituire con il valore con cui stai chiamando la continuazione corrente, e quando lo fa, riporta lo stack al punto in cui è stata creata la continuazione corrente, cioè quando hai chiamato callcc()
.
Il risultato di tutto ciò sarebbe che la nostra ipotetica variante di Python sarebbe stampata '42'
.
Spero che sia di aiuto, e sono sicuro che la mia spiegazione può essere migliorata parecchio!
La coroutine è una delle diverse procedure che a turno svolgono il loro lavoro e poi si fermano per dare il controllo alle altre coroutine del gruppo.
La continuazione è un "puntatore a una funzione" che si passa a una procedura, da eseguire ("continua con") al termine di tale procedura.
Generator (in .NET) è un costrutto di linguaggio che può sputare un valore, "mettere in pausa" l'esecuzione del metodo e quindi procedere dallo stesso punto quando viene richiesto il valore successivo.
Nella versione più recente di Python, è possibile inviare valori a Generatori con generator.send()
, il che rende i generatori di Python effettivamente coroutine.
La differenza principale tra python Generator e altri generatori, diciamo greenlet, è che in python, yield value
puoi solo tornare al chiamante. Mentre sei in greenlet, target.switch(value)
puoi portarti a un coroutine target specifico e produrre un valore in cui target
continuerebbe a funzionare.
yield
chiamate devono essere nella stessa funzione, che si chiama "Generatore". Non è possibile utilizzare yield
una sottofunzione, motivo per cui quelli di Python sono chiamati semi-coroutine , mentre Lua ha coroutine asimmetriche . (Ci sono proposte per propagare i raccolti, ma penso che siano solo fangose le acque.)