Продолжения в Clojure

Я где-то читал, что богатый Хикки сказал:

«Я думаю, что продолжения могут быть хороши в теории, но не на практике»

Я не знаком с clojure.
1. Есть ли в clojure продолжения?
2. Если нет, то нужны ли вам продолжения? Я видел много хороших примеров, особенно от этого парня. Какая альтернатива?
3. Если да, то есть ли документация?


person unj2    schedule 23.07.2009    source источник
comment
Название вашего вопроса, похоже, не имеет ничего общего с текстом вопроса.   -  person    schedule 23.07.2009


Ответы (7)


Говоря о продолжениях, вам придется различать два их вида:

  • Первоклассные продолжения — поддержка продолжений, глубоко интегрированная в язык (Scheme или Ruby). Clojure не поддерживает продолжения первого класса.

  • Стиль передачи продолжения (CPS) — CPS — это просто стиль кодирования, и любой язык, поддерживающий анонимные функции, допускает этот стиль (что также относится к Clojure).

Примеры:

-- Standard function
double :: Int -> Int
double x = 2 * x

-- CPS-function – We pass the continuation explicitly
doubleCPS :: Int -> (Int -> res) -> res
doubleCPS x cont = cont (2 * x)
; Call
print (double 2)

; Call CPS: Continue execution with specified anonymous function
double 2 (\res -> print res)

Прочтите продолжение в Википедии.

Я не думаю, что продолжения необходимы для хорошего языка, но особенно первоклассные продолжения и CPS в функциональных языках, таких как Haskell, могут быть весьма полезными (пример интеллектуального поиска с возвратом).

person Dario    schedule 23.07.2009
comment
Что касается второго из двух ваших пунктов, не потребуется ли оптимизация хвостового вызова, чтобы сделать CPS пригодным для использования в общем случае? Рассмотрим стандартную функцию, которую вы заключаете в блок while (!aborted), чтобы она повторялась до тех пор, пока не будет прервана. Разве эквивалентным использованием функции в стиле CPS не будет отправка самой функции в качестве продолжения? Таким образом, вы получаете неограниченную рекурсию, и потребуется оптимизация хвостового вызова. Я просто думаю здесь вслух, поэтому, пожалуйста, скажите мне, имеет ли это смысл :-) - person harms; 23.07.2009

Я написал порт cl-cont на Clojure, который добавляет продолжения в Common Lisp.

https://github.com/swannodette/delimc

person dnolen    schedule 24.07.2009

Абстрактные продолжения

Продолжения — это абстрактное понятие, которое используется для описания семантики потока управления. В этом смысле они и существуют, и не существуют (помните, что они абстрактны) в любом языке, который предлагает управляющие операторы (как и любой полный по Тьюрингу язык), точно так же, как числа существуют (как абстрактные объекты) и не существуют (как материальные сущности).

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

Продолжение первого класса

Если понятие продолжения овеществлено как первоклассный объект в языке, тогда у нас есть инструмент, на котором можно построить все виды эффектов контроля. Например, если в языке есть первоклассные продолжения, но нет исключений, мы можем создавать исключения поверх продолжений.

Задачи с продолжениями первого класса

Несмотря на то, что первоклассные продолжения во многих случаях являются мощным и полезным инструментом, их представление в языке также имеет некоторые недостатки:

  • Различные абстракции, построенные поверх продолжений, могут привести к неожиданному/неинтуитивному поведению при составлении. Например, блок finally может быть пропущен, если я использую продолжение для прерывания вычисления.
  • Если текущее продолжение может быть запрошено в любое время, то среда выполнения языка должна быть структурирована так, чтобы можно было в любое время создать некоторое представление структуры данных текущего продолжения. Это накладывает некоторую нагрузку на время выполнения функции, которая, к лучшему или к худшему, часто считается «экзотической». Если язык размещается (например, Clojure размещается на JVM), то это представление должно соответствовать структуре, предоставляемой платформой размещения. Также могут быть другие функции, которые язык хотел бы поддерживать (например, C-взаимодействие), которые ограничивают пространство решений. Подобные проблемы увеличивают вероятность «несоответствия импеданса» и могут серьезно усложнить разработку эффективного решения.

Добавление первоклассных продолжений к языку

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

Например, библиотека delimc Дэвида Нолена реализует продолжение частей программы Clojure с разделителями посредством серии макропреобразований. Аналогичным образом я написал pulley.cps, макрокомпилятор, преобразующий код в CPS вместе с библиотекой времени выполнения для поддержки дополнительных основных функций Clojure (таких как обработка исключений), а также взаимодействия с собственным кодом Clojure.

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

pulley.cps имеет тенденцию к последнему, хотя были предприняты некоторые попытки позволить пользователю управлять этим. Например, можно запретить коду CPS вызывать собственный код. Кроме того, предусмотрен механизм предоставления CPS-версий существующих встроенных функций.

В языке с достаточно строгой системой типов (таком как Haskell) можно использовать систему типов для инкапсуляции вычислений, которые могут использовать управляющие операции (т. е. продолжения) из функционально чистого кода.

