6

Why is guard based on Alternative?

guard :: Alternative f => Bool -> f ()
-- guard b is pure () if b is True,
-- and empty if b is False.

I ask because guard only uses the empty from Alternative. It doesn't use <|> from Alternative at all. So why bother using Alternative in the first place?

I guess this is because there is some unstated idea behind Alternative's empty that matches perfectly with what we're trying to accomplish with guard (stop on False, continue on True). If this is the case, please enlighten me about this unstated idea.

Yet at the same time, if feels that we're just ignoring <|>. It feels almost as if guard is not "fully capturing" what Alternative is all about. I hope that makes sense. To make it more concrete: Why didn't they invent another type class called something like Stoppable (or Abortable) and used that instead of Alternative?

duplode
  • 33,731
  • 7
  • 79
  • 150
haskellHQ
  • 1,027
  • 6
  • 15
  • 2
    You also need `pure`. Sure, you could have a `Pure` class providing `pure`, and a `Empty` class providing `empty`, and have `Alternative` require both. For pragmatic reasons, though, one does not always split a class into single-method classes. – chi Jun 22 '17 at 10:46
  • 1
    Pragmatic or historic reasons in this case? – gallais Jun 22 '17 at 11:37
  • 1
    I think it's because there aren't types, which can implement `empty` and can't implement `(<|>)`. So, theoretical a type class `Stoppable` will be equal to `Alternative` (set of types will be equal). – freestyle Jun 22 '17 at 11:42
  • 1
    @freestyle: funny enough, PureScript defines `Alternative` via `Plus` (provides `empty`), which is based on `Alt` (provides `(<|>)`). So they follow the Semigroup/Monoid approach. – Zeta Jun 22 '17 at 13:43

1 Answers1

8

TL;DR: Historical reasons. It was envisioned like this in MonadPlus, which got its Applicative variant Alternative later, and no one has proposed to split Alternative into AZero and AChoice or similar.


Alternative is a relatively new idea, just like Applicative. Back when guard was first envisioned, it was based on MonadPlus, a Monad that should support choice and failure, just like Alternative. Its original type was thus

guard :: MonadPlus m => Bool -> m ()

That was specified in the Haskell 98 report, where MonadPlus was already noted. Haskell 1.0 didn't use monads at all, by the way. When Applicative finally got a superclass of Monad, Alternative got a superclass of MonadPlus, and mzero = empty and mplus = (<|>).

Well, now we know why guard uses Alternative. Because it was based on MonadPlus beforehand. So why is MonadPlus defined like this?

One would have to write a mail to SPJ or someone else from the committee to get their rationale from 1998, because just one year later, Erik Meijer and Graham Hutton wrote their "Monadic Parsing in Haskell" paper. If you have a look at the paper, you'll notice that their MonadPlus just works like you intend:

class Monad m => MonadZero m where
  zero :: m a

class MonadZero m => MonadPlus m where
  (++) :: m a -> m a -> m a

So it's certainly possible to handle this "stoppable" the way you've described it. However, there is simply no base class that currently defines empty without Alternative. There could be one, but it wasn't proposed yet.

Note that this is a recurring theme with Haskell classes. Monoid contains mappend and mempty. After its conception, someone noticed that there are certain types where mappend makes sense, but not mempty. For example

newtype Min a = Min a

combine :: Ord a => Min a -> Min a -> Min a
combine (Min x) (Min y) = Min (min x y)

Here, mappend = combine is clearly associative, whereas an empty Min isn't possible if we just use Ord, we would have to use Bounded. That's why there is now Semigroup, which isn't a base class of Monoid yet, but gives us that associative operation.

To come back to your original question: guard uses Alternative, because Alternative provides empty, and empty "stops" the evaluation in certain Alternative's. There's no other class that contains that, yet.

But with a proposal, there might be at some point, although I'm not sure what's the community's opinion on splitting Alternative is.

By the way, languages like PureScript split Alternative, although they split it the other way round…

For more information about Alternative and why I used Monoid as another example, see Confused by the meaning of the 'Alternative' type class and its relationship to other type classes.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • 1
    I wouldn't say it's an oversight, per se. There's a legit design tradeoff between granularity and cohesiveness. I personally somewhat prefer the Haskell class hierarchy to the PureScript one. `empty` and `<|>` "go together" in a natural way. – Benjamin Hodgson Jun 22 '17 at 13:48
  • 1
    @BenjaminHodgson I removed the judgement, it wasn't really appropriate. – Zeta Jun 22 '17 at 13:51
  • 1
    It's also worth thinking about `MonadFail`, whose `fail` is kind of like `empty`, but which extends emptiness with `>>=` rather than recovery. – dfeuer Jun 23 '17 at 11:43