Haskell getContents等待EOF

Kir*_*kov 2 io monads haskell

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

import System.IO

main = do
  hSetBuffering stdin NoBuffering
  contents <- getContents
  putStrLn contents
Run Code Online (Sandbox Code Playgroud)

Tik*_*vis 10

基本问题是Lazy IOgetContents的实例.这意味着产生一个thunk,可以像普通的Haskell值一样进行评估,并且仅在强制执行时执行相关的IO .getContents

contents是一个putStr尝试打印的惰性列表,它强制列表并使原因getContents尽可能多地读取.putStr然后打印所有强制的内容,并继续尝试强制列表的其余部分直到它命中[].因为getContents可以读取越来越多的流 - 确切的行为取决于缓冲 - putStr可以立即打印越来越多,给你你看到的行为.

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

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

main = do
  contents <- getContents
  let n = length contents
  print n
  putStr contents
Run Code Online (Sandbox Code Playgroud)

但如果你移动print nputStr,你会回到原来的行为,因为n直到打印输入才会被强制(尽管之前n仍然定义了putStr):

main = do
  contents <- getContents
  let n = length contents
  putStr contents
  print n
Run Code Online (Sandbox Code Playgroud)

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

这也为我们提供了一个如何解决问题的提示:contents在打印之前我们需要一些强制方法.正如我们所看到的,我们可以这样做,length因为length在计算结果之前需要遍历整个列表.我们可以使用seq强制左手表达式与右手表达式同时进行评估而不是打印它,而是抛弃实际值:

main = do
  contents <- getContents
  let n = length contents
  n `seq` putStr contents
Run Code Online (Sandbox Code Playgroud)

与此同时,这仍然有点难看,因为我们length只是用来遍历列表,而不是因为我们真正关心它.我们真正想要的是一个只需遍历列表就可以对其进行评估的函数,而无需做任何其他事情.令人高兴的是,这正是这样deepseq做的(对于许多数据结构,而不仅仅是列表):

import Control.DeepSeq
import System.IO

main = do
  contents <- getContents
  contents `deepseq` putStr contents
Run Code Online (Sandbox Code Playgroud)


Mic*_*man 6

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

import qualified Data.ByteString as S

main :: IO ()
main = S.getContents >>= S.putStr
Run Code Online (Sandbox Code Playgroud)