为什么 ContT 不处理内部 monad?

Why ContT doesn't deal with the inner monad?

ContT 的绑定策略忽略了内部 monad,实际上代码与 Cont 相同。

根据其他 Monad Transformer 的类比,我会这样实现它:

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]

我的问题是该设计决策背后的原因是什么?为什么它是这样实现的,这使得它与其他 Monad Transformers 非常不同,请注意,仿函数实例是:

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

而不是:

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

这与其他 Monad Transformer 或多或少相同,但当前的实现似乎打破了函子组合,我的意思是因为 monad 不会自动组合,我们可能会讨论不同的绑定策略,但对于函子和应用程序它应该总是一样的,在这里它似乎完全不同。获得一些代表更多用例的代码是否是一种黑客攻击?

即使 IdentityT 也是这样工作的,但我读到 Cont 的绑定策略实际上与 Identity 相同,那么 ContTIdentityT?

让我们检查建议的 (>>=) 实现的类型:

> 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

(>>=) 不需要处理内部 monad(除此之外不可能按照您建议的方式实现它,如 chi 所示),因为我们可以 lift monadic值并获得所需的语义。

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

或标准中的 library:

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 的标准 monad 实例,我们已经可以任意 操纵当前延续,因此替代实现几乎无法为我们买任何东西。