使用getLine和putStr时,IO发生故障

Dre*_*rew 27 monads haskell user-input

我是Haskell的初学者,我刚刚开始围绕Monads,但我还没有真正得到它.我正在写一个游戏,包括询问用户输入和响应.这是我的函数的简化版本:

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)
Run Code Online (Sandbox Code Playgroud)

发生的事情是提示将出现在提示之前应该出现的文本乱序.

这是我编译上面代码后发生的事情的一个例子:

1
输入一些值:输入另一个值:2
3
4
这次输入一个点:输入x:输入y:y
这是否正确?(Y/N):

大胆的是我输入的内容.

显然,我有一些重大的概念错误,但我不知道是什么.请注意,它在解释器中正常工作,并在编译时失败.

ham*_*mar 50

正如迈克尔所说,问题在于缓冲.默认情况下,输出会被缓冲,直到您打印换行符(或者如果您的行很长,则直到缓冲区已满),因此在尝试使用putStr您正在执行的相同行提示时,通常会看到此问题.

我建议定义一个像这样的小助手函数来为你做冲洗:

import System.IO

prompt :: String -> IO String
prompt text = do
    putStr text
    hFlush stdout
    getLine
Run Code Online (Sandbox Code Playgroud)

现在你可以做到

getPoint = do
    xStr <- prompt "Enter x: "
    yStr <- prompt "Enter y: "
    return $ Point (read xStr) (read yStr)
Run Code Online (Sandbox Code Playgroud)

  • 这与C stdio库的行为相同,除了stdio有一个我认为Haskell应该具有的hack,即如果你从stdin读取然后你刷新stdout,如果两者都是tty. (16认同)
  • 您还可以使用应用形式: `value &lt;- putStr "Enter a value: " *&gt; hFlush stdout *&gt; getLine` (2认同)

Mic*_*man 13

IO正在以正确的顺序发生.问题是缓冲.如果你在每个putStr之后刷新stdout,它应该按预期工作.你需要进口hFlushstdoutSystem.IO.


Dav*_*ani 11

问题不在于IO代码中的操作顺序.问题是输入和输出在使用stdin和stdout时默认是缓冲的.这会增加应用程序中IO的性能,但是当使用stdin和stdout时,可能会导致操作无序发生.

有两种解决方案.您可以使用该hFlush方法强制刷新句柄(stdin或stdout).例如hFlush stdout,hFlush stdin.一个更简单的解决方案(适用于交互式应用程序)是完全禁用缓冲.您可以通过调用方法hSetBuffering stdout NoBufferinghSetBuffering stdin NoBuffering在启动程序之前执行此操作(即将这些行放在main方法中.