6
votes

I am currently reading about the Alternative/MonadPlus typeclasses in wikibooks. It describes the difference very well. However, one puzzling part is the guard function, which I am assuming, is used for "short-circuiting" a computation. (Am I right?)

The function guard although defined in Control.Monad has an Alternative constraint, as following (link).

guard           :: (Alternative f) => Bool -> f ()
guard True      =  pure ()
guard False     =  empty

But the above article, mentions that only the MonadPlus is required to enforce the left zero and right zero laws (Hence the stronger claim).

mzero >>= f  =  mzero -- left zero
m >> mzero   =  mzero -- right zero

Given the purpose of the guard function, shouldn't it be defined with a MonadPlus constraint? Don't we need the stronger laws if guard is supposed to "short-circuit" the computation? I am curious about the reason behind the specific design choice.

p.s.: I don't know what is a better way to describe the "cancelling the upfront computation" behavior other than the word "short-circuiting"?

1
MonadPlus is stronger than Alternative and you don't need MonadPlus to write guard - the type with Alternative is the most general type, i.e. the inferred one. Why would one give it a stronger type? (Note the wikibooks page is outdated post-AMP, so ghc 8 and up? I don't actually remember..) - user2407038
@user2407038 Don't we need the stronger laws (left zero, right zero) if guard is supposed to do "short-circuiting"? - zeronone
You get 'short circuiting' in the Applicative context: guard False *> x = empty and guard True *> x = x; it simply also works for Monad in the same natural way as all things which work for Applicative will work for Monad. - user2407038
@user2407038 But AFAIK Alternative doesn't mandate the empty *> x == empty law. So it might not be true for all instances. Am I right? - zeronone
It's a documentation issue. guard was MonadPlus m => ..., but every MonadPlus is now an Alternative, and by default mzero = empty and mplus = (<|>). Therefore, if you have a type that's both a Monad and an Alternative, it should hold those laws. However, that should be stated in either the class Alternative or the class Monad documentation. That being said, the functionality of guard wasn't reduced. mzero is empty on all common MonadPlus instances. - Zeta

1 Answers

0
votes

guard has an Applicative constraint because you don't need to perform monadic operations on it to define the function.

It's definition is (copied from the Hackage source):

guard           :: (Alternative f) => Bool -> f ()
guard True      =  pure ()
guard False     =  empty

If it had specified MonadPlus instead of Alternative, it wouldn't have gained anything. MonadPlus is a subclass of Alternative, so all MonadPlus instances can use guard, but not all Alternatives could use it.

(Although there would be very few cases of excluded Alternatives -- both cases mentioned here have an instance of MonadPlus even though they don't satisfy the left-distributive rule.)

In general, it's better to stick to the bare minimum restrictions needed to write a function, even if the intended use is more specific. This way, there is nothing excluded that shouldn't be, and no complaints from people whose types could work for it, but aren't monads.

Coincidentally, the Hoogle search results incorrectly show a MonadPlus restriction instead, though if you click the link it is correct.