Introduction

The most common complaint I hear from students of Haskell is that monads are hard to comprehend. This is a problem, because monads are as fundamental to modern Haskell as procedures are to C, or classes to Ruby. They are the primary method of sequencing computation in what is otherwise a declarative, lazily evaluated language.

I believe that persistent failure to understand monads is not due to the subject matter, but to the manner in which it is taught. Monads are derived from a mathematics known as category theory; most existing tutorials, such as Wikibooks Haskell/Category theory, attempt to describe them in terms of other CT constructs. A few tutorials, drawing from the _why school of thought, use more concrete examples — Of monads and spacesuits is an infamous and baffling case.

This article is written for somebody who understands the concept of an anonymous closure (AKA block, function literal, lambda), and is at least somewhat familiar with Haskell's typeclass mechanism. Real World Haskell, Chapter 6 provides an overview of of typeclasses. No attempt is made to make the code presented here elegant or efficient — rather, it tries to illustrate examples using minimum concepts.

Another monad tutorial along the same line of thought is Understanding Haskell Monads.

Monad is not difficult

The Monad typeclass defines two fundamental operations. Note that my description of these operations is very generic; that is because the actual behavior is determined entirely by the type's instance.

class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b

With these definitions in place, we can also define some variants for special purposes:

(>>) :: Monad m => m a -> m b -> m b x >> y = x >>= (\_ -> y) (=<<) :: Monad m => (a -> m b) -> m a -> m b x =<< y = y >>= x (<<) :: Monad m => m b -> m a -> m b x << y = y >> x

Maybe: Values and absence of value

Background

One of the most common design mistakes in popular languages is an untyped null value. Named NULL, nil, null, or undefined, null values are intended to represent that some input does not have a corresponding output. Unfortunately, being untyped means there is no way to prevent a null value from being passed to a function or procedure.

Haskell avoids this by defining a new type, Maybe, which can contain a value. Using a custom data type prevents null value errors, which manifest as segmentation faults or unexpected exceptions in other languages.

data Maybe a = Just a | Nothing deriving (Show)

Motivation

Often, Maybe values will be used to represent whether an entire sequence of computations succeed. For example, lets define a function which returns its input if a test passes, or Nothing if it fails:

test :: (a -> Bool) -> a -> Maybe a test f x = if f x then Just x else Nothing

We'd like to re-use this function for more complex tests; for example, that two integers are positive, and the first is smaller than the second. Note that this example makes use of partial application(>= 0) is equivalent to (\x -> x >= 0).

positiveAndSmaller :: Integer -> Integer -> Maybe Integer positiveAndSmaller x y = case test (>= 0) x of Nothing -> Nothing Just x2 -> case test (>= 0) y of Nothing -> Nothing Just y2 -> test (> x2) y2

As demonstrated here, manually chaining Maybes together is awkward and verbose.

Maybe as a monad

The pattern being used here can be simplified by using monads. Our monad instance for Maybe will behave like the case statements above; if Nothing is returned at any step, the results of the entire computation will be Nothing.

instance Monad Maybe where return x = Just x Nothing >>= _ = Nothing (Just x) >>= f = f x

We'll use this to write a new positiveAndSmaller. If you've forgotten the syntax for anonymous functions in Haskell, know that this function multiplies its parameters: (\x y -> x * y). Here's positiveAndSmaller2:

positiveAndSmaller2 :: Integer -> Integer -> Maybe Integer positiveAndSmaller2 x y = test (>= 0) x >>= (\x2 -> test (>= 0) y >>= (\y2 -> test (> x2) y2))

Although still ugly, this notation is sufficiently generic that the compiler can generate it from an imperative notation. Lets examine one more example; this time, using the imperative-style do notation. Note that although this is imperative, it is not impure:

positiveAndSmaller3 :: Integer -> Integer -> Maybe Integer positiveAndSmaller3 x y = do x2 <- test (>= 0) x y2 <- test (>= 0) y test (> x2) y2

