【问题标题】:Why MonadPlus and not Monad + Monoid?为什么是 MonadPlus 而不是 Monad + Monoid?
【发布时间】:2014-05-26 06:43:29
【问题描述】:

我试图了解MonadPlus 背后的动机。如果已经有类型类MonadMonoid,为什么还需要?

当然,Monoid 的实例是具体类型,而Monad 的实例需要单个类型参数。 (请参阅Monoid vs MonadPlus 以获得有用的解释。)但是你不能重写任何类型约束

(MonadPlus m) => ...

作为MonadMonoid 的组合?

(Monad m, Monoid (m a)) => ...

Control.Monad 中的guard 函数为例。它的实现是:

guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero

我只能使用 MonadMonoid 来实现它:

guard' :: (Monad m, Monoid (m ())) => Bool -> m ()
guard' True = return ()
guard' False = mempty

有人可以澄清MonadPlusMonad + Monoid 之间的真正区别吗?

【问题讨论】:

    标签: haskell monads monoids monadplus


    【解决方案1】:

    但是你不能重写任何类型约束

    (MonadPlus m) => ...
    

    作为 Monad 和 Monoid 的组合?

    没有。在您链接的问题的最佳答案中,已经对 MonadPlus 与 Monoid 的定律进行了很好的解释。但是,即使我们忽略类型类法则,也会存在差异。

    Monoid (m a) => ... 意味着m a 必须是调用者选择的特定a 的一个幺半群,但MonadPlus m 意味着m a 必须是所有a 的一个幺半群。所以MonadPlus a更灵活,这种灵活性在四种情况下很有帮助:

    1. 如果我们不想告诉调用者我们打算使用什么a
      MonadPlus m => ... 而不是Monoid (m SecretType) => ...

    2. 如果我们想使用多个不同的a
      MonadPlus m => ... 而不是(Monoid (m Type1), Monoid (m Type2), ...) => ...

    3. 如果我们想使用无限多个不同的a
      MonadPlus m => ... 而不是不可能。

    4. 如果我们不知道我们需要什么aMonadPlus m => ... 而不是不可能。

    【讨论】:

    • 如果你不介意的话,我真的很想在这里看到一个具体的例子。您能否提供一些使用特定 Monad 的实例,其中 MonadPlus 比 Monoid 更有用或更清洁?
    • @Fresheyeball:不,我无法提供“使用特定 Monad 的某些实例”,抱歉。如果 monad 是已知的,则不需要对 m 进行抽象,也不需要使用任何类型类来指定 m 的预期接口,因此 MonadPlusMonoid 之间的区别在你的代码中并不重要仅适用于一个特定的 monad。
    • 我明白这一点。但是如果不了解具体的 monad 如何与通用代码一起工作,就很难将这种做法概念化。
    【解决方案2】:

    您的guard' 与您的Monoid m a 类型不匹配。

    如果您的意思是Monoid (m a),那么您需要定义mempty 是什么m ()。完成此操作后,您就定义了 MonadPlus

    换句话说,MonadPlus 定义了两个操作:mzeromplus 满足两个规则:mzero 相对于 mplus 是中性的,mplus 是关联的。这满足Monoid 的定义,因此mzeromemptymplusmappend

    不同之处在于MonadPlus m 是任何a 的幺半群m a,但Monoid m 只为m 定义了一个幺半群。您的 guard' 有效,因为您只需要 m 成为 Monoid 仅适用于 ()。但是MonadPlus 更强大,它声称m a 是任何a 的幺半群。

    【讨论】:

      【解决方案3】:

      使用the QuantifiedConstraints language extension,您可以表示Monoid (m a) 实例必须在a 的所有选择中保持一致:

      {-# LANGUAGE QuantifiedConstraints #-}
      
      class (Monad m, forall a. Monoid (m a)) => MonadPlus m
      
      mzero :: (MonadPlus m) => m a
      mzero = mempty
      
      mplus :: (MonadPlus m) => m a -> m a -> m a
      mplus = mappend
      

      Alternatively,我们可以为所有这样的 monoid-monads 实现“真正的”MonadPlus 类:

      {-# LANGUAGE GeneralizedNewtypeDeriving, DerivingStrategies, QuantifiedConstraints #-}
      {-# LANGUAGE UndecidableInstances #-}
      
      import Control.Monad
      import Control.Applicative
      
      newtype MonoidMonad m a = MonoidMonad{ runMonoidMonad :: m a }
          deriving (Functor, Applicative, Monad)
      
      instance (Applicative m, forall a. Monoid (m a)) => Alternative (MonoidMonad m) where
          empty = MonoidMonad mempty
          (MonoidMonad x) <|> (MonoidMonad y) = MonoidMonad (x <> y)
      
      instance (Monad m, forall a. Monoid (m a)) => MonadPlus (MonoidMonad m)
      

      请注意,根据您选择的m,这可能会或可能不会给您期望的MonadPlus;例如,MonoidMonad [][] 完全相同;但是对于MaybeMonoid 实例通过人为地赋予它一个标识元素来提升一些底层半群,而MonadPlus 实例是左偏选择;所以我们必须使用MonoidMonad First 而不是MonoidMonad Maybe 来获取正确的实例。

      【讨论】:

        【解决方案4】:

        编辑:所以灯泡打开了,一切都咔哒一声就位。我完全误解了 Toxaris 的回答。现在我明白了,除了一些支持示例之外,我没有什么要补充的了,并且观察到 MonadPlus 类型类定义中似乎不需要 AlternativeMonad 约束。

        monadPlusExample :: (MonadPlus m) => m Int
        monadPlusExample = do
          x <- idM 7
          f <- idM (* 6)
          return $ f x
          where
            idM a = return a `mplus` mzero
        
        -- a Monoid constraint for each lifted type
        monoidExample :: (Monad m, Monoid (m Int), Monoid (m (Int -> Int))) => m Int
        monoidExample = do
          x <- idM 7
          f <- idM (* 6)
          return $ f x
          where
            idM a = return a <> mempty
        
        -- yes, QualifiedConstraints unifies the Monoid constraint quite nicely
        monoidExample2 :: (Monad m, forall a. Monoid (m a)) => m Int
        monoidExample2 = do
          x <- idM 7
          f <- idM (* 6)
          return $ f x
          where
            idM a = return a <> mempty
        

        为了被调用,它们必须使用具体类型(例如monoidExample2 :: [Int]

        【讨论】:

        • 使用相当新的QuantifiedConstraints 扩展,您可以编写类似(Monad m, forall a. Monoid (m a)) 的约束。如果没有那个扩展,只使用Monoid (m a),你可能会遇到多态递归的问题。当你需要多态递归时,很多事情变得很重要!
        • 需要扩展才能使数字类型类正常工作是另一个话题......
        • 这些扩展中的大多数都非常好。 QuantifiedConstraints 的边缘还是有点粗糙。但我真的认为你在这里的回答没有抓住我的意思。假设你有类似data Foo m a = Stop [m a] | Go (Foo m (a, a)) 的东西。现在想象一下,无论出于何种原因,您都需要能够在Stop 构造函数中组合列表的元素。除非你使用QuantifiedConstraints,否则没有办法给Monoid 约束让你这样做——你需要像MonadPlus 这样的东西。 QuantifiedConstraints 会改变游戏规则吗?好吧,我们拭目以待;太新了。
        • 我的问题不在于扩展本身。这与类型类对它们的需求有关。
        • @Sledge 我不明白为什么您对需要扩展的类型类的问题与此处相关。 (Monad m, Monoid (m a)) 并不完全等同于 MonadPlus m,因为前者可以由调用者以取决于特定 a 的方式满足,而后者不能(并且这两种情况都不是 更好 比另一个,它们只是不同)。对于QuantifiedConstraints,使用Monad + Monoid 有一个不同 约束,我们现在可以写出更接近MonadPlus 等价性,但我们一直在讨论的类都不需要任何约束扩展以正常工作。
        猜你喜欢
        • 2012-10-18
        • 2013-06-08
        • 1970-01-01
        • 2012-08-26
        • 1970-01-01
        • 1970-01-01
        • 2018-01-24
        • 2016-01-01
        • 1970-01-01
        相关资源
        最近更新 更多