我想等到用户输入终止于EOF然后全部输出。这不是getContents
应该做的吗?每次用户点击进入时,以下代码输出,我做错了什么?
import System.IO
main = do
hSetBuffering stdin NoBuffering
contents <- getContents
putStrLn contents
答案 0 :(得分:9)
根本问题是getContents
是 Lazy IO 的实例。这意味着getContents
产生的thunk可以像普通的Haskell值一样进行评估,只有在强制的情况下执行相关的IO 。
contents
是putStr
尝试打印的惰性列表,它强制列表并使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
来强制所有内容读入内存。