Reputation: 10274
I'm trying to understand the motivation behind the MonadPlus
. Why is it necessary if there are already the typeclasses Monad
and Monoid
?
Granted, instances of Monoid
are concrete types, whereas instances of Monad
require a single type parameter. (See Monoid vs MonadPlus for a helpful explanation.) But couldn't you rewrite any type constraint of
(MonadPlus m) => ...
as a combination of Monad
and Monoid
?
(Monad m, Monoid (m a)) => ...
Take the guard
function from Control.Monad
, for example. Its implementation is:
guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero
I was able to implement it using only Monad
and Monoid
:
guard' :: (Monad m, Monoid (m ())) => Bool -> m ()
guard' True = return ()
guard' False = mempty
Could someone please clarify the real difference between MonadPlus
and Monad
+ Monoid
?
Upvotes: 42
Views: 2282
Reputation: 27646
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
Alternative
ly, 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 choice 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.
Upvotes: 5
Reputation: 7276
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:
If we don't want to tell the caller what a
we intend to use.
MonadPlus m => ...
instead of Monoid (m SecretType) => ...
If we want to use multiple different a
.
MonadPlus m => ...
instead of (Monoid (m Type1), Monoid (m Type2), ...) => ...
If we want to use infinitely many different a
.
MonadPlus m => ...
instead of not possible.
If we don't know what a
we need.
MonadPlus m => ...
instead of not possible.
Upvotes: 38
Reputation: 178
Edit: So the lightbulb turned on, and everything clicked into place. I completely misinterpreted Toxaris's answer. Now that I understand, I've nothing to add except some supporting examples, and an observation that the Alternative
and Monad
constraints seem unnecessary in the MonadPlus
type class definition.
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
In order to be called, they have to be typed with a concrete type (e.g. monoidExample2 :: [Int]
)
Upvotes: 0
Reputation: 5406
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
.
Upvotes: 6