Mark Needham

Thoughts on Software Development

Haskell: Reading in multiple lines of arguments

without comments

I’ve mostly avoided doing any I/O in Haskell but as part of the Google Code Jam I needed to work out how to read a variable number of lines as specified by the user.

The input looks like this:

4
3 1 5 15 13 11
3 0 8 23 22 21
2 1 1 8 0
6 2 8 29 20 8 18 18 21

The first line indicates how many lines will follow. In this case we need to read in 4 lines.

The function to parse the input needed a type of ‘IO [String]‘. This was one of my first attempts:

readInput = do
  line <- getLine
  let count :: Int
      count = read line
  return $ [getLine | x <- [1..count]]

Unfortunately that doesn’t have the correct signature:

> :t readInput
readInput :: IO [IO String]

I thought I might be able to read the value from getLine and return a collection of those but that didn’t even compile:

readInput = do
  line <- getLine
  let count :: Int
      count = read line
  return $ [do line <- getLine; line | x <- [1..count]]
Couldn't match expected type `IO b0' with actual type `[Char]'
Expected type: IO b0
Actual type: String
  In the expression: line
  In the expression:
    do { line <- getLine;
         line }

I didn’t really understand what the error message was saying so I removed the syntactic sugar that ‘do’ provides and replaced it with the equivalent function calls.

readInput = do
  line <- getLine
  let count :: Int
      count = read line
  return $ [getLine >>= \line -> line | x <- [1..count]]
Couldn't match expected type `IO b0' with actual type `[Char]'
Expected type: IO b0
Actual type: String
In the expression: line
In the second argument of `(>>=)', namely `\ line -> line'

getLine returns us an IO Monad and ‘>>=’ allows us to read a value from a Monad. However, it expects the function passed as its second argument to return a Monad which leaves us in the same problematic situation!

readInput = do
  line <- getLine
  let count :: Int
      count = read line
  return $ [getLine >>= \line -> return line | x <- [1..count]]
> :t readInput
readInput :: IO [IO String]

I eventually came across a StackOverflow post which covered similar ground and used the replicateM function to solve the problem.

> :t replicateM
replicateM :: Monad m => Int -> m a -> m [a]

This is the function I ended up with:

readInput :: IO [String]
readInput = do
  line <- getLine
  let count :: Int
      count = read line
  lines <- replicateM (count) $ do
    line <- getLine
    return line
  return lines

If we wanted to print the arguments back to the user we could define our main function like so:

main = do
       lines <- readInput
       mapM_ (\(idx, line) -> putStrLn $ "Arg #" ++ show idx ++ " " ++ line) (zip [1..] lines)
> ./google_code_jam         
2
Mark
Needham
Arg #1 Mark
Arg #2 Needham
Be Sociable, Share!

Written by Mark Needham

April 15th, 2012 at 1:44 pm

Posted in Haskell

Tagged with