Skip to content

Representing Failure

We have already looked at our find function for lists:

GHCi
>>> 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:

GHCi
>>> 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:

GHCi
>>> 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:

GHCi
>>> 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
GHCi
>>> 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:

GHCi
>>> :{
  | 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
GHCi
>>> 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.