Here is a very simple example of something one can do with Alternative
:
import Control.Applicative
import Data.Foldable
data Nested f a = Leaf a | Branch (Nested f (f a))
flatten :: (Foldable f, Alternative f) => Nested f a -> f a
flatten (Leaf x) = pure x
flatten (Branch b) = asum (flatten b)
Now let's try the same thing with Monoid
:
flattenMonoid :: (Foldable f, Applicative f) => Nested f a -> f a
flattenMonoid (Leaf x) = pure x
flattenMonoid (Branch b) = fold (flattenMonoid b)
Of course, this doesn't compile, because in fold (flattenMonoid b)
we need to know that the flattening produces a container with elements that are an instance of Monoid
. So let's add that to the context:
flattenMonoid :: (Foldable f, Applicative f, Monoid (f a)) => Nested f a -> f a
flattenMonoid (Leaf x) = pure x
flattenMonoid (Branch b) = fold (flattenMonoid b)
Ah, but now we have a problem, because we can't satisfy the context of the recursive call, which demands Monoid (f (f a))
. So let's add that to the context:
flattenMonoid :: (Foldable f, Applicative f, Monoid (f a), Monoid (f (f a))) => Nested f a -> f a
flattenMonoid (Leaf x) = pure x
flattenMonoid (Branch b) = fold (flattenMonoid b)
Well, that just makes the problem worse, since now the recursive call demands even more stuff, namely Monoid (f (f (f a)))
...
It would be cool if we could write
flattenMonoid :: ((forall a. Monoid a => Monoid (f a)), Foldable f, Applicative f, Monoid (f a)) => Nested f a -> f a
or even just
flattenMonoid :: ((forall a. Monoid (f a)), Foldable f, Applicative f) => Nested f a -> f a
and we can: instead of writing forall a. Monoid (f a)
, we write Alternative f
. (We can write a typeclass that expresses the first, easier-to-satisfy constraint, as well.)
Monoid
, in particular points 3. and 4.: “3. If we want to use infinitely many differenta
.MonadPlus m => ...
instead of not possible. 4. If we don't know whata
we need.MonadPlus m => ...
instead of not possible.” If something about that isn't clear to you, please ask specifically about your doubts. - leftaroundaboutMonoid (f A), Monoid (f B)
constraints, but so what? – it doesn't look that bad” or “why don't we put those infinitely many / unknown types in an existential/GADT wrapper”. But all these workarounds aren't really practical in a big project. Just look up some library functions withAlternative
context, and try to write them withMonoid
. - leftaroundabout