34

我是一个 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)

发生的情况是提示将与应该出现在提示之前的文本乱序出现。

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

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

粗体字是我输入的内容。

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

4

3 回答 3

59

正如迈克尔所说,这个问题正在缓冲。默认情况下,输出是缓冲的,直到你打印一个换行符(或者如果你有很长的行,直到缓冲区已满),所以当你尝试使用putStr你正在做的相同行提示时,你最经常会看到这个问题。

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

import System.IO

prompt :: String -> IO String
prompt text = do
    putStr text
    hFlush stdout
    getLine

现在你可以简单地做

getPoint = do
    xStr <- prompt "Enter x: "
    yStr <- prompt "Enter y: "
    return $ Point (read xStr) (read yStr)
于 2012-11-02T07:11:00.170 回答
17

IO 以正确的顺序发生。问题是缓冲。如果在每个 putStr 之后刷新标准输出,它应该可以按预期工作。您需要导入hFlushstdoutSystem.IO.

于 2012-11-02T06:56:02.047 回答
12

问题不在于 IO 代码中的操作顺序。问题是输入和输出在使用标准输入和标准输出时默认缓冲。这会提高应用程序中 IO 的性能,但可能会导致在同时使用 stdin 和 stdout 时操作出现乱序。

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

于 2012-11-02T07:07:32.517 回答