BryceTheGrand
BryceTheGrand

Reputation: 663

Haskell placing putStr and putStrLn at the end of a program instead of during execution

I have a simple program that just takes a string from the user and a key and encrypts the string with a caesar cipher function. The function itself works so I won't show to source for that. The issue is that when the compiler compiles the program it will allow me to input all the getLines and then after having inputted everything the program will print all the putStr and putStrLn then close. This ONLY happens when the program is executed using "runhaskell" or compiled and executed as an exe, ie. not in the interpreter. Here is the program:

main = do

    choice <- prompt "Would you like to encrypt (1) or decrypt (2)? "

    if choice == "1" then do
        encrypt <- prompt "Enter code for encryption: "
        k       <- prompt "Enter key for the code:    "
        let key = read k in
            putStrLn     ("Encrypted message:         " ++ (caesar key encrypt)    ++ "\n")

    else do
        decrypt <- prompt "Enter code for decryption: "
        k       <- prompt "Enter key for the code:    "
        let key = read k in
            putStrLn     ("Decrypted message:         " ++ (caesar (-key) decrypt) ++ "\n")

    getLine

prompt str = do
    putStr str
    getLine

Output when run in the interpreter:

Prelude Main> main
Would you like to encrypt (1) or decrypt (2)? 1 <- the one is user input
Enter code for encryption: Hello world <- user input
Enter key for the code:    2           <- user input
Encrypted message:         Jgnnq"yqtnf <- program output

Output when executed after compilation:

1           <- user has to input before the console is printed out
Hello world <--┘
2           <--┘
Would you like to encrypt (1) or decrypt (2)? Enter code for encryption: Enter key for the code:    Encrypted message:         Jgnnq"yqtnf

Is there something about putStrLn and putStr that I am overlooking? Do they only execute as the result of a function or something?

Also, the "prompt" function I created is not the issue because I replaced all the uses of prompt with their respective putStr's and getLine's and it still did the same thing.

Upvotes: 4

Views: 1064

Answers (1)

Daniel Wagner
Daniel Wagner

Reputation: 153102

runhaskell and ghci are designed to start your program as quickly as possible, and de-emphasize the efficiency of running the program. For that reason they make many sub-optimal efficiency decisions compared to the ghc, and one that's biting you here is that they use no buffering on standard input or output by default, while ghc uses the more efficient line buffering by default. Since you never print a line ending during your prompts, in the compiled version, the buffer is not shown to the user... until you reach the putStrLn at the end of the program that prints a line ending, and the whole buffer is shown at once.

You have some choices:

  1. Explicitly request that there be no buffering. This will make your compiled program marginally slower (very unlikely to be noticed at human interaction speeds) but behave more like the interpreted version. Import System.IO and use hSetBuffering stdout NoBuffering at the beginning of main.
  2. Explicitly flush the buffer when you know you are going to pause for user input. Import System.IO and use hFlush stdout just before each call to getLine.
  3. Do output in a way that behaves the same between the two buffering modes. Use putStrLn instead of putStr everywhere.

Upvotes: 9

Related Questions