What is the difference between mapM_ and mapM in Haskell?

The core idea is that mapM maps an "action" (ie function of type a -> m b) over a list and gives you all the results as a m [b]. mapM_ does the same thing, but never collects the results, returning a m ().

If you care about the results of your a -> m b function (ie the bs), use mapM. If you only care about the effect, whatever it is, but not the resulting value, use mapM_ because it can be more efficient and, more importantly, makes your intentions clear.

You would always use mapM_ with functions of the type a -> m (), like print or putStrLn. These functions return () to signify that only the effect matters. If you used mapM, you'd get a list of () (ie [(), (), ()]), which would be completely useless but waste some memory. If you use mapM_, you would just get a (), but it would still print everything.

On the other hand, if you do care about the returned values, use mapM. As a hypothetical example, imagine a function fetchUrl :: Url -> IO Response—chances are, you care about the response you get for each URL. So for this, you'd use mapM to get a lists of responses out that you can then use in the rest of your code.

So: mapM if you care about the results and mapM_ if you don't.

Normal map is something different: it takes a normal function (a -> b) instead of one using a monad (a -> m b). This means that it cannot have any sort of effect besides returning the changed list. You would use it if you want to transform a list using a normal function. map_ doesn't exist because, since you don't have any effects, you always care about the results of using map.


mapM_ ignores the result. This means, that no return value will be returned. You can see this in interactive mode:

Prelude> mapM (putStrLn . show) [1,2]
1
2
[(),()]
Prelude> mapM_ (putStrLn . show) [1,2]
1
2

mapM_ is useful for executing something only for its side effects. For example, printing a string to standard output doesn't return anything useful - it returns (). If we have a list of three strings, we would end up accumulating a list[(), (), ()]. Building this list has a runtime cost, both in terms of speed and memory usage, so by using mapM_ we can skip this step entirely.

However, sometimes we need to execute side effects and build up a list of the results. If we have a function such as

lookupUserById :: UserId -> IO User

Then we can use this to inflate a list of UserIds to a list of Users:

lookupUsers :: [UserId] -> IO [User]
lookupUsers = mapM lookupUserById

In more general terms, the difference is that mapM_ only needs to "consume" all elements of the input, whereas mapM also needs to "re-build" the data structure, with new values. That's pretty much trivial for lists: you only need to traverse along the cons-cells, updating values with those received from the action you map. But it doesn't work that easily for containers whose structure depends on the actual contained values. So e.g. for Data.Set you can't define the equivalent of mapM with type (a -> m b) -> Set a -> m (Set b) – it is an instance of Foldable, but not of Traversable

Tags:

Haskell