# Haskell/Understanding monads/Maybe

We introduced monads using `Maybe`

as an example. The `Maybe`

monad represents computations which might "go wrong" by not returning a value. For reference, here are our definitions of `return`

and `(>>=)`

for `Maybe`

as we saw in the last chapter:^{[1]}

return :: a -> Maybe a return x = Just x (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b (>>=) m g = case m of Nothing -> Nothing Just x -> g x

## Safe functions[edit]

The `Maybe`

datatype provides a way to make a safety wrapper around functions which can fail to work for a range of arguments. For example, `head`

and `tail`

only work with non-empty lists. Another typical case, which we will explore in this section, are mathematical functions like `sqrt`

and `log`

; (as far as real numbers are concerned) these are only defined for non-negative arguments.

> log 1000 6.907755278982137 > log -1000 ''ERROR'' -- runtime error

To avoid this crash, a "safe" implementation of log could be:

safeLog :: (Floating a, Ord a) => a -> Maybe a safeLog x | x >= 0 = Just (log x) | otherwise = Nothing

> safeLog 1000 Just 6.907755278982137 > safeLog -1000 Nothing

We could write similar "safe functions" for all functions with limited domains such as division, square-root, and inverse trigonometric functions (`safeDiv`

, `safeSqrt`

, `safeArcSin`

, etc. all of which would have the same *type* as `safeLog`

but definitions specific to their constraints)

If we wanted to combine these monadic functions, the cleanest approach is with monadic composition (which was mentioned briefly near the end of the last chapter) and point-free style:

safeLogSqrt = safeLog <=< safeSqrt

Written in this way, `safeLogSqrt`

resembles a lot its unsafe, non-monadic counterpart:

unsafeLogSqrt = log . sqrt

## Lookup tables[edit]

A lookup table relates *keys* to *values*. You *look up* a value by knowing its key and using the lookup table. For example, you might have a phone book application with a lookup table where contact names are keys to corresponding phone numbers. An elementary way of implementing lookup tables in Haskell is to use a list of pairs: `[(a, b)]`

. Here `a`

is the type of the keys, and `b`

the type of the values.^{[2]} Here's how the phone book lookup table might look like:

phonebook :: [(String, String)] phonebook = [ ("Bob", "01788 665242"), ("Fred", "01624 556442"), ("Alice", "01889 985333"), ("Jane", "01732 187565") ]

The most common thing you might do with a lookup table is look up values. Everything is fine if we try to look up "Bob", "Fred", "Alice" or "Jane" in our phone book, but what if we were to look up "Zoe"? Zoe isn't in our phone book, so the lookup would fail. Hence, the Haskell function to look up a value from the table is a `Maybe`

computation (it is available from Prelude):

lookup :: Eq a => a -- a key -> [(a, b)] -- the lookup table to use -> Maybe b -- the result of the lookup

Let us explore some of the results from lookup:

Prelude> lookup "Bob" phonebook Just "01788 665242" Prelude> lookup "Jane" phonebook Just "01732 187565" Prelude> lookup "Zoe" phonebook Nothing

Now let's expand this into using the full power of the monadic interface. Say, we're now working for the government, and once we have a phone number from our contact, we want to look up this phone number in a big, government-sized lookup table to find out the registration number of their car. This, of course, will be another `Maybe`

-computation. But if the person we're looking for isn't in our phone book; we certainly won't be able to look up their registration number in the governmental database. What we need is a function that will take the results from the first computation and put it into the second lookup *only* if we get a successful value in the first lookup. Of course, our final result should be `Nothing`

if we get `Nothing`

from the either of the lookups.

getRegistrationNumber :: String -- their name -> Maybe String -- their registration number getRegistrationNumber name = lookup name phonebook >>= (\number -> lookup number governmentDatabase)

If we then wanted to use the result from the governmental database lookup in a third lookup (say we want to look up their registration number to see if they owe any car tax), then we could extend our `getRegistrationNumber`

function:

getTaxOwed :: String -- their name -> Maybe Double -- the amount of tax they owe getTaxOwed name = lookup name phonebook >>= (\number -> lookup number governmentDatabase) >>= (\registration -> lookup registration taxDatabase)

Or, using the `do`

-block style:

getTaxOwed name = do number <- lookup name phonebook registration <- lookup number governmentDatabase lookup registration taxDatabase

Let's just pause here and think about what would happen if we got a `Nothing`

anywhere. By definition, when the first argument to `>>=`

is `Nothing`

, it just returns `Nothing`

while ignoring whatever function it is given. Thus, a `Nothing`

at *any stage* in the large computation will result in a `Nothing`

overall, regardless of the other functions. After the first `Nothing`

hits, all `>>=`

s will just pass it to each other, skipping the other function arguments. The technical description says that the structure of the `Maybe`

monad *propagates failures*.

## Open monads[edit]

Another trait of the `Maybe`

monad is that it is "open": if we have a `Just`

value, we can see the contents and extract the associated values through pattern matching.

zeroAsDefault :: Maybe Int -> Int zeroAsDefault mx = case mx of Nothing -> 0 Just x -> x

This usage pattern of replacing `Nothing`

with a default is captured by the `fromMaybe`

function in `Data.Maybe`

.

zeroAsDefault :: Maybe Int -> Int zeroAsDefault mx = fromMaybe 0 mx

The `maybe`

Prelude function allows us to do it in a more general way, by supplying a function to modify the extracted value.

displayResult :: Maybe Int -> String displayResult mx = maybe "There was no result" (("The result was " ++) . show) mx

Prelude> :t maybe maybe :: b -> (a -> b) -> Maybe a -> b Prelude> displayResult (Just 10) "The result was 10" Prelude> displayResult Nothing "There was no result"

This possibility makes sense for `Maybe`

, as it allows us to recover from failures. Not all monads are open in this way; often, they are designed to hide unnecessary details. `return`

and `(>>=)`

alone do not allow us to extract the underlying value from a monadic computation, and so it is perfectly possible to make a "no-exit" monad, from which it is never possible to extract values. The most obvious example of that is the `IO`

monad.

## Maybe and safety[edit]

We have seen how `Maybe`

can make code safer by providing a graceful way to deal with failure that does not involve runtime errors. Does that mean we should always use `Maybe`

for *everything*? Not really.

When you write a function, you are able to tell whether it might fail to produce a result during normal operation of the program,^{[3]} either because the functions you use might fail (as in the examples in this chapter) or because you know some of the argument or intermediate result values do not make sense (for instance, imagine a calculation that is only meaningful if its argument is less than 10). If that is the case, by all means use `Maybe`

to signal failure; it is far better than returning an arbitrary default value or throwing an error.

Now, adding `Maybe`

to a result type without a reason would only make the code more confusing and no safer. The type signature of a function with unnecessary Maybe would tell users of the code that the function could fail when it actually can't. Of course, that is not as bad a lie as the opposite one (that is, claiming that a function will not fail when it actually can), but we really want honest code in *all* cases. Furthermore, using `Maybe`

forces us to propagate failure (with `fmap`

or monadic code) and eventually handle the failure cases (using pattern matching, the `maybe`

function, or `fromMaybe`

from `Data.Maybe`

). If the function cannot actually fail, coding for failure is an unnecessary complication.

## Notes[edit]

- ↑ The definitions in the actual instance in
`Data.Maybe`

are written a little differently, but are fully equivalent to these. - ↑ Check the chapter about maps in Haskell in Practice for a different, and potentially more useful, implementation.
- ↑ With "normal operation" we mean to exclude failure caused by uncontrollable circumstances in the real world, such as memory exhaustion or a dog chewing the printer cable.