Первое, что нужно понять о монаде продолжения, это то, что, по сути, на самом деле ничего не делает вообще.Это правда!
Основная идея продолжения в целом состоит в том, что оно представляет остальную часть вычисления .Скажем, у нас есть такое выражение: foo (bar x y) z
.Теперь извлеките только часть в скобках, bar x y
- это часть всего выражения, но это не просто функция, которую мы можем применить.Вместо этого нам нужно применить функцию к .Таким образом, мы можем говорить об «остальной части вычисления» в этом случае как о \a -> foo a z
, который мы можем применить к bar x y
для восстановления полной формы.
Теперь, случается, что это понятие«Остальная часть вычислений» полезна, но работать с ней неудобно, поскольку это нечто за пределами подвыражения, которое мы рассматриваем.Чтобы заставить вещи работать лучше, мы можем вывернуть вещи наизнанку: извлечь интересующее нас подвыражение, а затем обернуть его в функцию, которая принимает аргумент, представляющий остальную часть вычисления: \k -> k (bar x y)
.
Эта измененная версия дает нам большую гибкость - она не только извлекает подвыражение из своего контекста, но и позволяет манипулировать этим внешним контекстом внутри самого подвыражения .Мы можем думать об этом как о как о приостановленном вычислении , что дает нам явный контроль над тем, что происходит дальше.Теперь, как мы можем обобщить это?Что ж, подвыражение практически не изменилось, поэтому давайте просто заменим его параметром для функции наизнанку, что даст нам \x k -> k x
- другими словами, не более чем приложение функции, в обратном порядке .Мы могли бы так же легко написать flip ($)
или добавить немного экзотического иноязычного аромата и определить его как оператор |>
.
Теперь это было бы просто, хотя и утомительно и ужасно запутанно,переведите каждый фрагмент выражения в эту форму.К счастью, есть лучший способ.Как программисты на Haskell, когда мы думаем построение вычислений в фоновом контексте , следующее, что мы думаем, это Скажите, это монада? И в этом случае ответ да, да, это так.
Чтобы превратить это в монаду, мы начнем с двух основных строительных блоков:
- Для монады
m
, значение типа m a
означает наличие доступа к значению типа a
в контексте монады. - Ядром наших «приостановленных вычислений» является перевернутое приложение-функция.
Что делаетэто значит иметь доступ к чему-то типа a
в этом контексте?Это просто означает, что для некоторого значения x :: a
мы применили flip ($)
к x
, что дает нам функцию, которая принимает функцию, которая принимает аргумент типа a
, и применяет эту функцию к x
,Допустим, у нас есть приостановленное вычисление, содержащее значение типа Bool
.Какой тип это нам дает?
> :t flip ($) True
flip ($) True :: (Bool -> b) -> b
Так что для приостановленных вычислений тип m a
работает до (a -> b) -> b
... что, возможно, является антиклимаксом, так как мы уже знали сигнатуру для Cont
, но покажите мне.
Интересно отметить, что своего рода «обращение» также применимо к типу монады: Cont b a
представляет функцию, которая принимает функцию a -> b
и оцениваетдо b
.Как продолжение представляет «будущее» вычислений, так и тип a
в сигнатуре представляет в некотором смысле «прошлое».
Итак, заменяя (a -> b) -> b
на Cont b a
, что является монадическимтип для нашего основного строительного блока приложения обратной функции?a -> (a -> b) -> b
переводит в a -> Cont b a
... ту же сигнатуру типа, что и 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
- это сумасшедший перевернутый мир, в котором у всех есть бородки, поэтому то, что происходит на самом деле, включает в себя путаницу вещей в замешательстве, чтобы соединить два приостановленных вычисления в новое приостановленное вычисление, но, по сути, там 1109 * на самом деле ничего необычного не происходит! Применение функций к аргументам, хо хам, еще один день из жизни функционального программиста.