You may come across a programming task where you want to implement the following pattern: try this and, if it doesn’t work out, try the other thing.
In other words, you want a computation composed of two (or more) alternatives which should be tried one after the other until one of them produces a useful result.
As an example, let’s build a function that takes a String
as input and returns Just String
if that input is either "bob"
or "alice"
.
First, we’ll define two functions:
isAlice :: String -> Maybe String
isAlice a = if a == "alice" then Just a else Nothing
isBob :: String -> Maybe String
isBob a = if a == "bob" then Just a else Nothing
We can then compose them like this:
isAliceOrBob :: String -> Maybe String
isAliceOrBob a =
case isAlice a of
Just _ -> Just a
Nothing -> case isBob a of
Just _ -> Just a
Nothing -> Nothing
λ> isAliceOrBob "alice"
Just "alice"
λ> isAliceOrBob "bob"
Just "bob"
λ> isAliceOrBob "charly"
Nothing
This works fine for two functions, but becomes ugly when we add more alternatives.
isCharly :: String -> Maybe String
isCharly a = if a == "charly" then Just a else Nothing
isAliceOrBoborCharly :: String -> Maybe String
isAliceOrBoborCharly a =
case isAlice a of
Just _ -> Just a
Nothing -> case isBob a of
Just _ -> Just a
Nothing -> case isCharly a of
Just _ -> Just a
Nothing -> Nothing
It would be better if we could use an operator (such as <|>
) for this pattern:
isAliceOrBobOrCharly :: String -> Maybe String
isAliceOrBobOrCharly a = isAlice a <|> isBob a <|> isCharly a
Actually, the code above is valid Haskell. All you need to do to
make use of the <|>
operator is to import it from Control.Applicative
.
It’s the main method of the Alternative
typeclass, which we’ll cover in this article.
The Alternative typeclass
Let’s look at what GHCi can tell us about the Alternative
typeclass:
type Alternative :: (* -> *) -> Constraint
class Applicative f => Alternative f where
empty :: f a
(<|>) :: f a -> f a -> f a
some :: f a -> f [a]
many :: f a -> f [a]
{-# MINIMAL empty, (<|>) #-}
-- Defined in ‘GHC.Base’
instance Alternative ZipList -- Defined in ‘Control.Applicative’
instance Alternative [] -- Defined in ‘GHC.Base’
instance Alternative Maybe -- Defined in ‘GHC.Base’
instance Alternative IO -- Defined in ‘GHC.Base’
This gives us some useful facts:
- A type implementing
Alternative
must be of kind* -> *
. - It must also implement
Applicative
(and, with that, alsoFunctor
). - To implement it, you must implement
<|>
andempty
. - In addition to those methods, it gives you
some
andmany
, which we’ll discuss further below.
Alternative for Maybe
Alternative
is already defined for the Maybe
type that we used in the example at the start.
Let’s take a look at the implementation:
instance Alternative Maybe where
empty = Nothing
Nothing <|> r = r
l <|> _ = l
If you take a look at the definitions, you can see that empty :: f a
is
the identity of (<|>) :: f a -> f a -> f a
, very much like mempty :: a
is
the identity of mappend :: a -> a -> a
in case of Monoid
.
In fact, Alternative
is said to be a monoid on applicative functors.
The truth table of Alternative Maybe
looks like this:
Nothing | Just r | |
---|---|---|
Nothing | Nothing | Just r |
Just l | Just l | Just l |
Alternative for IO
Another interesting Alternative
instance is IO
.
Here, the first alternative from left to right that succeeds wins:
test :: IO String
test = fail "foo" <|> pure "bar" <|> fail "baz"
When executed, the first alternative (fail "foo"
) fails. The second one
(pure "bar"
) succeeds, which is why the third isn’t executed at all.
λ> test
"bar"
A simple parser using Alternative
A very common use case of the Alternative
typeclass is in parsers.
Let’s assume we want to implement a simple parser that reads a
stream of type ‘i’ and possibly returns a value of type a
if the input matches.
newtype Parser i a
Parser { runParser :: [i] -> (Maybe a, [i]) }
This Parser
type wraps a function that takes an input and gives back
Just a
if it matches and Nothing
if it doesn’t, together with the
remaining unused input.
evalParser :: (Show i) => Parser i a -> [i] -> Either String a
evalParser p is = case runParser p is of
(Nothing, is) -> Left $ "parser failed on " ++ show is
(Just a, _) -> Right a
evalParser
runs such a parser and returns a more convenient Either
with an error string or the result.
A concrete implementation of the parser could, for example, consume a numeric input and succeed if it is smaller than a given number or fail if it is equal or greater than that number:
parseLT :: Ord a => a -> Parser a a
parseLT a = Parser f
where f input@(i : is)
| i < a = (Just i, is) -- parser succeeds
| otherwise = (Nothing, input) -- parser fails
f input = (Nothing, input) -- empty input - parser also fails
Running it shows that it works as expected:
λ> evalParser (parseLT 10) [2,3,10,20]
Right 2
λ> evalParser (parseLT 10) [10,2,3,20]
Left "parser failed on [10,2,3,20]"
λ> evalParser (parseLT 10) []
Left "parser failed on []"
Implementing an Alternative instance for our parser
First, we need a Functor
instance for our Parser i a
type, which is pretty
straightforward and reuses the Functor
instance for our inner Maybe a
.
{-# LANGUAGE InstanceSigs #-}
instance Functor (Parser i) where
fmap :: (a -> b) -> Parser i a -> Parser i b
fmap f (Parser p) =
Parser $ \is ->
let (aM, rest) = p is -- run the parser
in (fmap f aM, rest) -- reuse the functor instance on the 'Maybe a`
Note: Above, I’ve explicitly written the fmap
function signature in the instance
declaration. It isn’t required but it makes it easier to understand what
is going on. To do that, you need to enable the InstanceSigs
language extension.
Before we can implement Alternative
, we also need to implement
Applicative
:
instance Applicative (Parser i) where
pure :: a -> Parser i a
pure a = Parser (\is -> (Just a, is))
-- with TupleSections could be written as : Parser (Just a,)
(<*>) :: Parser i (a -> b) -> Parser i a -> Parser i b
(Parser f) <*> (Parser p) = Parser $ \is ->
let (fM, rest) = f is
in case fM of
Nothing -> (Nothing, rest)
Just f' -> case p rest of
(Just o, rest') -> (pure $ f' o, rest')
(Nothing, rest') -> (Nothing, rest')
I won’t go into the details of Applicative
here, as there are excellent
articles on the idea and use of applicative functors. But with that out of the way, we’re finally able
to define the Alternative
typeclass for our parser.
instance Alternative (Parser i) where
empty = Parser (\is -> (Nothing, is))
-- with TupleSections could be written as: Parser (Nothing,)
(<|>) :: Parser i a -> Parser i a -> Parser i a
(Parser pa) <|> (Parser pb) = Parser $ \is ->
let (aMa, restA) = pa is
(aMb, restB) = pb is
in case (aMa, aMb) of
(Just a, _) -> (Just a, restA)
(Nothing, Just b) -> (Just b, restB)
_ -> (Nothing, is)
But why do we want an Alternative
instance for the parser?
First of all, because of the possibility to compose multiple Parser i a
with
the <|>
operator.
Another reason is the two methods of Alternative
that we haven’t
discussed so far, which we get for free:
some :: f a -> f [a]
which means: one or moremany :: f a -> f [a]
which means: zero of more
In our case, some (parseLT 10)
will create a new parser of type
parseLT :: Int -> Parser Int [Int]
that expects at least one integer
that is smaller than ten and will consume all the following numbers which are
smaller than ten as well.
many (parseLT 10)
is similar, but it doesn’t fail if the first element in the input doesn’t match.
In that case, it will just return the empty list []
.
Let’s try out these methods:
λ> evalParser (some $ parseLT 10) [2,3,10,20]
Right [2,3]
λ> evalParser (many $ parseLT 10) [2,3,10,20]
Right [2,3]
λ> evalParser (some $ parseLT 10) [10,2,3,20]
Left "parser failed on [10,2,3,20]"
λ> evalParser (many $ parseLT 10) [10,2,3,20]
Right []
some
and many
are very common and useful patterns in parser combinators.
If you want to dive deeper into the topic of parser combinators in Haskell, take a look at this post.
MonadPlus
There is a typeclass called MonadPlus m
which has the same semantics as Alternative
but for types that implement Monad
.
So it can be seen as a “monoid on monads”. It comes with a default implementation
based on Alternative
, so you don’t have to implement any of the methods.
-- The MonadPlus class definition
-- | Monads that also support choice and failure.
class (Alternative m, Monad m) => MonadPlus m where
-- | The identity of 'mplus'. It should also satisfy the equations
--
-- > mzero >>= f = mzero
-- > v >> mzero = mzero
--
-- The default definition is
--
-- @
-- mzero = 'empty'
-- @
mzero :: m a
mzero = empty
-- | An associative operation. The default definition is
--
-- @
-- mplus = ('<|>')
-- @
mplus :: m a -> m a -> m a
mplus = (<|>)
Although you could use the Alternative
machinery everywhere where you could use MonadPlus
(as every Monad
is also an Applicative
), there are useful functions that only work in the
context of a MonadPlus
. For example, mfilter
,
which filters a MonadPlus
with a predicate.
λ> mfilter odd (Just 1)
Just 1
λ> mfilter odd (Just 2)
Nothing
λ> mfilter odd (Nothing)
Nothing
Summary
Alternative
is a useful instance to implement for your applicative functor if it has a semantic of try this or, alternatively, that.Alternative
can be seen as a monoid on applicative functors. In theData.Monoid
module there even exists a wrapperAlt
that further shows this idea.- If you implement a parser (combinator), you almost always want to implement
an
Alternative
instance for it.
If you would like to read more Haskell articles about topics like this, be sure to follow us on Twitter or subscribe to our newsletter via the form below.
Exercises
-
Implement the
isAliceOrBobOrCharly
example from the beginning in terms of our newParser i a
type.So you come up with a parser:
parseAliceBobCarly :: Parser Char String parseAliceBobCarly = ...
You’re allowed to utilize functions from
Data.List
.Solution
parseString :: Eq i => [i] -> Parser i [i] parseString a = Parser f where f input = case stripPrefix a input of Nothing -> (Nothing, input) Just rest -> (Just a, rest) parseAliceBobCarley :: Parser Char String parseAliceBobCarley = parseString "alice" <|> parseString "bob" <|> parseString "charly"
As mentioned above, there is a very close relationship between
Alternative
andMonoid
typeclasses, which provides the semantics of concatenation of elements of a list usingMonoid
smappend
namedmconcat
. TheData.Foldable
module provides a useful function with the same semantics onAlternative
namedasum :: (Foldable t, Alternative f) => t (f a) -> f a
. With that, we could write the function above also as:parseAliceBobCarley' :: Parser Char String parseAliceBobCarley' = asum [ parseString "alice", parseString "bob", parseString "charly"]
-
To be able to give the user better information about what went wrong in case of parser failure (parser didn’t match? parser reached the end of input?), replace the inner
Maybe a
with aEither err a
and fix class instances:newtype Parser2 err i a = Parser { runParser2 :: [i] -> (Either err a, [i]) }
Solution
newtype Parser2 err i a = Parser2 { runParser2 :: [i] -> (Either err a, [i]) } instance Functor (Parser2 err i) where fmap f (Parser2 p) = Parser2 $ \is -> let (aE, rest) = p is in (f <$> aE, rest) instance Applicative (Parser2 err i) where pure a = Parser2 (Right a,) -- TupleSections: Parser (\is -> (Just a, is)) (Parser2 f) <*> (Parser2 p) = Parser2 $ \is -> let (fE, rest) = f is in case fE of Left err -> (Left err, rest) Right f' -> case p rest of (Right o, rest') -> (pure $ f' o, rest') (Left err, rest') -> (Left err, rest') data Parser2Error = EmptyParser | NoInput | ParserFailure deriving (Eq, Show) instance Alternative (Parser2 Parser2Error i) where empty = Parser2 (Left EmptyParser, ) (Parser2 pa) <|> (Parser2 pb) = Parser2 $ \is -> let (aEa, restA) = pa is (aEb, restB) = pb is in case (aEa, aEb) of (Right a, _) -> (Right a, restA) (_, Right b) -> (Right b, restB) _ -> (Left ParserFailure, is) evalParser2 :: (Show i) => Parser2 Parser2Error i a -> [i] -> Either String a evalParser2 p is = case runParser2 p is of (Left err, is) -> Left $ "parser failed with " <> show err <> " on " ++ show is (Right a, _) -> Right a