Skip to main content

Maybe

The Maybe type

data Maybe a = Nothing
| Just a

The Maybe datatype is often used to encode failure or an exceptional value:

lookup :: (Eq a) => a -> [(a, b)] -> Maybe b
find :: (a -> Bool) -> [a] -> Maybe a

Encoding exceptions using Maybe

Assume that we have a data structure with the following operations:

up, down, right :: Loc -> Maybe Loc
update :: (Int -> Int) -> Loc -> Loc

Given a location l1, we want to move up, right, down, and update the resulting position with using update (+ 1) ...

Each of the steps can fail.

Encoding exceptions using Maybe (contd.)

case up l1 of
Nothing -> Nothing
Just l2 -> case right l2 of
Nothing -> Nothing
Just l3 -> case down l3 of
Nothing -> Nothing
Just l4 -> Just (update (+ 1) l4)

In essence, we need

  • a way to sequence function calls and use their results if successful
  • a way to modify or produce successful results.

Sequencing:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= g = case f of
Nothing -> Nothing
Just x -> g x
up l1 >>=
\ l2 -> case right l2 of
Nothing -> Nothing
Just l3 -> case down l3 of
Nothing -> Nothing
Just l4 -> Just (update (+ 1) l4)

Sequencing:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= g = case f of
Nothing -> Nothing
Just x -> g x
up l1 >>=
\ l2 -> right l2 >>=
\ l3 -> case down l3 of
Nothing -> Nothing
Just l4 -> Just (update (+ 1) l4)

Sequencing:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= g = case f of
Nothing -> Nothing
Just x -> g x
up l1 >>=
\ l2 -> right l2 >>=
\ l3 -> down l3 >>=
\ l4 -> Just (update (+ 1) l4)

Sequencing:

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

Sequencing and embedding

up l1 >>=
\ l2 -> right l2 >>=
\ l3 -> down l3 >>=
\ l4 -> Just (update (+ 1) l4)
up l1 >>=
\ l2 -> right l2 >>=
\ l3 -> down l3 >>=
\ l4 -> return (update (+ 1) l4)

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= g = case f of
Nothing -> Nothing
Just x -> g x
return :: a -> Maybe a
return x = Just x
up l1 >>=
\ l2 -> right l2 >>=
\ l3 -> down l3 >>=
\ l4 -> return (update (+ 1) l4)
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= g = case f of
Nothing -> Nothing
Just x -> g x
return :: a -> Maybe a
return x = Just x
(up l1) >>= right >>= down >>= return . update (+ 1)

Observation

Code looks a bit like imperative code. Compare:

up l1 >>= \ l2 ->
right l2 >>= \ l3 ->
down l3 >>= \ l4 ->
return (update (+ 1) l4)
l2 := up l1;
l3 := right l2;
l4 := down l3;
return update (+ 1) l4
  • In the imperative language, the occurrence of possible exceptions is a side effect.
  • Haskell is more explicit because we use the Maybe type and the appropriate sequencing operation.

A variation: Either

Compare the datatypes

data Either a b = Left a  | Right b
data Maybe a = Nothing | Just a

The datatype Maybe can encode exceptional function results (i.e., failure), but no information can be associated with Nothing . We cannot distinguish different kinds of errors.

Using Either , we can use Left to encode errors, and Right to encode successful results.

Sequencing and returning for Either

We can define variants of the operations for Maybe :

(>>=) :: Either Error a -> (a -> Either Error b)
-> Either Error b
f >>= g = case f of
Left e -> Left e
Right x -> g x
return :: a -> Either Error a
return x = Right x

Simulating exceptions

We can abstract completely from the definition of the underlying Either type if we define functions to throw and catch errors.

throwError :: e -> Either e a
throwError e = Left e
catchError :: Either e a -> -- computation
(e -> Either e a) -> -- handler
Either e a
catchError f handler = case f of
Left e -> handler e
Right x -> Right x