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 xs
appliesf
to 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 fs
executes each of the actions infs
, once again in the order in which these actions occur infs
. -
zipWithM f xs ys
is much likemapM f xs
, onlyf
takes two arguments, one fromxs
and one fromys
. Once again, we applyf
to the first element inxs
and the first element inys
, then to the second element inxs
and the second element inys
, and so on until one of the two lists runs out of elements. -
foldM f init xs
appliesf
once for each element inxs
. In contrast tomapM
,f
actually takes two arguments, the current accumulator value and the current element inxs
, and we update the accumulator value to whatever valuef
returns. But this does not change the fact that, once again, we runf
once 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!