6

I would like my commandline Haskell program to function like this: program wait for user input,

  1. user type something, push "enter"
  2. Haskell process the input, shows the result on stdout
  3. Haskell waits for next user input
  4. If no more input, user terminate program by Ctrl+D

I tried getContents. But getContents wait for user to type all lines before processing them.

1
  • 2
    getContents returns a lazy list of characters. Perhaps the consuming function is too strict. Commented Apr 17, 2012 at 16:38

3 Answers 3

9

There's a lot of confusion going on here. Let's try to clear things up.

I tried getContents. But getContents wait for user to type all lines before processing them.

The most likely thing here is that you've compiled your program, and didn't notice that the default buffering for output was block-buffering. This is easy to fix:

f line = putStrLn ("Hi, " ++ line ++ "!")

main = do
    hSetBuffering stdout LineBuffering -- or use NoBuffering
    putStrLn "Enter some names."
    input <- getContents
    mapM_ f (lines input)

You should use NoBuffering if you don't plan on printing a whole line (including newline) after each line of user input.

For a more precise answer, we'll need to see the code you tried that didn't work.

Q: But in my first try, I use: "interact show" and it doesn't work. Do you know why?
A: Because show will not return any output until its entire input has been exhausted.

This answer is not quite correct. The real answer is that show produces a string with no newlines in it! (Though the character sequence ['\\','\n'] does show up sometimes if the input is more than one line long.) So, for interact show, you really must use NoBuffering on stdout. For example, if you use this:

main = do
    hSetBuffering stdout NoBuffering
    interact show

...the program will print a bit more output after each line. You might also want to set stdin's buffering to NoBuffering (instead of the default LineBuffering), since show is productive enough that it really can produce more output after each keystroke.

Sign up to request clarification or add additional context in comments.

Comments

7

You can try using the interact function. It takes a function of type String -> String and converts it into an action that reads stdin, passes it continuously to the function, and writes the string that the function returns to stdout. If you take care not to overconsume the input string, you will get the behavior that you requested.

8 Comments

In particular, interact (unlines . map processTheInput . lines).
Great it works ! But in my first try, I use: "interact show" and it doesn't work. Do you know why ?
@osager Because show will not return any output until its entire input has been exhausted; interact requires an incrementally evaluating function.
@osager Also, check that your buffering is set to Line
@Ptharien'sFlame Someone from IRC channel suggest this and I used it: hSetBuffering stdin LineBuffering. But "interact show" still doesn't work
|
5

Use isEOF and getLine to read input.

This avoids the lazy i/o problems you got using interact.

(If we don't use isEOF, an exception will be thrown when we press Ctrl+D.)

import Control.Monad (unless)
import System.IO (isEOF)

processEachLine :: (String -> IO a) -> IO ()
processEachLine k = do
        finished <- isEOF
        unless finished $ do
                k =<< getLine
                processEachLine k

Here I assume that the k function prints the required output itself. It is easy to modify processEachLine to print the output so that k can be pure.

1 Comment

And if you want to feed in a function of type String -> String, you can easily create a similar helper: processLinesPure :: (String -> String) -> IO (); processLinesPure f = processEachLine (putStrLn . f)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.