Representing Failure
We have already looked at our find function for lists:
>>> find even [1,2,3,4,5]
Just 2
>>> find even [1,3,5]
Nothing
find pred xs returns Just the first element in xs that satisfies pred or
Nothing if it fails to find such an element. So that's our representation of
success or failure: Just x is what a function returns when it succeeds and the
result is x. If the function fails, it returns Nothing.
Now how about the following problem? We are given a list that stores a bunch of students along with their banner numbers:
>>> bannerNumbers = [("John Doe", 1234), ("Jane Doe", 1235), ("Teacher's Pet", 1), ("Sleepy Head", 555)]
and a list that stores each student's grade in some course, keyed by their banner numbers:
>>> grades = [(1, "A+"), (555, "C"), (1234, "B")]
We want to implement a function that allows us to look up a student's grade by
name. The process is rather obvious: We find the banner number in the
bannerNumbers list and then look up the grade by banner number in grades.
First we need a function that gives us the banner number corresponding to the student's name:
bannerNumberByName :: String -> Maybe Int
bannerNumberByName name = snd <$> find (\entry -> fst entry == name) bannerNumbers
Remember, fst gives the first component of a pair, snd gives the second
component of a pair, and (<$>) is just the infix version of fmap. For the
Maybe functor, fmap applies the given function to x if the Maybe value
is Just x. If it is Nothing, then fmapping any function over it gives
Nothing again. So what this function does is to return Just the banner
number corresponding to the given name if there exists such a banner number, and
Nothing if there is no banner number associated with the given name:
>>> bannerNumberByName name = snd <$> find (\entry -> fst entry == name) bannerNumbers
>>> bannerNumberByName "Jane Doe"
Just 1235
>>> bannerNumberByName "Johnny Doe"
Nothing
Similarly, we can look up the grade by banner number:
gradeByBannerNumber :: Int -> Maybe String
gradeByBannerNumber banner = snd <$> find (\entry -> fst entry == banner) grades
>>> gradeByBannerNumber banner = snd <$> find (\entry -> fst entry == banner) grades
>>> gradeByBannerNumber 555
Just "C"
>>> gradeByBannerNumber 556
Nothing
The logic of this function is exactly the same as that of bannerNumberByName.
What we want to implement now is a function
gradeByName :: String -> Maybe String
Ideally, we'd like to implement it as
gradeByName name = gradeByBannerNumber (bannerNumberByName name)
but this doesn't work because gradeByBannerNumber expects an Int argument,
and bannerNumberByName returns Maybe Int. And this composition shouldn't
work, because both bannerNumberByName and gradeByBannerNumber may fail. What
should we apply gradeByBannerNumber to if bannerNumberByName name returns
Nothing? Similarly, we may be able to find a banner number for name, but
then the lookup of banner number in grades comes up empty. Again, we have no
choice but to report Nothing, because we didn't find a grade associated with
the given name. So the logic is that if either bannerNumberByName or
gradeByBannerNumber comes up empty, then the whole function must return
Nothing. We can easily implement this using a careful case analysis:
gradeByName name = case bannerNumberByName name of
    Nothing     -> Nothing
    Just banner -> gradeByBannerNumber banner
You can verify that this does what we expect:
>>> :{
  | gradeByName name = case bannerNumberByName name of
  |     Nothing     -> Nothing
  |     Just banner -> gradeByBannerNumber banner
  | :}
>>> gradeByName "John Doe"
Just "B"
>>> gradeByName "Johnny Doe"
Nothing
>>> gradeByName "Jane Doe"
Nothing
The grade lookup for "John Doe" succeeded. The other two lookups failed for
different reasons. The grade lookup for "Johnny Doe" failed because there is no
Johnny Doe in bannerNumbers. So bannerNumberByName returns Nothing, and we
use the first branch of the case expression, which returns Nothing. There
does exist a banner number for "Jane Doe" in bannerNumbers, so
bannerNumberByName returns Just Jane Doe's banner number. However, the
grade lookup using gradeByBannerNumber banner fails because there is no grade
recorded in grades for Jane Doe's banner number, 1235.
There's a more elegant way to implement gradeByName using the maybe function
from the standard library:
maybe :: b -> (a -> b) -> Maybe a -> b
maybe def f x = case x of
    Nothing -> def
    Just y  -> f y
maybe def f x applies f to the value y if x contains a value, is
Just y. If x does not contain any value to which to apply f, that is, if
x = Nothing, then maybe def f x returns the default value def. With the
help of maybe, we can implement gradeByname more elegantly:
gradeByName = maybe Nothing gradeByBannerNumber . bannerNumberByName
>>> gradeByName = maybe Nothing gradeByBannerNumber . bannerNumberByName
>>> gradeByName "John Doe"
Just "B"
>>> gradeByName "Johnny Doe"
Nothing
>>> gradeByName "Jane Doe"
Nothing
Still, it's tedious to compose functions that can fail in this fashion,
especially if we have more than two functions to compose. We would like to
extract the common pattern that a computation composed of multiple steps that
can fail can itself fail, and that the computation aborts as soon as it
encounters the first failure. To this end, we turn Maybe into a monad.