35

I've already checked Hoogle, http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#v:mapM, which says that mapM_ ignores the results.

However, I still don't have idea how to use it properly.

main = mapM_ (putStrLn.show) [1,2]

main = mapM (putStrLn.show) [1,2]

main = map (putStrLn.show) [1,2]
Enlico
  • 23,259
  • 6
  • 48
  • 102
Gogo Tanaka
  • 835
  • 2
  • 9
  • 10

4 Answers4

49

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
Hugh
  • 8,872
  • 2
  • 37
  • 42
ocharles
  • 6,172
  • 2
  • 35
  • 46
  • 1
    "Building this list has a runtime cost". In a lazy language, if we never actually consume anything from the list, does that cost still apply? – dainichi Jan 28 '22 at 01:32
20

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.

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
  • Do you mind explaining why you use the rather specific "action" in your answer? Wouldn't Maybe (Option in F#) be a potential `m` in `a -> m b` ? It'd be great if you could clarify that a bit for newbies such as myself :) – GrumpyRodriguez Aug 18 '22 at 07:27
9

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

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
4

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
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
quant
  • 2,184
  • 2
  • 19
  • 29
  • 1
    Something is always returned unless there's an error or the computation fails to terminate. GHCi just doesn't print out the result of an `IO` action if that result is `()`. If you wrote `mapM_ (putStrLn . show) [1,2] >>= print`, you'd get `1`, `2`, and `()`. – dfeuer Dec 23 '14 at 04:00