Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IO happens out of order when using getLine and putStr

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.

like image 764
Drew Avatar asked Nov 02 '12 06:11

Drew


3 Answers

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)
like image 92
hammar Avatar answered Oct 18 '22 08:10

hammar


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.

like image 18
Michael Snoyman Avatar answered Oct 18 '22 06:10

Michael Snoyman


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.

like image 12
David Miani Avatar answered Oct 18 '22 08:10

David Miani