Стратегия связывания для 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
?