Резюме

Теперь у нас есть информация, необходимая для прямого ответа на ваши три вопроса:

  1. Clojure не поддерживает первоклассные продолжения по практическим соображениям.
  2. Все языки построены на продолжениях в теоретическом смысле, но лишь немногие языки представляют продолжения как первоклассные объекты. Однако к любому языку можно добавить продолжения, например, путем преобразования в CPS.
  3. Ознакомьтесь с документацией для delimc и/или pulley.cps.
person Nathan Davis    schedule 19.05.2016
comment
На это ушло почти семь лет, но мы наконец-то получили правильный и исчерпывающий ответ на этот вопрос. Отлично! Это должно стать принятым ответом. - person Ben Kovitz; 20.05.2016
comment
@BenKovitz Спасибо. Я обдумывал, давать ли ответ, учитывая возраст вопроса. Ваш комментарий меня радует. - person Nathan Davis; 20.05.2016

Является ли продолжение необходимым свойством языка?

Нет. Во многих языках нет продолжений.

Если нет, то разве вам не нужны продолжения? Я видел много хороших примеров, особенно от этого парня. Какова альтернатива?

Стек вызовов

person Draemon    schedule 23.07.2009

Обычно продолжения используются в реализации управляющих структур для: возврата из функции, выхода из цикла, обработки исключений и т. д. Большинство языков (таких как Java, C++ и т. д.) предоставляют эти функции как часть основного языка. Некоторые языки этого не делают (например, Scheme). Вместо этого эти языки представляют продолжения как объекты первого класса и позволяют программисту определять новые управляющие структуры. Таким образом, Scheme следует рассматривать как инструментарий языка программирования, а не как полноценный язык сам по себе.

В Clojure нам почти никогда не нужно использовать продолжения напрямую, потому что почти все управляющие структуры предоставляются комбинацией языка и виртуальной машины. Тем не менее, первоклассные продолжения могут быть мощным инструментом в руках компетентного программиста. Особенно в Scheme продолжения лучше, чем эквивалентные аналоги в других языках (например, пара setjmp/longjmp в C). Эта статья содержит больше подробности об этом.

Кстати, будет интересно узнать, как Рич Хики обосновывает свое мнение о продолжениях. Любые ссылки для этого?

person Vijay Mathew    schedule 05.08.2009
comment
Какие функции необходимо иметь, чтобы язык был полноценным? - person unj2; 05.08.2009
comment
Он должен был быть полным, а не полным. Когда я сказал «полный», я не имел в виду «полный по Тьюрингу». Я имел в виду функции, которые люди обычно ожидают от современных языков. Например, обработка исключений с помощью try-catch. В Scheme это не предусмотрено, но с помощью первоклассных продолжений вы можете реализовать свой собственный механизм обработки исключений. - person Vijay Mathew; 06.08.2009
comment
Хорошие лисперы пишут программы, развивая язык в соответствии с задачей. Другими словами, вы превращаете Лисп в язык, который лучше всего подходит для решения поставленной задачи. Это большая разница между Лиспом и другими языками. Пол Грэм очень подробно объясняет это в своей книге «О Лиспе». Я не знаю, кто вы и чем занимаетесь, но я полагаю, что вы не написали на Лиспе ни одной значимой программы. Иначе мне не пришлось бы это объяснять. Я поддерживаю свою собственную схему и ежедневно использую ее для решения реальных проблем. - person Vijay Mathew; 07.08.2009
comment
Кстати, я не думаю, что это хорошая идея просить кого-то объяснить свою точку зрения после того, как назвал ее бессмысленной бессвязностью. - person Vijay Mathew; 07.08.2009
comment
Относительно того, что почти все структуры управления предоставляются комбинацией языка и виртуальной машины, вы забыли добавить, что никому никогда не понадобится более 640 КБ ОЗУ. - person Eli Barzilay; 04.12.2009

Clojure (точнее clojure.contrib.monads) имеет монаду-продолжение; вот статья, в которой описывается его использование и мотивация.

person pmf    schedule 04.12.2009

Ну... Clojure -> реализует то, что вам нужно... Но вместо этого с помощью макроса

person zcaudate    schedule 07.01.2014
comment
Я думаю, что это правильный ответ. Результат одного шага передается на вход следующего шага. Эффект представляет собой механизм продолжения, достигаемый за счет перезаписи кода. - person zcaudate; 08.01.2014
comment
Я не думаю, что это -> действительно отражает то, что нужно ОП. В лучшем случае -> похож на модад Identity. Теперь, если бы вы могли заменить -> на использование любой произвольной монады, я бы согласился с вами, что -> было бы достаточно для выражения любых вычислений, использующих продолжения (вы могли бы просто использовать монаду Continuation). Но ->, реализованный в Clojure, не позволяет приостанавливать и возобновлять вычисления. Первоклассные продолжения дают вам полный контроль над тем, какие части вычислений выполняются и когда они выполняются. Это полностью отсутствует с ->. - person Nathan Davis; 15.09.2016