Haskell将putStr和putStrLn放在程序结尾而不是执行过程中。

3

我有一个简单的程序,它只需从用户那里获取一个字符串和一个密钥,然后使用凯撒密码函数加密该字符串。函数本身能够正常工作,因此我不会展示其源代码。问题在于,当编译器编译程序时,它允许我输入所有的getLines,然后在输入完所有内容后,程序将打印所有的putStr和putStrLn,然后关闭。这种情况仅发生在使用"runhaskell"或编译并以exe形式执行程序时,也就是不在解释器中运行时。以下是该程序:

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

在解释器中运行时的输出:

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

编译后执行的输出结果:
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

我是否忽略了putStrLn和putStr的某些内容?它们只在函数的结果下执行吗?

此外,我创建的“prompt”函数不是问题所在,因为我已经用它们的相应putStr和getLine替换了所有的prompt使用,并且它仍然表现出相同的问题。


4
尝试使用 prompt str = do { putStr str; hFlush stdout; getLine }。意思是:输入一个字符串并返回用户的输入。 - Will Ness
1
默认情况下,stdout 是按行缓冲的。除非你填满整行,否则需要将其刷新。可使用 do putStr "..." ; hFlush stdout ; getLine - chi
2
或者使用hSetBuffering stdout NoBuffering(来自System.IO)。 - Thomas M. DuBuisson
1个回答

8

runhaskellghci旨在尽快启动您的程序,并弱化运行程序的效率。出于这个原因,与ghc相比,它们做出了许多次优化的决策。其中一个问题是,默认情况下它们不对标准输入或输出进行缓冲,而ghc默认使用更高效的行缓冲。由于您在prompt期间从未打印换行符,在编译版本中,缓冲区不会显示给用户...直到您在程序末尾使用putStrLn打印一行结束符,然后整个缓冲区才会一次性显示出来。

你有几种选择:

  1. 显式请求没有缓冲。这会使您编译的程序略微变慢(在人类交互速度下很难被注意到),但表现得更像解释版本。在main的开头导入System.IO并使用hSetBuffering stdout NoBuffering
  2. 在您知道要暂停等待用户输入时,显式刷新缓冲区。导入System.IO并在每次调用getLine之前使用hFlush stdout
  3. 以在两种缓冲模式下表现相同的方式输出。在任何地方都使用putStrLn而不是putStr

这可能是标准库中的一个错误。如果 stdout 和 stdin 都是行缓冲区(或者也许当它们都链接到终端时),它应该自动刷新 stdout,然后再从 stdin 中读取。 - n. m.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接