Первое, что нужно понять о монаде продолжения, это то, что, по сути, она вообще ничего не делает. Это правда!
Основная идея продолжения в целом состоит в том, что оно представляет остальную часть вычисления. Допустим, у нас есть такое выражение: foo (bar x y) z
. Теперь извлеките только заключенную в скобки часть _2 _ - это часть всего выражения, но это не просто функция, которую мы можем применить. Вместо этого нам нужно применить функцию к. Итак, мы можем говорить об «остальной части вычислений» в этом случае как о \a -> foo a z
, которые мы можем применить к bar x y
, чтобы восстановить полную форму.
Бывает, что эта концепция «остальной части вычислений» полезна, но с ней неудобно работать, поскольку это что-то за пределами рассматриваемого подвыражения. Чтобы все работало лучше, мы можем вывернуть все наизнанку: извлечь интересующее нас подвыражение, а затем обернуть его в функцию, которая принимает аргумент, представляющий остальную часть вычисления: \k -> k (bar x y)
.
Эта модифицированная версия дает нам большую гибкость - она не только извлекает подвыражение из своего контекста, но и позволяет нам управлять этим внешним контекстом внутри самого подвыражения. Мы можем рассматривать это как своего рода приостановленное вычисление, дающее нам явный контроль над тем, что произойдет дальше. Итак, как мы можем это обобщить? Что ж, подвыражение практически не изменилось, поэтому давайте просто заменим его параметром вывернутой наизнанку функции, что даст нам _6 _-- другими словами, не что иное, как приложение функции, перевернутое. Мы могли бы так же легко написать flip ($)
или добавить немного экзотического иностранного языка и определить его как оператор |>
.
Теперь было бы просто, хотя и утомительно и ужасно запутать, перевести каждую часть выражения в эту форму. К счастью, есть способ получше. Как программисты Haskell, когда мы думаем о построении вычислений в фоновом контексте, следующее, что мы думаем, это скажем, это монада? И в этом случае ответ будет <сильный > да, да, это так.
Чтобы превратить это в монаду, мы начнем с двух основных строительных блоков:
- Для монады
m
значение типа m a
представляет наличие доступа к значению типа a
в контексте монады.
- Ядро наших «приостановленных вычислений» - это приложение с перевернутой функцией.
Что значит иметь доступ к чему-то типа a
в этом контексте? Это просто означает, что для некоторого значения x :: a
мы применили flip ($)
к x
, дав нам функцию, которая принимает функцию, которая принимает аргумент типа a
, и применяет эту функцию к x
. Допустим, у нас есть приостановленное вычисление, содержащее значение типа Bool
. Какой тип это дает нам?
> :t flip ($) True
flip ($) True :: (Bool -> b) -> b
Итак, для приостановленных вычислений тип m a
работает до _21 _..., что, возможно, является препятствием, поскольку мы уже знали сигнатуру для Cont
, но пока меня посмешите.
Здесь интересно отметить, что своего рода «разворот» также применяется к типу монады: Cont b a
представляет функцию, которая принимает функцию a -> b
и оценивает ее как b
. Поскольку продолжение представляет «будущее» вычисления, так и тип a
в сигнатуре в некотором смысле представляет «прошлое».
Итак, заменив (a -> b) -> b
на Cont b a
, какой монадический тип у нашего основного строительного блока приложения обратной функции? a -> (a -> b) -> b
переводится в _30 _... сигнатура того же типа, что и return
, и, по сути, это именно то, что есть.
С этого момента все в значительной степени напрямую выпадает из типов: по сути, нет разумного способа реализовать >>=
, кроме фактической реализации. Но что он на самом деле делает?
На этом этапе мы возвращаемся к тому, что я сказал изначально: монада продолжения на самом деле ничего не делает. Что-то типа Cont r a
тривиально эквивалентно чему-то просто типу a
, просто путем передачи id
в качестве аргумента приостановленного вычисления. Это может привести к вопросу: если Cont r a
- монада, но преобразование такое тривиальное, не должна ли a
сама по себе также быть монадой? Конечно, это не работает как есть, поскольку нет конструктора типа, который можно было бы определить как экземпляр Monad
, но, скажем, мы добавляем тривиальную оболочку, например data Id a = Id a
. Это действительно монада, а именно тождественная монада.
Что >>=
делает для монады идентичности? Сигнатура типа Id a -> (a -> Id b) -> Id b
, что эквивалентно a -> (a -> b) -> b
, что опять же является простым приложением функции. Установив, что Cont r a
тривиально эквивалентно Id a
, мы можем сделать вывод, что и в этом случае (>>=)
- это просто приложение-функция.
Конечно, Cont r a
- это сумасшедший перевернутый мир, в котором у всех есть козлиные бородки, поэтому на самом деле происходит путаница в путанице, чтобы связать два приостановленных вычисления вместе в новое приостановленное вычисление, но, по сути, здесь нет на самом деле происходит что-то необычное! Применение функций к аргументам, хх, еще один день в жизни функционального программиста.
person
C. A. McCann
schedule
23.07.2010