Как уже упоминалось в Сущность функционального программирования :
Программирование с помощью монад, сильно напоминающих стиль продолжения - прохождения (CPS), и в этой статье рассматривается взаимосвязь между ними. В некотором смысле они эквивалентны: CPS возникает как особый случай монады, и любая монада может быть встроена в CPS путем изменения типа ответа. Но монадический подход обеспечивает дополнительную проницательность и обеспечивает более высокую степень контроля.
Эта статья довольно строгая, и на самом деле она не раскрывает отношения между CPS и монадами. Здесь я попытаюсь привести неофициальный, но наглядный пример:
(Примечание: ниже дано понимание Monad от новичка (меня самого), хотя после написания он выглядит как ответ одного из тех пользователей с высоким повторением. Пожалуйста, возьмите его с тонной соли)
Рассмотрим классическую Maybe
монаду
-- I don't use the do notation to make it
-- look similar to foo below
bar :: Maybe Int
bar =
Just 5 >>= \x ->
Just 4 >>= \y ->
return $ x + y
bar' :: Maybe Int
bar' =
Just 5 >>= \x ->
Nothing >>= \_ ->
return $ x
GHCi> bar
Just 9
GHCi> bar'
Nothing
Таким образом, вычисление останавливается, как только встречается Nothing
, здесь нет ничего нового. Давайте попробуем имитировать такое монадическое поведение, используя CPS:
Вот наша ванильная add
функция с использованием CPS. Мы используем все Int
здесь вместо алгебраического типа данных, чтобы упростить:
add :: Int -> Int -> (Int -> Int) -> Int
add x y k = k (x+y)
GHCi> add 3 4 id
7
Обратите внимание, насколько она похожа на монаду
foo :: Int
foo =
add 1 2 $ \x -> -- 3
add x 4 $ \y -> -- 7
add y 5 $ \z -> -- 12
z
GHCi> foo
12
OK. Предположим, что мы хотим, чтобы вычисление было ограничено 10. То есть, любое вычисление должно быть остановлено, когда следующий шаг приведет к значению, большему 10. Это похоже на выражение «вычисление Maybe должно остановиться и вернуть Nothing
как как только любое значение в цепочке будет Nothing
). Давайте посмотрим, как мы можем это сделать, написав «CPS преобразователь»
cap10 :: (Int -> Int) -> (Int -> Int)
cap10 k = \x ->
if x <= 10
then
let x' = k x in
if x' <= 10 then x' else x
else x
foo' :: Int
foo' =
add 1 2 $ cap10 $ \x -> -- 3
add x 4 $ cap10 $ \y -> -- 7
add y 5 $ cap10 $ \z -> -- 12
undefined
GHCi> foo'
7
Обратите внимание, что окончательное возвращаемое значение может быть undefined
, но это нормально, потому что оценка останавливается на 3-м шаге (z
).
Мы можем видеть, что cap10
"оборачивает" нормальное продолжение некоторой дополнительной логикой. И это очень близко к тому, что монада - склеить вычисления вместе с некоторой дополнительной логикой.
Давайте сделаем еще один шаг:
(>>==) :: ((Int -> Int) -> Int) -> (Int -> Int) -> Int
m >>== k = m $ cap10 k
foo'' :: Int
foo'' =
add 1 2 >>== \x -> -- 3
add x 4 >>== \y -> -- 7
add y 5 >>== \z -> -- 12
undefined
GCHi> foo''
7
Woa! Может быть, мы только что изобрели Cap10
монаду!
Теперь, если мы посмотрим на исходный код Cont , мы увидим, что Cont
равно
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
Тип runCont
является
Cont r a -> (a -> r) -> r
((a -> r) -> r) -> (a -> r) -> r
Что хорошо сочетается с типом нашего >>==
Теперь, чтобы действительно ответить на вопрос
Теперь, набрав все это, я перечитал оригинальный вопрос. ОП попросил «разницу»: P
Полагаю, разница в том, что CPS дает вызывающему больше контроля, тогда как обычно >>=
в монаде полностью контролируется ее автором.