【问题标题】:Why isn't my IO executed in order?为什么我的 IO 没有按顺序执行?
【发布时间】:2026-02-22 19:30:01
【问题描述】:

我遇到了一个问题,即 IO 没有按顺序执行,即使在 do 构造中也是如此。

在下面的代码中,我只是跟踪剩下的牌,其中牌是一个字符元组(一个代表花色,一个代表价值),然后不断地询问用户哪些牌已经打出。我希望在每个输入之间执行putStr,而不是像现在这样在最后执行。

module Main where
main = doLoop cards
doLoop xs = do  putStr $ show xs
                s <- getChar
                n <- getChar
                doLoop $ remove (s,n) xs
suits = "SCDH"
vals = "A23456789JQK"
cards = [(s,n) | s <- suits, n <- vals]
type Card = (Char,Char)
remove :: Card -> [Card] -> [Card]
remove card xs = filter (/= card) xs

【问题讨论】:

    标签: haskell io buffering


    【解决方案1】:

    如果问题是我认为的那样,那么您的问题是 Haskell 的 IO 被缓冲:this question 解释了正在发生的事情。当您运行已编译的 Haskell 程序时,GHC 将输出存储在缓冲区中,并且只会定期将其刷新到屏幕上;如果 (a) 缓冲区太满,(b) 如果打印换行符,或者 (c) 如果您调用 hFlush stdout,则会这样做。

    您可能会看到的另一个问题是getChar 在读取换行符之前可能不会触发,但是换行符在您的输入流中;你也许可以用一个额外的getChar 来解决这个问题来吞下换行符,但应该有更好的方法。

    【讨论】:

    • 是的,好电话。自己试过了;在 GHCi 中工作正常,编译时什么都不打印,就像你链接到的问题一样。
    【解决方案2】:

    absz 的回答是正确的,Haskell 的缓冲 IO 是给您带来麻烦的原因。这是重写您的doLoop 以获得您正在寻找的效果的一种方法:

    doLoop xs = do  putStrLn $ show xs
                    input <- getLine
                    let s:n:_ = input
                    doLoop $ remove (s,n) xs
    

    两个变化:使用putStrLn 追加换行符并刷新输出(这可能是您想要的),并使用getLine 一次获取一行输入(同样,可能是您想要的) .

    【讨论】:

    • 错误代码!您在这里引入了潜在的模式匹配失败。
    • 诚然,这个案例远非详尽无遗,但作为概念证明(关于 IO 操作),它可以胜任。
    【解决方案3】:

    正如其他人指出的那样,putStr 形式的缓冲是您的问题。

    还有一个样式点:putStrLn $ show xsprint xs 相同

    【讨论】: