Is Haskell’s mapM not lazy?

Well, there’s lazy, and then there’s lazy. mapM is indeed lazy in that it doesn’t do more work than it has to. However, look at the type signature:

mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]

Think about what this means: You give it a function a -> m b and a bunch of as. A regular map can turn those into a bunch of m bs, but not an m [b]. The only way to combine the bs into a single [b] without the monad getting in the way is to use >>= to sequence the m bs together to construct the list.

In fact, mapM is precisely equivalent to sequence . map.

In general, for any monadic expression, if the value is used at all, the entire chain of >>=s leading to the expression must be forced, so applying sequence to an infinite list can’t ever finish.

If you want to work with an unbounded monadic sequence, you’ll either need explicit flow control–e.g., a loop termination condition baked into the chain of binds somehow, which simple recursive functions like mapM and sequence don’t provide–or a step-by-step sequence, something like this:

data Stream m a = Nil | Stream a (m (Stream m a))

…so that you only force as many monad layers as necessary.

Edit:: Regarding splitRandom, what’s going on there is that you’re passing it a Rand computation, evaluating that with the seed splitRandom gets, then returning the result. Without the splitRandom, the seed used by the single getRandom has to come from the final result of sequencing the infinite list, hence it hangs. With the extra splitRandom, the seed used only needs to thread though the two splitRandom calls, so it works. The final list of random numbers works because you’ve left the Rand monad at that point and nothing depends on its final state.

Leave a Comment