懒惰和I / O如何在Haskell中协同工作?

时间:2010-05-06 00:47:39

标签: haskell lazy-evaluation

我正在努力深入了解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兼容?无论我是否访问pixels image,还是运行时都会调用readFile,如果我从未引用它,那么运行时是否会重新评估thunk?

如果确实懒惰地读取了图像,那么I / O操作是否可能无序发生?例如,如果在调用image后立即删除文件怎么办?现在,putStrLn调用在尝试读取时将找不到任何内容。

2 个答案:

答案 0 :(得分:17)

  

懒惰如何与I / O兼容?

简短回答:不是。


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

  • 滚动您自己的显式延迟加载例程,就像您在任何严格的语言中一样。似乎令人讨厌,被授予,但另一方面,Haskell制作了一个严格的,严格的命令式语言。如果您想尝试新的有趣的事情,请尝试查看Iteratees

  • 作弊骗子作弊。函数such as hGetContents将为您执行惰性,按需I / O,无需提问。有什么收获?它(技术上)打破了参考透明度。纯代码可以间接导致副作用,如果您的代码真的错综复杂,可能会发生涉及副作用排序的有趣事情。 hGetContents和朋友已经实施了using unsafeInterleaveIO,这正是它在锡上所说的。与使用unsafePerformIO相比,它在你的脸上不太可能爆炸,但请注意自己警告。

答案 1 :(得分:9)

懒惰的I / O打破了Haskell的纯度。 readFile的结果确实是根据需要懒散地产生的。 I / O操作发生的顺序不固定,所以是的,它们可能“乱序”。在拉动像素之前删除文件的问题是真实的。简而言之,懒惰的I / O非常方便,但它是一种边缘非常锋利的工具。

关于真实世界哈斯克尔的书有一个lengthy treatment of lazy I/O,并且讨论了一些陷阱。