开发者

Can't perform I/O in foldr?

I have a Data.Map structure that maps Strings to Stringss. For whatever reason, I want to print the contents of the map in the format key: value using foldrWithKey, like so:

M.foldrWithKey (\k v b -> putStrLn (k++": "++v++"\n")) (retur开发者_C百科n ()) data

However, only the first element of the map appears in the output (even though the map has more than one element). But when I try to construct a list using foldrWithKey and then print it, all of the elements show up:

print (M.foldrWithKey (\k v b -> k:b) [] data)

So, why don't the other elemnts show up when trying to perform I/O? Is it the way foldr works or is there some subtle lazy-io-related quirk that I'm missing?


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.


Here is a clearer example of what is happening, without using I/O.

foldr (\x b -> x) 9 [8,7,6,5,4,3,2,1,0]

This expression returns 8, the head of the list. Where does the rest of the list go? Well, the result of processing the rest of the list is passed in 'b', which is not used, so the rest of the list is simply ignored.

The same thing is happening in your case. By ignoring the accumulator 'b', you are constructing an I/O action that only uses one element of the map. Basically you have said, "to print a map, print its first key and value." What you should say is, "to print a map, print its first key and value, then print the rest of the map." To do that, you need to arrange for the contents of variable 'b' to run after the call to putStrLn runs:

M.foldrWithKey (\k v b -> do {putStrLn (k ++ ": " ++ v ++ "\n"); b}) (return ()) d


It's somewhat bad form to intermix IO and pretty printing like that, so how about floating the IO out:

> putStr $ foldrWithKey (\k v b -> b ++ k ++ ": "++v++"\n") [] m

Now, as for why your code isn't working, think about what your fold is building up: a sequence of print statements, in the b parameter. However, you throw away b each time around the loop!

So keep track of it:

> foldrWithKey (\k v b -> putStrLn (k++": "++v) >> b) (return ()) m   

Lesson, don't throw away your accumulators.


when you fold with (\k v b -> putStrLn (k++": "++v++"\n")) you don't use the b anywhere, so all you got left is the last IO () that is left in the fold. Therefore it prints the first value. You can prevent this by folding with (\k v b -> putStrLn (k++": "++v++"\n") >> b).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