Haskell -- problem with pretty-printing a list
Asked Answered
O

1

11

I'm new to haskell, and i read through and digested Learn You A Haskell For Great Good, trying out a couple of things along the way. For my first project i wanted to try the classic: FizzBuzz. So i came up with the following code:

import System.IO

fizzBuzz :: (Integral a) => a -> String
fizzBuzz num
    | fizz && buzz = "FizzBuzz"
    | fizz = "Fizz"
    | buzz = "Buzz"
    | otherwise = show num
    where fizz = num `mod` 3 == 0
          buzz = num `mod` 5 == 0

main = print $ map fizzBuzz [1..100]

Worked great, except i got a rather dense looking list that was hard to read. So i tried this main function instead:

main = map putStrLn $ map fizzBuzz [1..100]

And that gives me the error Couldn't match expected type 'IO t' against inferred type '[IO ()]'. I tried half a dozen things and none of it seemed to help. What's the proper way to do what i'm trying to do?

Overprint answered 6/1, 2010 at 21:44 Comment(0)
J
26
map :: (a -> b) -> [a] -> [b]
putStrLn :: Show a => a -> IO ()
map putStrLn :: Show a => [a] -> [IO ()]

You've got a list of IO () actions.

main :: IO ()

You need to join them into a single IO () action.

What you want to do is to perform each of those IO () actions in sequence/sequence_:

sequence :: Monad m => [m a] -> m [a]
sequence_ :: Monad m => [m a] -> m ()

For convenience, mapM/mapM_ will map a function over a list and sequence the resulting monadic results.

mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

So your fixed code would look like this:

main = mapM_ putStrLn $ map fizzBuzz [1..100]

Although I'd probably write it like this:

main = mapM_ (putStrLn . fizzBuzz) [1..100]

Or even this:

main = putStr $ unlines $ map fizzBuzz [1..100]

Let's write our own sequence. What do we want it to do?

sequence [] = return []
sequence (m:ms) = do
    x <- m
    xs <- sequence ms
    return $ x:xs
  • If there's nothing left in the list, return (inject into the monad) an empty list of results.
  • Otherwise, within the monad,
    • Bind (for the IO monad, this means execute) the first result.
    • sequence the rest of the list; bind that list of results.
    • Return a cons of the first result and the list of other results.

GHC's library uses something more like foldr (liftM2 (:)) (return []) but that's harder to explain to a newcomer; for now, just take my word that they're equivalent.

sequence_ is easier, since it doesn't bother keeping track of the results. GHC's library implements it as sequence_ ms = foldr (>>) (return ()) ms. Let's just expand the definition of foldr:

  sequence [a, b, c, d]
= foldr (>>) (return ()) [a, b, c, d]
= a >> (b >> (c >> (d >> return ())))

In other words, "do a, discard the result; do b; discard the result, … finally, return ()".

mapM  f xs = sequence  $ map f xs
mapM_ f xs = sequence_ $ map f xs

On the other hand, you don't even need to know monads at all with the alternate unlines solution.

What does unlines do? Well, lines "a\nb\nc\nd\n" = ["a", "b", "c", "d"], so of course unlines ["a", "b", "c", "d"] = "a\nb\nc\nd\n".

unlines $ map fizzBuzz [1..100] = unlines ["1", "2", "Fizz", ..] = "1\n2\nFizz\n..." and off it goes to putStr. Thanks to the magic of Haskell's laziness, the full string never needs to be constructed in memory, so this will happily go to [1..1000000] or higher :)

Jurist answered 6/1, 2010 at 21:53 Comment(3)
Thank you, i knew haskell had something like that but i couldn't figure it out! now to understand why it works... :)Overprint
As I tried to explain, you generated a list of IO () actions, but you need to say what you want to do with them. sequence runs a list of monads in order, which happens to be what you want here. My last solution, instead, joins the fizz/buzz strings into a single multi-line string before it heads off into the strange land of IO.Jurist
Yep, i just didn't make the connection between a monad and an IO action -- some of that tutorial is still a little fuzzyOverprint

© 2022 - 2024 — McMap. All rights reserved.