Sequencing Actions for Their Effects
Here is something that mapM, zipWithM, foldM, and sequence have in
common: They all perform a sequence of actions:
-
mapM f xsappliesfto each element inxs. Each of these function applications is an action, and we perform these actions in the order in which the list elements occur inxs. -
sequence fsexecutes each of the actions infs, once again in the order in which these actions occur infs. -
zipWithM f xs ysis much likemapM f xs, onlyftakes two arguments, one fromxsand one fromys. Once again, we applyfto the first element inxsand the first element inys, then to the second element inxsand the second element inys, and so on until one of the two lists runs out of elements. -
foldM f init xsappliesfonce for each element inxs. In contrast tomapM,factually takes two arguments, the current accumulator value and the current element inxs, and we update the accumulator value to whatever valuefreturns. But this does not change the fact that, once again, we runfonce for each element inxs, in the order in which the elements occur inxs.
If we want to observe the results of pure functions such as map, foldl or
zipWith, we need to inspect their return values. That's the only way pure
functions can communicate their results to us—there are no side effects. That's
no longer true for effectful functions such as mapM, foldM, zipWithM or
sequence. In addition to producing return values, each of these functions has
an effect, such as performing I/O or modifying the state of the State monad.
Sometimes, the effects of a sequence of actions are all we care about. We saw
this in the previous subsection when using sequence to print a sequence of
strings on screen:
>>> sequence [putStrLn "Hello", putStrLn "world!"]
Hello
world!
[(),()]
We didn't really care about the list of return values [(),()] here. For such
cases, we have
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
zipWithM_ :: Monad m => (a -> b -> m c) -> [a] -> [b] -> m ()
foldM_ :: Monad m => (b -> a -> m b) -> b -> [a] -> m ()
sequence_ :: Monad m => [m a] -> m ()
They all behave like their counterparts without underscores, but they return
() (void). Their implementation is straightforward once you remember that we
have the void function for
functors:
void :: Functor f => f a -> f ()
void xs = () <$ xs
In the context of a monad, void f runs the action f and then throws away its
return value.
With the help of this function, we obtain
mapM_ f xs = void $ mapM f xs
zipWithM_ f xs ys = void $ zipWithM f xs ys
foldM_ f init xs = void $ foldM f init xs
sequence_ fs = void $ sequence fs
We've seen one example of using sequence_ already:
>>> sequence_ [putStrLn "Hello", putStrLn "world!"]
Hello
world!
Similarly, we could print a list of strings by mapping putStrLn over the
strings in a list:
>>> mapM_ putStrLn ["Hello world!", "Hallo Welt!", "Hola mundo!"]
Hello world!
Hallo Welt!
Hola mundo!