VII. The List Monad

Lists with their head:tail pattern matching are not intrinsically monadic. Haskell lets us manipulate lists the way we would expect to do in any functional language. Think of the List monad as adding a little something to the normal list processing functions.

The return method in the List monad is defined this way

return x = [x]  

The interior objects of a list are just the elements of the list and return x has just one interior object, x itself.

The (>>=) combinator is defined this way

(>>=) mon f = 
   concat (map f mon)  

Which means that

mon >>= f

is the list formed by applying f to each of mon's interior values and concatenating those of the resulting lists which are nonempty.

Here's a simple example

[1,2] >>= (\x -> [x+1])

which produces this List monad (aka list)

[2,3]

If you wanted to compile and run this code, you could write this source file

main = 
  putStrLn (show ([1,2] >>= (\x ->[x+1])))

List monads exemplify the kind of freedom we get when a type is both monadic and nonmonadic. We can make lists with both the [ ] constructor and with return. The [x+1] above could have been written return (x+1). Doing that, applying substitution rule 3, and thinking of return (x+1) as a function of x gives

do [1,2] >>= 
      (\x -> return (x+1))

Then substitution rule 2 leads us to an equivalent do block

do x <- [1,2]
   return (x+1)

What is interesting is that the do block looks a lot like a loop whose control variable x iterates over the interior elements of the list [1,2]. In monadic terms

\x -> return (x+1)

is evaluated for each of the interior objects in [1,2].

Creating loops in do blocks like this may be the main value of the List monad. Such do blocks take lists (which is to say List monadic objects) and turn them into lists. This is what the map function does in many programming languages. The do block however is more flexible.

For a more illustrative example, we will write a function pairs that forms the cross product of two lists. For example, we want

pairs [1,2] ['a','b']

to evaluate to

[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

Of course, we are going to take advantage of the loop-like behavior of

do x <- lst
   <loop body for x here>

And we need an inner loop as well.