It's because of how a right fold works, yes. A fold function is an accumulation: At each step, given one element (in this case, the key and value) and the accumulated result of the rest of the data, it combines them into a single result. The fold as a whole does that recursively to summarize the entire set of data.
In your case, you're discarding the "result" input of the accumulator function--note that the b
argument is never used. Keep in mind that IO a
isn't just a value of type a
with some extra junk attached, what it actually represents is a computation to produce an a
, and that computation will only be run by combining it with other computations as part of the final value of the main
function (or, in GHCi, of the expression that gets evaluated).
By discarding the accumulated value, the other computations never become part of the final result, and so the values never get printed.
From the way you phrased the question I'm guessing that you're still more comfortable in imperative-style programming than Haskell's functional style. Obviously, in an imperative language where the printing is actually "happening" during the fold in some meaningful sense, it would be reasonable to assume that the accumulated value is useless. If it helps, think of this more as a sort of meta-programming; you're not actually writing a loop to print the values, you're constructing an imperative program that does the actual printing, and by discarding the accumulated value you're basically discarding all but the first lines of an unrolled loop, to abuse the analogy badly.
At any rate, what you probably want in this case is to take the "print the rest of the data" action, the b
parameter, and combine it with the putStrLn ...
action using (>>)
, an operator that basically means "execute the first action, ignore the result, execute the second". This is a pretty direct translation of the imperative style "print statement in a loop".
Also, while I understand that it's beside the point entirely, I'd probably avoid mixing the formatting and printing that way anyhow. To my eye, it seems tidier to format each key/value pair separately into a list, then just mapM_ putStrLn
over that.
mapM_
is a higher-order function that describes the essence of what you're doing here; given a list of some type a
and a function that turns an a
into an IO
action of some sort, it applies the function to each item and runs the resulting list of actions in order. mapM_
has type Monad m => (a -> m b) -> [a] -> m ()
which seems cryptic at first, but one of the nice things about Haskell is that once you get used to reading type signatures, mapM_
's type is not only understandable at a glance, it's nearly self-documenting in that there's only one sensible thing for a function with that type to do and that's precisely what mapM_
itself does.