Coroutine против продолжения против генератора

147

В чем разница между сопрограммой и продолжением и генератором?

Мехди Аскари
источник
2
Интересно, действительно ли сопрограммы и продолжения эквивалентны? Я знаю, что можно моделировать сопрограммы с продолжениями, но возможно ли моделировать продолжения с сопрограммами или нет, потому что продолжения строго более мощные?
Вскоре

Ответы:

127

Я начну с генераторов, посмотрев, как они являются простейшим случаем. Как уже упоминалось @zvolkov, это функции / объекты, которые можно вызывать многократно без возврата, но при вызове возвращает (выдает) значение и затем приостанавливает их выполнение. Когда их снова вызывают, они начнут с того места, где они в последний раз приостанавливают исполнение, и снова сделают свое дело.

Генератор - это, по сути, урезанная (асимметричная) сопрограмма. Разница между сопрограммой и генератором состоит в том, что сопрограмма может принимать аргументы после того, как она была первоначально вызвана, тогда как генератор не может.

Немного сложно придумать тривиальный пример использования сопрограмм, но вот моя лучшая попытка. Возьмите этот (составленный) код Python в качестве примера.

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

Примером использования сопрограмм являются лексеры и парсеры. Без сопрограмм в языке или каким-либо образом эмулированных, лексический и синтаксический анализ кода должны быть смешаны вместе, хотя на самом деле это две разные проблемы. Но используя сопрограмму, вы можете разделить код лексинга и синтаксического анализа.

(Я собираюсь зачеркнуть разницу между симметричными и асимметричными сопрограммами. Достаточно сказать, что они эквивалентны, вы можете конвертировать из одной в другую, и асимметричные сопрограммы - которые наиболее похожи на генераторы - являются легче понять. Я описал, как можно реализовать асимметричные сопрограммы в Python.)

Продолжения на самом деле довольно простые звери. Все они являются функциями, представляющими другую точку в программе, которая, если вы вызовете ее, заставит выполнение автоматически переключиться на точку, которую представляет функция. Вы используете очень ограниченные версии их каждый день, даже не осознавая этого. Например, исключения можно рассматривать как своего рода продолжение наизнанку. Я дам вам пример продолжения, основанный на Python.

Скажем, у Python была вызванная функция callcc(), и эта функция принимала два аргумента, первый из которых был функцией, а второй - список аргументов для вызова. Единственным ограничением для этой функции будет то, что последним аргументом будет функция (которая будет нашим текущим продолжением).

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

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

То, что произошло бы, это, callcc()в свою очередь, вызовет foo()с текущим продолжением ( cc), то есть ссылкой на точку в программе, в которой callcc()был вызван. Когда foo()вызывается текущее продолжение, это по сути то же самое, что сказать, что callcc()нужно возвращать значение, с которым вы вызываете текущее продолжение, и когда оно это делает, оно откатывает стек туда, где было создано текущее продолжение, то есть когда вы вызывали callcc(),

Результатом всего этого будет то, что наш гипотетический вариант Python будет напечатан '42'.

Я надеюсь, что это помогает, и я уверен, что мое объяснение может быть улучшено немного!

Кит Гоган
источник
6
Одни ниты: продолжения с разделителями - это функции, но неограниченные продолжения - это не: okmij.org/ftp/continuations/undelimited.html#delim-vs-undelim
Фрэнк Шиарар,
2
Неплохо подмечено. Тем не менее, в большинстве практических приложений, когда люди говорят «продолжение», они говорят о частичных / разделенных продолжениях. Внесение различных других видов продолжений могло бы несколько усложнить объяснение.
Кит Гоган
1
Продолжения не являются функциями, хотя их можно преобразовать в функции. «Тем не менее, в большинстве практических приложений, когда люди говорят« продолжение », они говорят о частичных / разделенных продолжениях». Не могли бы вы указать на такое использование термина «продолжение»? Я никогда не встречал такого использования. Также вы привели пример неограниченного продолжения, используя call / cc. Операторами продолжения с разделителями обычно являются «сброс» и «сдвиг» (они могут иметь другие имена).
Иванчо
3
Начнем с того, что прошло пять лет с тех пор, как я написал это. Вы немного опоздали на вечеринку. Во-вторых, я знаю, что неограниченные продолжения не являются функциями, но вы пытаетесь объяснить, как они работают, не ссылаясь на них как таковые, при этом сохраняя язык простым и понятным. С точки зрения обычного программиста, тот факт, что неограниченное продолжение не возвращает, просто делает его одноразовой функцией, что неверно с точки зрения определения функции, но, по крайней мере, понятно .
Кит Гоган
2
Я не опаздываю на вечеринку, так как это первый результат, который я получаю в Google, когда я ищу "coroutine vs generator". Я надеялся найти хорошую информацию об их различиях. Во всяком случае, я нашел это в другом месте. И я не первый, кто указывает, что ваше объяснение о продолжениях неверно. Проблема в том, что кто-то поймет это неправильно и, возможно, будет сбит с толку позже, когда она или он встретит одно и то же слово, используемое для чего-то другого.
Иванчо
33

Сопрограмма - это одна из нескольких процедур, которые по очереди выполняют свою работу, а затем делают паузу, чтобы передать контроль другим сопрограммам в группе.

Продолжение - это «указатель на функцию», которую вы передаете какой-либо процедуре, которая должна быть выполнена («продолжено с помощью») после выполнения этой процедуры.

Генератор (в .NET) - это языковая конструкция, которая может выплевывать значение, «приостанавливать» выполнение метода и затем переходить из той же точки, когда запрашивается следующее значение.

zvolkov
источник
Я понимаю, что ответ может быть не точным, но на этом уровне вопроса я старался сделать его простым. Кроме того, я сам не очень понимаю все это :)
zvolkov
Генератор в python похож на версию C #, но реализован в виде специального синтаксиса для создания экземпляра объекта итератора, который возвращает значения, возвращаемые определением «function», которое вы предоставляете.
Бенсон
2
Небольшое исправление: «... включая стек вызовов и все переменные, НО НЕ ИХ ЗНАЧЕНИЯ» (или просто отбросьте «все переменные»). Продолжения не сохраняют значения, они просто содержат стек вызовов.
Вскоре
Нет, продолжения не являются «указателем на функцию». В наиболее наивной реализации он содержит указатель на функцию, а среда содержит локальные переменные. И оно никогда не вернется, если вы не используете что-то вроде call / cc для захвата его с возвращаемым значением.
NalaGinrut
9

В более новой версии Python вы можете отправлять значения в Generators с помощью generator.send(), что делает Python Generators эффективно сопрограммами.

Основное отличие между Python Generator и другим генератором, скажем, greenlet, заключается в том, что в python вы yield valueможете только вернуться обратно к вызывающей стороне. Находясь в гринлете, target.switch(value)вы можете перейти к определенной целевой сопрограмме и получить значение, при котором она targetбудет продолжать работать.

Ичуань Ван
источник
3
Но в Python все yieldвызовы должны быть в одной и той же функции, которая называется «Генератор». Вы не можете сделать это yieldиз подфункции, поэтому Python называют полу-сопрограммами , в то время как у Lua есть асимметричные сопрограммы . (Есть предложения по распространению урожая, но я думаю, что это только мутная вода.)
cdunn2001
7
@ cdunn2001: (комментарий Уинстона) В Python3.3 введено выражение «yield from», которое позволяет вам выводить данные из суб-генератора.
Линус Колдуэлл