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 b
s), 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 UserId
s to a list of User
s:
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