Why MonadPlus and not Monad + Monoid?

前端 未结 3 1067
余生分开走
余生分开走 2020-12-13 23:58

I\'m trying to understand the motivation behind the MonadPlus. Why is it necessary if there are already the typeclasses Monad and Monoid

相关标签:
3条回答
  • 2020-12-14 00:25

    With the QuantifiedConstraints language extension you can express that the Monoid (m a) instance has to be uniform across all choices of 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, we can implement the "real" MonadPlus class generically for all such 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)
    

    Note that depending on your choise of m, this may or may not give you the MonadPlus you expect; for example, MonoidMonad [] is really the same as []; but for Maybe, the Monoid instance lifts some underlying semigroup by artifically giving it an identity element, whereas the MonadPlus instance is left-biased choice; and so we have to use MonoidMonad First instead of MonoidMonad Maybe to get the right instance.

    0 讨论(0)
  • 2020-12-14 00:38

    Your guard' does not match your Monoid m a type.

    If you mean Monoid (m a), then you need to define what mempty is for m (). Once you've done that, you've defined a MonadPlus.

    In other words, MonadPlus defines two opeartions: mzero and mplus satisfying two rules: mzero is neutral with respect to mplus, and mplus is associative. This satisfies the definition of a Monoid so that mzero is mempty and mplus is mappend.

    The difference is that MonadPlus m is a monoid m a for any a, but Monoid m defines a monoid only for m. Your guard' works because you only needed m to be a Monoid only for (). But MonadPlus is stronger, it claims m a to be a monoid for any a.

    0 讨论(0)
  • 2020-12-14 00:45

    But couldn't you rewrite any type constraint of

    (MonadPlus m) => ...
    

    as a combination of Monad and Monoid?

    No. In the top answer to the question you link, there is already a good explanation about the laws of MonadPlus vs. Monoid. But there are differences even if we ignore the typeclass laws.

    Monoid (m a) => ... means that m a has to be a monoid for one particular a chosen by the caller, but MonadPlus m means that m a has to be a monoid for all a. So MonadPlus a is more flexible, and this flexibility is helpful in four situations:

    1. If we don't want to tell the caller what a we intend to use.
      MonadPlus m => ... instead of Monoid (m SecretType) => ...

    2. If we want to use multiple different a.
      MonadPlus m => ... instead of (Monoid (m Type1), Monoid (m Type2), ...) => ...

    3. If we want to use infinitely many different a.
      MonadPlus m => ... instead of not possible.

    4. If we don't know what a we need. MonadPlus m => ... instead of not possible.

    0 讨论(0)
提交回复
热议问题