4

I'm encountering strange behaviour with IO, within compiled Haskell code. Here's what's going on:

-- MyScript.hs
main = do
    putStr "Enter your name: "
    a <- getLine
    putStrLn (a ++ " - that's a nice name!")

I run this in GHCi by calling main and it works as one would expect, first printing Enter your name: and then doing whatever it's to do afterwards. However, when I compile it with GHC (With and without --make), it first prompts for a line, and then prints everything at once, like this:

$ ./MyScript
Jimmy Johnson
Enter your name: Jimmy Johnson - That's a nice name!

To clarify, I want it to occur in the following sequence:

$ ./MyFixedScript
Enter your name: Jimmy Johnson
Jimmy Johnson - That's a nice name!

Could someone explain why this happens as it is, and how to sequence the IO the way that I would expect it to.

Note also that I've tried changing the first line of the do statement to _ <- putStr "Enter your name: ", but that still doesn't work.

AJF
  • 11,767
  • 2
  • 37
  • 64

2 Answers2

15

The IO actions are happening in the correct order, the problem lies in how input and output pipes work. The string "Enter your name: " is written to the output buffer by putStr before the getLine, but the buffer hasn't necessarily been flushed. Adding hFlush stdout after the putStr will flush the buffer.

import System.IO

-- MyScript.hs
main = do
    putStr "Enter your name: "
    hFlush stdout
    a <- getLine
    putStrLn (a ++ " - that's a nice name!")
Cirdec
  • 24,019
  • 2
  • 50
  • 100
  • Wow, I had a thought that that might have been the case, but I didn't believe Haskell would actually obey a stateful thing like flushing stdout... – AJF Dec 05 '14 at 20:49
  • 1
    @AJFarmar: In this case, Haskell just gives you a pretty direct interface to the same underlying pipe abstraction as other programming languages. Since it's tucked away inside `IO`, it can just replicate the behavior exactly. – Tikhon Jelvis Dec 05 '14 at 22:10
  • And I imagine GHCi flushes automatically? – AJF Dec 06 '14 at 08:35
4

I have the exact same problem today, and it appears that it worked well when I was using putStrLn but stopped when I changed it to putStr. As other people said, it's not related to Haskell or GHC but how IO are flushed. According to the System.IO documentation there are 3 buffering mode :

  • line-buffering: the entire output buffer is flushed whenever a newline is output, the buffer overflows, a System.IO.hFlush is issued, or the handle is closed.
  • block-buffering: the entire buffer is written out whenever it overflows, a System.IO.hFlush is issued, or the handle is closed.
  • no-buffering: output is written immediately, and never stored in the buffer.

The default buffering mode is said to besystem dependent, but it seems that normal program are in line-buffering mode, whereas GHCI is in no-buffering mode. This explain because using putStrLn or putStr will flush or not.

To solve your problem, you can use hFlush stdout to flush explictitey (see Cirdec answer) or change the buffering mode once by doing hSetBuffering stdout NoBuffering. Obvioulsly the NoBuffering mode is not optimal but is probably enough for small toy program.

Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
mb14
  • 22,276
  • 7
  • 60
  • 102
  • The handle for `hSetBuffering` is the first argument. `hSetBuffering :: Handle -> BufferMode -> IO ()` so setting `NoBuffering` on `stdout` would be `hSetBuffering stdout NoBuffering`. – Cirdec Dec 06 '14 at 23:05