« Real World HaskellJavascript scope creed »

I finally understand Haskell's List monad

01/09/08

Permalink 09:23:51 am, 377 words
Categories: Code

I finally understand Haskell's List monad

So I needed to write code to calculate the Cartesian product of two lists, and I happened to be using Haskell since I was writing a tiny prototype of tree kernels*.

That’s easy to do imperatively (Python):

acc = []
for x in xs:
  for y in ys:
    acc.append((x,y))

Or with a list comprehension (Haskell):

cartesian (xs,ys) = [(x,y) | x <- xs, y <- ys]

But Python and Haskell’s list comprehensions both put the expression first and the generators afterwards, like math notation. I prefer the way Scala and C#/F# do it, with the generators first and the expression afterward. Normally I wouldn’t worry about it much, but I was reading the Wikipedia page on using monads and I found a real example of using the List monad with do notation. This is rare. Usually, the List monad is described in papers right between the Maybe monad and IO monad, sort of in passing, so the examples were subpar and never really made sense to me.

So now I know that it’s trivial to turn the above list comprehension around to use do notation.

cartesian (xs,ys) = do { x <- xs; y <- ys; return (x,y) }

Syntactically, it looks a little noisier to me, but that’s probably because I don’t like braces, semi-colons or return (and if you’re used to other languages, the do/return pair looks weird compared to the normal for/yield). I do like having the generators show up first. Semantically, there is no change in the result, only the type, which now allows you to pass other kinds of things besides lists:

*Main> cartesian ([1..4], [1..2])
[(1,1),(1,2),(2,1),(2,2),(3,1),(3,2),(4,1),(4,2)]  -- normal, with lists
*Main> cartesian (Just 4, Just 2)  -- Maybe monad
Just (4,2)
*Main> cartesian (Nothing, Just 4)  -- oh no! Nothing! Abort! Abort!
Nothing

So do gives you automatic flattening for lists, just like list comprehensions. For Maybe (which are much like nullable types), you get automatic null propagation–if either xs or ys is Nothing, then you don’t get your (x,y) answer–you get Nothing instead.

Incidentally, here is a generalised Cartesian product, although it may be kind of slow since it’s recursive. It’s called cross because that’s the wrong name I normally use.

cross [] = [[]]
cross (l:ls) = [x:rest | x <- l, rest <- cross ls]

7 comments

Comment from: Russell [Visitor]

You will find that Control.Monad.sequence is your cross function.

01/09/08 @ 12:47
Comment from: Evan M [Visitor] Email
Curlies are optional:
do x
01/09/08 @ 13:38
Comment from: Evan M [Visitor] Email
Curlies are optional:
do x &lt- xs; y &lt- ys; return (x,y)

Once you're using monads you can use the whole monad library to write point-free code. Two other options:

liftM2 (,) xs ys

return (,) `ap` xs `ap` ys
01/09/08 @ 13:41
Comment from: sandersn [Member]
@Russell: I thought I might. I don't know the Haskell library at all. Thanks. Is Control.Monad.sequence as inefficient as cross is in an eager language? There, I use the numeric 'odometer' method in real library code that needs to be be efficient.

@Evan M: Thanks for the curly tip. I didn't know about the (,) syntax for constructing tuples either.
01/09/08 @ 16:07
Comment from: josh [Visitor] Email
Note that Control.Monad.sequence is in the standard prelude: http://haskell.org/onlinereport/standard-prelude.html#$vsequence
01/09/08 @ 22:52
Comment from: Jake McArthur [Visitor] Email · http://geekrant.wordpress.com
Just FYI, your cartesian :: ([a], [b]) -> [(a, b)] function can be simplified down to cartesian = uncurry $ liftM2 (,), and if you are willing to change the type to cartesian :: [a] -> [b] -> [(a, b)] then it can be defined simply as cartesian = liftM2 (,).
02/09/08 @ 00:23
Comment from: herty [Visitor] Email · http://www.picktorrent.com/
Curlies are optional:
do н
27/08/09 @ 03:58

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)
powered by b2evolution