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}}?
答案 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)
^^^^^^^^^^^^^^^^
。