26
votes

I’m new to Haskell and have a problem with interact function. This is my sample program:

main :: IO ()
main = interact inputLength

inputLength :: String -> String
inputLength input = show $ length input

It compiles but when running doesn’t print the output - just prints the string that is passed to it and moves to the next line. When I pass the interact another String -> String function like this:

upperCase :: String -> String
upperCase input = map toUpper input

it runs ok and prints the argument in uppercase as expected – so what is wrong with the first function?

2
I think interact is still a lovely way to write command-line programs that are meant to work with pipes. With IO I am tempted to write non-composable user-only programs.luqui

2 Answers

49
votes

The String -> String argument given to interact should take a string containing all the input and return a string containing all the output. The reason you see output after pressing enter with interact (map toUpper) is because map toUpper acts lazily -- it can start giving output before all the input is known. Finding the length of a string is not like this -- the whole string must be known before any output can be produced.

You need to either signal an EOF to say that you are done entering input (in the console, this is Control-D on Unix/Mac systems, I believe it's Control-Z on Windows), then it will give you the length. Or you can find the length of each line by saying so:

interact (unlines . map inputLength . lines)

This will always be lazy in each line, so you know you can get one output after each input.

Since acting on lines is such a common pattern, I like to define a little helper function:

eachLine :: (String -> String) -> (String -> String)
eachLine f = unlines . map f . lines

Then you can do:

main = interact (eachLine inputLength)
3
votes

A more reusable solution:

main = interactLineByLine processLine

-- this wrapper does the boring thing of mapping, unlining etc.... you have to do all the times for user interaction
interactLineByLine:: (String -> String) -> IO ()
interactLineByLine f = interact (unlines . (map processLine) . lines) 

-- this function does the actual work line by line, i.e. what is
-- really desired most of the times
processLine:: String -> String
processLine line = "<" ++ line ++ ">"