Haskell: Reading in multiple lines of arguments
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 http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad.html#v: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
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.