41

我试图了解背后的动机MonadPlusMonad如果已经有 typeclasses和 ,为什么还有必要Monoid

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

(MonadPlus m) => ...

作为和的Monad组合Monoid

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

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

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

我只能使用Monadand来实现它Monoid

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

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

4

4 回答 4

38

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

(MonadPlus m) => ...

作为 Monad 和 Monoid 的组合?

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

Monoid (m a) => ...意味着 thatm a必须是调用者选择的一个特定的 monoid a,但MonadPlus m意味着 thatm a必须是 all 的 monoid 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. 如果我们不知道a我们需要什么。 MonadPlus m => ...而不是不可能。

于 2014-04-11T23:31:42.010 回答
6

您的类型guard'与您的类型不符Monoid m a

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

换句话说,MonadPlus定义了两个操作:mzero并且mplus满足两个规则:mzero对于 是中性的mplus,并且mplus是关联的。这满足 a Monoidso that mzeroismemptymplusis的定义mappend

不同之处在于它MonadPlus mm aany的幺半群a,但Monoid m只为 定义了一个幺半群m。你的guard'作品,因为你只需要m成为一个Monoid只为()。但MonadPlus更强大的是,它声称m a是任何a.

于 2014-04-11T23:12:44.607 回答
5

使用语言扩展QuantifiedConstraints您可以表示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,我们可以MonadPlus为所有这样的 monoid-monads 一般地实现“真实”类:

{-# 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 []真的和[];一样 但是对于Maybe,该Monoid实例通过人为地赋予它一个标识元素来提升一些潜在的半群,而该MonadPlus实例是左偏选择;所以我们必须使用MonoidMonad First而不是MonoidMonad Maybe获得正确的实例。

于 2019-07-15T14:19:08.317 回答
0

编辑:所以灯泡打开了,一切都点击到位。我完全误解了 Toxaris 的回答。现在我明白了,除了一些支持示例,以及在类型类定义中似乎没有必要的观察AlternativeMonad约束之外,我没有什么可添加的。MonadPlus

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]

于 2021-04-22T03:26:03.163 回答