10

我试图更深入地了解 Haskell 中的惰性。

我今天在想象以下片段:

data Image = Image { name :: String, pixels :: String }

image :: String -> IO Image
image path = Image path <$> readFile path

这里的吸引力在于我可以简单地创建一个 Image 实例并传递它;如果我需要图像数据,它将被懒惰地读取 - 如果不需要,则可以避免读取文件的时间和内存成本:

 main = do
   image <- image "file"
   putStrLn $ length $ pixels image

但它实际上是这样工作的吗?惰性如何与 IO 兼容?无论我是否访问 readFile 都会被调用,pixels image或者如果我从不引用它,运行时是否会留下未评估的 thunk?

如果图像确实是延迟读取的,那么 I/O 操作是否可能发生乱序?例如,如果在调用后立即image删除文件怎么办?现在 putStrLn 调用在尝试读取时将一无所获。

4

2 回答 2

17

惰性如何与 I/O 兼容?

简短的回答:不是。


长答案:IO动作是严格按顺序排列的,这几乎是您正在考虑的原因。当然,对结果进行的任何纯计算都可能是惰性的;例如,如果您读入一个文件,进行一些处理,然后打印出一些结果,则可能不会评估输出不需要的任何处理。但是,将读取整个文件,甚至是您从未使用过的部分。如果你想要惰性 I/O,你大致有两种选择:

  • Roll your own explicit lazy-loading routines and such, like you would in any strict language. Seems annoying, granted, but on the other hand Haskell makes a fine strict, imperative language. If you want to try something new and interesting, try looking at Iteratees.

  • Cheat like a cheating cheater. Functions such as hGetContents will do lazy, on-demand I/O for you, no questions asked. What's the catch? It (technically) breaks referential transparency. Pure code can indirectly cause side effects, and funny things can happen involving ordering of side effects if your code is really convoluted. hGetContents and friends are implemented using unsafeInterleaveIO, which is... exactly what it says on the tin. It's nowhere near as likely to blow up in your face as using unsafePerformIO, but consider yourself warned.

于 2010-05-06T01:24:39.080 回答
9

Lazy I/O breaks Haskell's purity. The results from readFile are indeed produced lazily, on demand. The order in which I/O actions occur is not fixed, so yes, they could occur "out of order". The problem of deleting the file before pulling the pixels is real. In short, lazy I/O is a great convenience, but it's a tool with very sharp edges.

The book on Real World Haskell has a lengthy treatment of lazy I/O and goes over some of the pitfalls.

于 2010-05-06T01:31:24.813 回答