6

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"?

duplode
  • 33,731
  • 7
  • 79
  • 150
zeronone
  • 2,912
  • 25
  • 28
  • 2
    `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 Feb 04 '17 at 06:34
  • 1
    @user2407038 Don't we need the stronger laws (left zero, right zero) if guard is supposed to do "short-circuiting"? – zeronone Feb 04 '17 at 06:37
  • 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 Feb 04 '17 at 07:10
  • @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 Feb 04 '17 at 07:20
  • 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 Feb 04 '17 at 15:33
  • @user2407038 That Wikibooks page was updated recently (~2 months ago) to account for the AMP. (Older versions of it didn't cover `Alternative`, only `MonadPlus`.) – duplode Feb 04 '17 at 21:30
  • Side note: I haven't seen any indication of a right zero law, and several important instances don't satisfy one. – dfeuer Aug 29 '21 at 23:34

1 Answers1

0

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.

Community
  • 1
  • 1
zbw
  • 922
  • 5
  • 13