【问题标题】:Why ContT doesn't deal with the inner monad?为什么 ContT 不处理内部单子?
【发布时间】:2015-01-20 13:09:34
【问题描述】:

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

按照其他 Monad Transformers 的类比,我会以这种方式实现它:

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 Transformer 非常不同,注意 functor 实例是:

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

而不是:

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

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

即使IdentityT 也是如此,但我读到Cont 的绑定策略实际上与Identity 的绑定策略相同,那么ContTIdentityT 呢?

【问题讨论】:

    标签: haskell monad-transformers


    【解决方案1】:

    (&gt;&gt;=) 不需要处理内部 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 实例,我们已经可以任意操纵当前的延续,因此替代实现几乎无法为我们带来任何好处。

    【讨论】:

    • 谢谢。我暂时将此标记为已回答。但如果有人提供更多详细信息,我很乐意切换到另一个答案
    • ContT r m 实际上是一个单子,即使 m 不是。这实际上是在Text.ParserCombinators.ReadP 模块中使用的:ReadP 基本上是forall r . ContT r PP 确实有一个 Monad 实例但不是一个有效的实例,但 ReadPMonad 实例是有效的。
    【解决方案2】:

    让我们检查一下(&gt;&gt;=) 的建议实现的类型:

    > 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 以匹配 (&gt;&gt;=) 的类型。

    同样适用于return

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

    上面多了一个m1

    【讨论】:

    • 感谢您的回答,但我更感兴趣的是它以这种方式实现的原因,而不是对我的示例代码的代码审查。我想知道为什么采用绑定策略。我不认为这是因为类型,类型通常是根据绑定策略的功能决定的,而不是相反。您可以交换类型变量的顺序,但忘记细节,即标题中的主要问题。
    • @Gustavo 如果你想instance Monad m =&gt; Monad (ContT r m),那么返回和绑定操作必须具有这些类型。也许您的实际问题是“为什么不将ContT r m a 定义为(m a -&gt; m r) -&gt; m r”或类似的允许您的绑定功能?如果是这样,你应该在你的问题中清楚它,因为我认为它是关于我们所知道的ContT(绑定/返回除外),而不是关于不同的 monad 转换器。如果可能,建议您将用于ContT 的类型。
    • 谢谢@chi。实际上我什么都不想要;)看起来通过提供更多细节我被误解了。我没有提出任何建议,也许我应该删除我的代码。我只是想知道为什么决定以与其他 monad 转换器非常不同的方式工作。必须有充分的理由打破类比,尤其是 Functor 和 Applicative。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 2018-07-06
    • 1970-01-01
    • 2012-05-17
    • 2020-03-28
    • 2015-12-15
    相关资源
    最近更新 更多