Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between forM and forM_ in haskell?

Tags:

haskell

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 ()
like image 673
Xavier Shay Avatar asked Jul 14 '11 05:07

Xavier Shay


3 Answers

The forM_ function is more efficient because it does not save the results of the operations. That is all. (This only makes sense when working with monads because a pure function of type a -> () is not particularly useful.)

like image 191
Dietrich Epp Avatar answered Nov 13 '22 13:11

Dietrich Epp


Ok,

forM is mapM with its arguments flipped.

forM_ is mapM_ with its arguments flipped.

Let's see in mapM and mapM_ :

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

mapM mf xs takes a monadic function mf (having type Monad m => (a -> m b)) and applies it to each element in list xs; the result is a list inside a monad. The difference between mapM and mapM_ is, that mapM returns a list of the results, while mapM_ returns an empty result. The result of each action in mapM_ is not stored.

like image 28
0xAX Avatar answered Nov 13 '22 12:11

0xAX


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.

like image 7
James Lawson Avatar answered Nov 13 '22 14:11

James Lawson