42

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?

duplode
  • 33,731
  • 7
  • 79
  • 150
mxxk
  • 9,514
  • 5
  • 38
  • 46

4 Answers4

38

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.

Enlico
  • 23,259
  • 6
  • 48
  • 102
Toxaris
  • 7,156
  • 1
  • 21
  • 37
  • 1
    If you would not mind, I would really like to see a concrete example here. Can you provide some instances using specific Monads where MonadPlus is more useful or cleaner than Monoid? – Fresheyeball Apr 27 '15 at 17:38
  • 1
    @Fresheyeball: No, I canot provide "some instances using specific Monads", sorry. If the monad is known, there is no need to abstract over m, and no need to use any type classes to specify the expected interface of m, so the difference between `MonadPlus` and `Monoid` doesn't really matter if your code only works with one particular monad. – Toxaris Apr 28 '15 at 00:28
  • 1
    I understand that. But its hard to conceptualize the practice without seeing how specific monads would work with generalized code. – Fresheyeball Apr 28 '15 at 16:42
6

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.

Sassa NF
  • 5,306
  • 15
  • 22
5

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 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.

Cactus
  • 27,075
  • 9
  • 69
  • 149
0

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])

Sledge
  • 178
  • 13
  • With the fairly new `QuantifiedConstraints` extension, you can write a constraint like `(Monad m, forall a. Monoid (m a))`. Without that extension, using just `Monoid (m a)`, you could run into problems with polymorphic recursion. Lots of things turn out to matter when you need polymorphic recursion! – dfeuer Apr 22 '21 at 19:57
  • The need for extensions in order to get a number type classes working correctly is a whole other topic... – Sledge Apr 22 '21 at 20:47
  • 1
    Most of those extensions are perfectly fine. `QuantifiedConstraints` is still a little rough around the edges. But I really think your answer here is missing my point. Suppose you have something like `data Foo m a = Stop [m a] | Go (Foo m (a, a))`. Now imagine that for whatever reason you need to be able to combine elements of the list in the `Stop` constructor. Unless you use `QuantifiedConstraints`, there is no way to give a `Monoid` constraint that will let you do that—you'll need something like `MonadPlus`. Is `QuantifiedConstraints` a game changer? Well, we'll see; it's too new. – dfeuer Apr 22 '21 at 20:55
  • My issue isn't with the extensions themselves. It's with the type classes' need for them. – Sledge Apr 22 '21 at 21:24
  • 2
    @Sledge I don't understand why your issue with type classes that need extensions is relevant here. `(Monad m, Monoid (m a))` is not quite equivalent to `MonadPlus m` because the former can be satisfied by a caller in a way that depends on the specific `a`, and the latter can't (and neither of those situations is *better* than the other, they're just different). With `QuantifiedConstraints` there is a *different* constraint using `Monad` + `Monoid` we now can write that is closer to `MonadPlus` equivalence, but none of the classes we've been talking about need any extensions to work correctly. – Ben Apr 23 '21 at 01:06
  • @Ben Purely tangential. When type classes and extensions are mentioned in the same context, I'm reminded of type classes like `MonadState` and Parsec's `Stream` that require multiple extensions to make them work. However, as you implied, that's not the case here, hence my comment of "a whole other topic." – Sledge Apr 24 '21 at 15:52