39

I'm a Haskell beginner, I'm just beginning to wrap my head around Monads, but I don't really get it yet. I'm writing a game that consists of asking the user for input, and responding. Here is a simplified version of my function:

getPoint :: IO Point
getPoint = do
    putStr "Enter x: "
    xStr <- getLine
    putStr "Enter y: "
    yStr <- getLine
    return $ Point (read xStr) (read yStr)


completeUserTurn :: (Board, Player) -> IO (Board, Player)
completeUserTurn (board, player) = do
    putStr $ "Enter some value: "
    var1 <- getLine
    putStr $ "Enter another value: "
    var2 <- getLine
    putStr $ "Enter a point this time: "
    point <- getPoint
    if (... the player entered legal values ...) then do
        putStr $ "This is what would happen if you did that: {stuff} do you want to do that? (y/n) "
        continue <- getLine
        if continue == "y" then
            return (...updated board..., ...updated player...)
        else
            completeUserTurn (board, player)
    else do
        putStr "Invalid Move!\n"
        completeUserTurn (board, player)

What's happening is that the prompts will appear out of order with the text that is supposed to appear before the prompt.

Here's an example of what's happening after I compiled the code above:

1
Enter some value: Enter another value:2
3
4
Enter a point this time: Enter x: Enter y: y
Is this correct? (y/n):

The bold are the things I typed in.

Obviously, I have some major conceptual error, but I don't know what. Note that it works correctly in the interpreter and fails when compiled.

nbro
  • 15,395
  • 32
  • 113
  • 196
Drew
  • 12,578
  • 11
  • 58
  • 98

3 Answers3

65

As Michael said, the issue is buffering. By default, output is buffered until you print a newline (or until the buffer is full if you have really long lines), so you'll most often see this issue when trying to do same-line prompts using putStr like you're doing.

I suggest defining a small helper function like this to take care of doing the flushing for you:

import System.IO

prompt :: String -> IO String
prompt text = do
    putStr text
    hFlush stdout
    getLine

Now you can simply do

getPoint = do
    xStr <- prompt "Enter x: "
    yStr <- prompt "Enter y: "
    return $ Point (read xStr) (read yStr)
hammar
  • 138,522
  • 17
  • 304
  • 385
  • 17
    This is the same behavior as the C stdio library, except that stdio has a hack that I argued Haskell should have as well, namely if you read from stdin then you flush stdout, if both are a tty. – augustss Nov 02 '12 at 08:23
  • 2
    You may also use the Applicative form: `value <- putStr "Enter a value: " *> hFlush stdout *> getLine` – CtC Jan 19 '21 at 19:50
19

The IO is happening in the correct order. The issue is buffering. If you flush stdout after each putStr, it should work as expecting. You'll need to import hFlush and stdout from System.IO.

Michael Snoyman
  • 31,100
  • 3
  • 48
  • 77
13

The problem wasn't with the order of operations in the IO code. The issue was input and output is by default buffered when using stdin and stdout. This increases the performance of IO in an app, but can cause operations to appear to occur out of order when both stdin and stdout are used.

There is two solutions to this. You can use the hFlush method to force a handle (either stdin or stdout) to be flushed. Eg hFlush stdout, hFlush stdin. A simpler solution (which works fine for interactive apps) is to disable buffering altogether. You can do this by calling the methods hSetBuffering stdout NoBuffering and hSetBuffering stdin NoBuffering before you start your program (ie put those lines in your main method.

David Miani
  • 14,518
  • 2
  • 47
  • 66