What is the difference between forM and forM_ in haskell?

Viewed 10008

HLint suggests that I use forM_ rather than forM. Why? I see they have different type signatures but haven't found a good reason to use one over the other.

forM  :: (Traversable t, Monad m) => t a -> (a -> m b) -> m (t b)
forM_ :: (Foldable t,    Monad m) => t a -> (a -> m b) -> m ()
3 Answers

To understand the difference between (A): forM xs f and (B): forM_ xs f, it might help to compare the difference between the following:

-- Equivalent to (A)
do
  r1 <- f x1
  r2 <- f x2
  ...
  rn <- f xn
  return [r1, r2, ..., rn]

-- Equivalent to (B)
do
  _ <- f x1
  _ <- f x2
  ...
  _ <- f xn
  return ()

The crucial difference being that forM_ ignores the results r1, ... rn and just returns an empty result via return (). Think of the underscore as meaning "don't care" ... forM_ doesn't care about the results. forM however, does care about the results and returns them in as a list via return [r1, r2, ... rn].


Example 1 The code below asks for your name three times and prints the results of the forM.

import Control.Monad (forM, forM_)

main = do
  let askName i = do
      putStrLn $ "What's your name (" ++ (show i) ++ ")"
      name <- getLine
      return name
  results <- forM [1,2,3] askName
  putStrLn $ "Results = " ++ show results

An example execution with forM:

What's your name? (1)
> James
What's your name? (2)
> Susan
What's your name? (3)
> Alex
Results = ["James", "Susan", "Alex"]

But if we change the forM to a forM_, then we would have instead:

What's your name? (1)
> James
What's your name? (2)
> Susan
What's your name? (3)
> Alex
Results = ()

In your case, the linter is telling you that you're not using the return values of your forM (you don't have foo <- forM xs f, you probably have forM xs f by itself on a line) and so should use forM_ instead. This happens, for example, when you are using a monadic action like putStrLn.


Example 2 The code below asks for your name and then says "Hello" – repeating three times.

import Control.Monad (forM, forM_)

main = do
  let askThenGreet i = do
      putStrLn $ "What's your name (" ++ (show i) ++ ")"
      name <- getLine
      putStrLn $ "Hello! " ++ name
  forM [1,2,3] askThenGreet

An example execution with forM:

 What's your name? (1)
> Sarah
Hello! Sarah

What's your name? (2)
> Dick
Hello! Dick

What's your name? (3)
> Peter
Hello! Peter

[(), (), ()]

The overall result of main comes from the result of the forM: [(), (), ()]. It's pretty useless and annoyingly, it appears in the console. But if we change the forM to a forM_, then we would have instead:

What's your name? (1)
> Sarah
Hello! Sarah

What's your name? (2)
> Dick
Hello! Dick

What's your name? (3)
> Peter
Hello! Peter

With that change, the overall result comes from the mapM_ and is now (). This doesn't show up in the console (a quirk of the IO monad)! Great! Also, by using mapM_ here, it's clearer to other readers of your code – you're indirectly explaining / self-documenting that you don't care about the results [r1, ..., rn] = [(), (), ()] – and rightly so as they're useless here.

Related