为什么ContT不处理内部monad?

时间:2015-01-20 13:09:34

标签: haskell monad-transformers

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

按照其他Monad变形金刚的比喻,我会用这种方式实现它:

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变形金刚非常不同,请注意,仿函数实例是:

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

而不是:

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

与其他Monad变形金刚或多或少相同,但目前的实现似乎打破了仿函数组合,我的意思是因为monad不会自动编写,我们可能会讨论不同的绑定策略,但对于仿函数和应用程序它应该永远是相同的,在这里它似乎是完全不同的。获得一些代表更多用例的代码是不是很糟糕?

即使IdentityT也是这样,但我读到Cont的绑定策略实际上与Identity相同,那么ContT和{{ 1}}?

2 个答案:

答案 0 :(得分:5)

(>>=)并不需要处理内部monad(除了它不可能以你提出的方式实现它,正如chi所示),因为我们只能{{ 1}} monadic值并获得所需的语义。

lift

或者与标准library

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

现在我们有了

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

换句话说,使用ContT的标准monad实例,我们已经可以随意操作任意的当前延续,因此替代实现几乎不能给我们任何东西。

答案 1 :(得分:4)

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

> 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
     ^^^^^^^^^^^^^^^

应该{​​{1}}匹配ContT r m a的类型。

同样适用于(>>=)

return

上面还有一个> let bar x = ContT ($ (return x)) > :t bar bar :: Monad m1 => a -> ContT r m (m1 a) ^^^^^^^^^^^^^^^^