`mapM` maps an action-producing function across a collection, performs the resulting actions in order, and collects their results. For lists in `IO`, it is the action-based counterpart of `map`: where `map` computes pure results, `mapM` can actually run an action for every element. section 4.3.5
With `putStrLn`, each action returns `()`, so `mapM putStrLn` produces a list of units after printing. Choose `mapM_` instead when those collected results are useless: it performs the same actions but discards their outputs and returns one `()`. section 4.3.5 section 4.5
Printing every line
Applying `mapM putStrLn` to a list prints each string in sequence and returns `[(),(),()]`. The visible list is the collected return value of the individual printing actions, not additional output from `putStrLn`. section 4.3.5
ghci> mapM putStrLn ["Hello", "World", "!"] Hello World ! [(),(),()] ghci> mapM_ putStrLn ["Hello", "World", "!"] Hello World !
Compare `mapM` and `mapM_` in GHCi: both print every string, but only `mapM` retains the list of action results. section 4.3.5
`mapM` also works beyond `IO`. Its general type uses a `Traversable` collection and a `Monad`: `(a -> m b) -> t a -> m (t b)`. In `Either String`, it transforms elements one by one and stops with the first `Left`, making it useful for validating parsed pixel data while preserving successful transformed values. section 13.2
Validation with Either
A function that rejects zero can be mapped over `[1..10]` to produce `Right [1,2,3,4,5,6,7,8,9,10]`. Mapping it over `[0..10]` produces `Left "zero is not allowed"`, because `Either` short-circuits when an element fails. section 13.2
