Haskell getContents等待EOF

时间:2015-02-05 20:12:54

标签: haskell io monads

我想等到用户输入终止于EOF然后全部输出。这不是getContents应该做的吗?每次用户点击进入时,以下代码输出,我做错了什么?

import System.IO

main = do
  hSetBuffering stdin NoBuffering
  contents <- getContents
  putStrLn contents

3 个答案:

答案 0 :(得分:9)

根本问题是getContents Lazy IO 的实例。这意味着getContents产生的thunk可以像普通的Haskell值一样进行评估,只有在强制的情况下执行相关的IO

contentsputStr尝试打印的惰性列表,它强制列表并使getContents尽可能多地读取。 putStr然后打印所有被强制的内容,并继续尝试强制列表的其余部分,直到它达到[]。由于getContents可以读取越来越多的流 - 确切的行为取决于缓冲 - putStr可以立即打印越来越多的内容,为您提供所看到的行为。

虽然这种行为对于非常简单的脚本非常有用,但它将Haskell的评估顺序与 observable 效果联系起来 - 这是它从未打算做的事情。这意味着准确控制contents部分打印的时间是不方便的,因为你必须打破正常的Haskell抽象并准确理解事物的评估方式。

这导致一些可能不直观的行为。例如,如果您尝试获取输入的长度 - 实际上使用它 - 在打印之前强制列表,为您提供所需的行为:

main = do
  contents <- getContents
  let n = length contents
  print n
  putStr contents

但是如果您在print n之后移动putStr,则会回到原来的行为,因为在打印输入之后 才会强制使用n (即使n在使用putStr之前main = do contents <- getContents let n = length contents putStr contents print n 仍然定义了>:

contents

通常,这种事情不是问题,因为它不会改变代码的行为(尽管它可能会影响性能)。懒惰的IO只是通过刺穿抽象层将其带入正确的领域。

这也为我们提供了一个如何解决问题的提示:在打印之前我们需要一些强制length的方法。正如我们所看到的,我们可以使用length执行此操作,因为seq需要在计算结果之前遍历整个列表。我们可以使用main = do contents <- getContents let n = length contents n `seq` putStr contents 来强制左手表达式与右手表达式同时进行评估,而不是打印它,而是抛弃实际值:

length

与此同时,这仍然有点难看,因为我们只是使用deepseq来遍历列表,而不是因为我们真正关心它。我们真正想要的是一个只是遍历列表足以评估它的函数,而不做任何其他事情。令人高兴的是,这正是import Control.DeepSeq import System.IO main = do contents <- getContents contents `deepseq` putStr contents 所做的(对于许多数据结构,而不仅仅是列表):

{{1}}

答案 1 :(得分:6)

这是一个懒惰的I / O问题。一个简单的解决方案是使用严格的I / O,例如via ByteStrings:

import qualified Data.ByteString as S

main :: IO ()
main = S.getContents >>= S.putStr

答案 2 :(得分:4)

您可以使用strict(link)中的替换功能:

import qualified System.IO.Strict as S

main = do
  contents <- S.getContents
  putStrLn contents

请注意,对于阅读,不需要设置缓冲。缓冲真的只对写入文件有帮助。有关详细信息,请参阅此答案(link)

System.IO.Strict中严格版本hGetContents的定义非常简单:

hGetContents    :: IO.Handle -> IO.IO String
hGetContents h  = IO.hGetContents h >>= \s -> length s `seq` return s

即,它通过在length的标准/惰性版本返回的字符串上调用hGetContents来强制所有内容读入内存。