Продолжение (информатика): различия между версиями
[отпатрулированная версия] | [отпатрулированная версия] |
м →Программирование в стиле передачи продолжений: викификация |
→Программирование в стиле передачи продолжений: стилевые правки |
||
Строка 75: | Строка 75: | ||
|} |
|} |
||
Можно заметить, что в чистом CPS фактически не существует самих продолжений — всякий вызов оказывается [[Хвостовая рекурсия|хвостовым]]. Если язык не гарантирует оптимизацию хвостовых вызовов ({{lang-en|Tail call optimization, TCO}}), то |
Можно заметить, что в чистом CPS фактически не существует самих продолжений — всякий вызов оказывается [[Хвостовая рекурсия|хвостовым]]. Если язык не гарантирует оптимизацию хвостовых вызовов ({{lang-en|Tail call optimization, TCO}}), то при каждом вложенном вызове <code>callcc</code> растёт и само продолжение, и [[стек вызовов]]. Обычно это нежелательно, но временами используется интересным способом (см. {{iw|Chicken (реализация языка Scheme)|компилятор Chicken Scheme|en|Chicken (Scheme implementation)}}). Совместное использование стратегий оптимизации TCO и CPS позволяет полностью устранить динамический стек из исполнимой программы. Ряд компиляторов [[Функциональное программирование|функциональных языков]] работает именно таким образом, к примеру компилятор SML/NJ для языка [[Standard ML]]. |
||
== Ограниченные и неограниченные продолжения == |
== Ограниченные и неограниченные продолжения == |
Версия от 16:15, 22 апреля 2014
Продолжение (англ. continuation) представляет состояние программы в определённый момент, которое может быть сохранено и использовано для перехода в это состояние. Продолжения содержат всю информацию, чтобы продолжить выполнения программы с определённой точки. Состояние глобальных переменных обычно не сохраняется, однако для функциональных языков это несущественно (выборочное сохранение/восстановление значений глобальных объектов в Scheme достигается отдельным механизмом dynamic-wind). Продолжения похожи на goto
Бейсика или setjmp/longjmp
Си, так как также позволяют перейти в любое место программы. Но продолжения, в отличие от goto
, позволяют перейти только в участок программы с определённым состоянием, которое должно быть сохранено заранее, в то время, как goto
позволяет перейти в участок программы с неинициализированными переменными.
Scheme была первым промышленным языком, в котором реализованы полноценные продолжения. Bruce Duba ввёл продолжения в Standard ML.
Понятие
Формально, callcc
— это функция высшего порядка, позволяющая абстрагировать динамический контекст имеющейся функции в виде другой функции, которая и называется «продолжением».
Более наглядно, продолжение — это «вся оставшаяся часть программы от данной точки», или «функция, которая никогда не возвращает управление в точку своего вызова»[1]. В процессе изучения функционального программирования многие испытывают трудности с пониманием сущности продолжений. Традиционное объяснение этого понятия сводится к «расширению (усложнению) понятия сопрограммы», но в педагогическом смысле такое объяснение считается бесполезным[1]. Причина трудности понимания заключается том, что продолжения фактически представляют собой альтернативное обоснование понятия «поведения» («вызова» в самом широком понимании), т.е. иную семантическую модель, и в этом смысле начальный переход от «обычного» функционального программирования к программированию с интенсивным использованием продолжений можно сравнить с начальным переходом от императивного программирования к функциональному.
Продолжения представляют собой математическое обоснование всего порядка выполнения программы, от goto
и циклов до рекурсии, исключений, генераторов?!, сопрограмм и механизма возврата[1]. Как следствие, они позволяют реализовать все эти элементы в языке посредством единой конструкции.
Программирование в стиле передачи продолжений
Программирование в стиле передачи продолжений (англ. Continuation-passing style, CPS) — это стиль программирования, при котором передача управления осуществляется через механизм продолжений. Стиль CPS впервые представили Джеральд Джей Сассман[англ.] и Гай Стил мл.[англ.], одновременно с языком Scheme.
Такой стиль весьма затруднителен для использования человеком, и обычно является стратегией оптимизации в компиляторах.
(define (pyth x y)
(sqrt (+ (* x x) (* y y))))
|
(define (pyth& x y k)
(*& x x (lambda (x2)
(*& y y (lambda (y2)
(+& x2 y2 (lambda (x2py2)
(sqrt& x2py2 k))))))))
|
(define (factorial n)
(if (= n 0)
1 ; NOT tail-recursive
(* n (factorial (- n 1)))))
|
(define (factorial& n k)
(=& n 0 (lambda (b)
(if b ; продолжение растёт
(k 1) ; в рекурсивном вызове
(-& n 1 (lambda (nm1)
(factorial& nm1 (lambda (f)
(*& n f k)))))))))
|
(define (factorial n)
(f-aux n 1))
(define (f-aux n a)
(if (= n 0)
a ; tail-recursive
(f-aux (- n 1) (* n a))))
|
(define (factorial& n k) (f-aux& n 1 k))
(define (f-aux& n a k)
(=& n 0 (lambda (b)
(if b ; продолжение сохраняется
(k a) ; в рекурсивном вызове
(-& n 1 (lambda (nm1)
(*& n a (lambda (nta)
(f-aux& nm1 nta k)))))))))
|
Можно заметить, что в чистом CPS фактически не существует самих продолжений — всякий вызов оказывается хвостовым. Если язык не гарантирует оптимизацию хвостовых вызовов (англ. Tail call optimization, TCO), то при каждом вложенном вызове callcc
растёт и само продолжение, и стек вызовов. Обычно это нежелательно, но временами используется интересным способом (см. компилятор Chicken Scheme[англ.]). Совместное использование стратегий оптимизации TCO и CPS позволяет полностью устранить динамический стек из исполнимой программы. Ряд компиляторов функциональных языков работает именно таким образом, к примеру компилятор SML/NJ для языка Standard ML.
Ограниченные и неограниченные продолжения
Существует несколько разновидностей продолжений. Наиболее распространенная из них - неограниченные (undelimited continuations) продолжения, реализуемые с помощью функции call/cc или её аналогов. Такие продолжения действительно представляют собой состояние всей программы (или одной её нити) в определенный момент. Вызов такого продолжения не похож на вызов функции, поскольку он соответствует "прыжку" в сохраненное состояние программы и не возвращает никакого значения; такое продолжение обычно нельзя вызвать несколько раз. Ограниченные (delimited continuations) же продолжения абстрагируют зависимость результата некоторого блока программы от результата некоторого подвыражения этого блока. В определенном смысле они соответствуют сегменту стека вызовов, а не всему стеку. Такие продолжения могут использоваться как функции, вызываться несколько раз и т.п. Они абстрагируются с помощью механизма shift/reset: reset оборачивает внешний блок, shift действует как call/cc, но получает в качестве аргумента не глобальное продолжение, а ограниченное - зависимость значения блока reset от значения на месте блока shift. Существуют и другие разновидности, к примеру prompt/control.
Поддержка языками программирования
Многие языки программирования предоставляют эту возможность под различными именами, например:
- Scheme:
call/cc
(краткая запись дляcall-with-current-continuation
) - Standard ML:
SMLofNJ.Cont.callcc
- Си:
setcontext
et al. (UNIX System V и GNU libc) - Ruby:
callcc
- Smalltalk:
Continuation currentDo:
, в большинстве современных реализаций продолжения могут быть реализованы на чистом Smalltalk, не требуя специальной поддержки в виртуальной машине. - Rhino :
Continuation
- Haskell :
callCC
(в модулеControl.Monad.Cont
) - Factor :
callcc0
иcallcc1
- Python :
yield
- Scala : Существует плагин для поддержки ограниченных продолжений.
- PHP: Есть поддержка.
- C#: конструкции
yield return
иawait
.
В любом языке, поддерживающем замыкания, можно писать программы в стиле передачи продолжений (англ. Continuation-passing style, CPS) и вручную реализовать call/cc
. В частности это принятая практика в Haskell, где легко строятся «монады, передающие продолжения» (для примера, монада Cont
и трансформер монад ContT
библиотеки mtl
).
См. также
Примечания
- ↑ 1 2 3 Fake threads, 1999.
Ссылки
- Продолжение всемирной паутины — о использования продолжений для построения веб-приложений.
- Библиотечка ПФП — в статье "Паттерны использования call with current continuation" (перевод) описана концепция продолжений и дан ряд разнообразных примеров их использования.
- Continuations and Continuation Passing Style — большая коллекция статей о разных видах продолжений и их использовании.
- Tim Peters. Fake threads (was [Python-Dev] ActiveState & fork & Perl). — 1999.