Either: Simple, predictable exception handling

Maybe is useful, but sometimes more information about the source of an error is required. For this, we can use Either. Lets look at the definition and monad instance:

data Either a b = Left a | Right b deriving (Show) instance Monad (Either a) where return x = Right x (Left x) >>= _ = Left x (Right x) >>= f = f x

This is almost the same as Maybe, except that we can include additional data in the error case.

List: One wrapper, many values

At first glance, it's not immediately obvious how a list can be a monad. Both examples we've looked at so far have contained at most one value, but lists may contain an arbitrary amount of values. The trick is that the >>= operator is sufficiently generic that it doesn't care how you build the output.

First, we'll define the list type and some helpful functions. I'm using English words for the constructors, rather than the customary : and [] symbols, so that it's easier for students to understand. This is a standard, LISP-style linked list.

data List a = Item a (List a) | End deriving (Show) append :: List a -> List a -> List a append End ys = ys append (Item x xs) ys = Item x (append xs ys) concat :: List (List a) -> List a concat End = End concat (Item x xs) = append x (concat xs) map :: (a -> b) -> List a -> List b map _ End = End map f (Item x xs) = Item (f x) (map f xs) concatMap :: (a -> List b) -> List a -> List b concatMap f x = concat (map f x)

You've probably figured out already what the monad instance is going to look like; there's only one function there which matches up with the type signature for >>=.

instance Monad List where return x = Item x End list >>= f = concatMap f list

It's not immediately clear what this instance could be used for, but it's great whenever one needs to build or convert lists. Here's an implementation of filter, which reduces a list to items matching some predicate:

filter :: (a -> Bool) -> List a -> List a filter p list = do x <- list if p x then return x else End

State: Stateful computation with pure functions

State's a big deal in Haskell; much thought has been put into the best way to cleanly write stateful algorithms in a language which discourages them. The simplest way discovered so far is to simply define each step of the algorithm as a state transformer. That is, it takes a state as input, and returns it (plus a result value) as output. Lets look at a stateful way to sum a set of numbers; each call returns the current sum, and a state to get the next sum:

data SumState = SumState Integer statefulSum :: Integer -> SumState -> (Integer, SumState) statefulSum x inState = let SumState inSum = inState newSum = inSum + x newState = SumState newSum in (newSum, newState)

We can also define a function to set the current sum to anything; this function uses the () type, which is the Haskell equivalent of void. Note that this function ignores its input state.

setSum :: Integer -> SumState -> ((), SumState) setSum x _ = ((), SumState x)

We can see that both functions have a similar type signature; we'll represent this similarity using a new data type, State:

data State s a = State (s -> (a, s)) statefulSum :: Integer -> State SumState Integer statefulSum x = State (\inState -> let SumState inSum = inState newSum = inSum + x newState = SumState newSum in (newSum, newState)) setSum :: Integer -> State SumState () setSum x = State (\_ -> ((), SumState x))

Note that State values contain a function; each State is a state transformer. Lets define how State should behave as a monad:

runState :: State s a -> s -> (a, s) runState (State f) = f instance Monad (State s) where return x = State (\inState -> (x, inState)) first >>= f = State (\inState -> let (result, newState) = runState first inState in runState (f result) newState)

We can use monadic state to clean up statefulSum and setSum:

getState :: State s s getState = State (\inState -> (inState, inState)) setState :: s -> State s () setState newState = State (\_ -> ((), newState)) statefulSum :: Integer -> State SumState Integer statefulSum x = do SumState inSum <- getState let newSum = inSum + x setState (SumState newSum) return newSum setSum :: Integer -> State SumState () setSum x = setState (SumState x)

Summary

Monads can be understood in terms of existing, well-known concepts. By gaining an intuitive grasp of the purpose and use of monads, a student of Haskell can write effective programs well before fully understanding the category theory behind their code. Students are, of course, encouraged to go farther if interested — but you don't need to know what an endofunctor is before taking advantage of Haskell.