Почему ContT не работает с внутренней монадой?

Стратегия связывания для ContT игнорирует внутреннюю монаду, фактически код такой же, как для Cont.

Следуя аналогии с другими преобразователями монад, я бы реализовал это так:

return x = ContT ($ (return x))
(>>=) x f = ContT (\k -> runContT x ((=<<) (\a -> runContT (f a) k)))

Тогда, например, это выражение:

do 
    x1 <- ContT (\k -> k [1, 2])
    x2 <- ContT (\k -> k [10, 20])
    return ((+) x1 x2) 

Результатом будет [11, 21, 12, 22]

У меня вопрос: в чем причина этого дизайнерского решения? Почему он был реализован таким образом, что сильно отличает его от других преобразователей монад, обратите внимание, что экземпляр функтора:

fmap f m = ContT $ \c -> runContT m (c . f)

скорее, чем:

fmap f m = ContT $ runCont $ (fmap . fmap) f (Cont (runContT m))

что более или менее похоже на другие преобразователи монад, но текущая реализация, похоже, нарушает композицию функторов, я имею в виду, что поскольку монады не компонуются автоматически, мы можем обсуждать различные стратегии связывания, но для функторов и аппликативов это всегда должно быть то же самое, а здесь, кажется, совсем другое. Было ли это попыткой получить какой-то код, который представляет больше работающих вариантов использования?

Даже IdentityT работает таким образом, но я читал, что стратегия привязки для Cont на самом деле такая же, как для Identity, так что насчет ContT и IdentityT?


person Gus    schedule 20.01.2015    source источник


Ответы (2)


(>>=) не нужно иметь дело с внутренней монадой (кроме того, ее невозможно реализовать так, как вы предложили, как показал chi), потому что мы можем просто lift монадические значения и получить желаемую семантику.

lift :: Monad m => m a -> ContT r m a
lift ma = ContT (ma >>=)

Или как в стандартной библиотеке:

instance MonadTrans (ContT r) where
    lift m = ContT (m >>=)

Теперь у нас есть

import Control.Monad.Trans.Cont
import Control.Monad.Trans.Class

test :: ContT r [] Int
test = do
  x <- lift [1, 2]
  y <- lift [10, 20]
  return (x + y)

-- evalContT test == [11, 21, 12, 22]

Другими словами, со стандартным экземпляром монады для ContT мы уже можем манипулировать текущими продолжениями произвольно, поэтому альтернативные реализации вряд ли могут нам что-то купить.

person András Kovács    schedule 20.01.2015
comment
Спасибо. Я пока помечаю это как ответ. Но я с радостью перейду к другому ответу, если кто-то предоставит более подробную информацию. - person Gus; 24.01.2015
comment
ContT r m на самом деле монада, даже если m - нет. Фактически это используется в модуле Text.ParserCombinators.ReadP: ReadP в основном forall r . ContT r P. У P есть экземпляр Monad, но он недействителен, но экземпляр Monad для ReadP действителен. - person Jeremy List; 20.10.2016

Проверим типы предлагаемой реализации (>>=):

> let foo x f = ContT (\k -> runContT x ((=<<) (\a -> runContT (f a) k)))
> :t foo
foo
  :: Monad m =>
     ContT r m (m a) -> (a -> ContT r m a1) -> ContT r m a1
     ^^^^^^^^^^^^^^^

Это должно быть ContT r m a, чтобы соответствовать типу для (>>=).

Аналогично для return:

> let bar  x = ContT ($ (return x))
> :t bar
bar :: Monad m1 => a -> ContT r m (m1 a)
                        ^^^^^^^^^^^^^^^^

Выше есть дополнительный m1.

person chi    schedule 20.01.2015
comment
Спасибо за ваш ответ, но меня больше интересовала причина, по которой это было реализовано таким образом, чем обзор кода моего примера кода. Я хочу знать, почему была принята эта стратегия привязки. Я не думаю, что это было из-за типов, типы обычно определяются в зависимости от стратегии связывания, а не наоборот. Вы можете поменять местами порядок переменных типа, но забудьте о деталях, главный вопрос в заголовке. - person Gus; 20.01.2015
comment
@Gustavo. Если вы хотите instance Monad m => Monad (ContT r m), тогда операции return и bind должны иметь именно эти типы. Может быть, ваш настоящий вопрос заключается в том, почему ContT r m a не определен как (m a -> m r) -> m r или что-то подобное, что позволило бы использовать вашу функцию привязки? Если это так, вы должны четко указать на это в своем вопросе, так как я думал, что речь идет о ContT, как мы его знаем (за исключением привязки / возврата), а не о другом преобразователе монад. Если возможно, предложите тип, который вы бы использовали для ContT. - person chi; 20.01.2015
comment
Спасибо @chi. На самом деле я ничего не хочу;) Похоже, что из-за более подробной информации меня неправильно поняли. Я ничего не предлагаю, может, стоит удалить свой код. Я просто хочу знать, почему было решено работать таким образом, который сильно отличается от других преобразователей монад. Должна быть веская причина для разрыва аналогии, особенно с функтором и аппликативом. - person Gus; 20.01.2015