Going through Haskell's documentation is always a bit of a pain for me, because all the information you get about a function is often nothing more than just: f a -> f [a]
which could mean any number of things.
As is the case of the <|>
function.
All I'm given is this: (<|>) :: f a -> f a -> f a
and that it's an "associative binary operation"...
Upon inspection of Control.Applicative
I learn that it does seemingly unrelated things depending on implementation.
instance Alternative Maybe where
empty = Nothing
Nothing <|> r = r
l <|> _ = l
Ok, so it returns right if there is no left, otherwise it returns left, gotcha.. This leads me to believe it's a "left or right" operator, which kinda makes sense given its use of |
and |
's historical use as "OR"
instance Alternative [] where
empty = []
(<|>) = (++)
Except here it just calls list's concatenation operator... Breaking my idea down...
So what exactly is that function? What's its use? Where does it fit in in the grand scheme of things?
Typically it means "choice" or "parallel" in that a <|> b
is either a "choice" of a
or b
or a
and b
done in parallel. But let's back up.
Really, there is no practical meaning to operations in typeclasses like (<*>)
or (<|>)
. These operations are given meaning in two ways: (1) via laws and (2) via instantiations. If we are not talking about a particular instance of Alternative
then only (1) is available for intuiting meaning.
So "associative" means that a <|> (b <|> c)
is the same as (a <|> b) <|> c
. This is useful as it means that we only care about the sequence of things chained together with (<|>)
, not their "tree structure".
Other laws include identity with empty
. In particular, a <|> empty = empty <|> a = a
. In our intuition with "choice" or "parallel" these laws read as "a or (something impossible) must be a" or "a alongside (empty process) is just a". It indicates that empty
is some kind of "failure mode" for an Alternative
.
There are other laws with how (<|>)
/empty
interact with fmap
(from Functor
) or pure
/(<*>)
(from Applicative
), but perhaps the best way to move forward in understanding the meaning of (<|>)
is to examine a very common example of a type which instantiates Alternative
: a Parser
.
If x :: Parser A
and y :: Parser B
then (,) <$> x <*> y :: Parser (A, B)
parses x
and then y
in sequence. In contrast, (fmap Left x) <|> (fmap Right y)
parses either x
or y
, beginning with x
, to try out both possible parses. In other words, it indicates a branch in your parse tree, a choice, or a parallel parsing universe.