问题描述
我今天在想象下面的代码片段:
data Image = Image {name :: String,pixels :: String}
image :: String - > IO Image
image path =图像路径< $> readFile path
这里的吸引力是我可以简单地创建一个Image实例并传递它;如果我需要图像数据,它会被懒惰地读取 - 如果没有,读取文件的时间和内存成本将被避免:
main = do
image< - imagefile
putStrLn $ length $ pixels image
但是,它是如何实际工作的?懒惰是如何与IO兼容的?无论我是否访问像素图像
,是否会调用readFile,或者如果我从不引用它,运行时是否会将该thunk评估为未评估?
如果图像确实是懒懒地读取的,那么I / O操作可能不按顺序发生吗?例如,如果在调用 image
后立即删除文件?
简短回答:不是。
长答案: IO
操作是严格按照顺序排列的, 。当然,对结果所做的任何纯粹的计算都可能是懒惰的。例如,如果您读取文件,执行一些处理,然后打印出一些结果,则可能不会评估输出不需要的任何处理。然而,整个文件将被读取,甚至你永远不会使用的部分。如果你想懒惰的I / O,你大致有两种选择:
-
滚动你自己的显式延迟加载例程等等,就像你会用任何严格的语言一样。看起来很烦人,但是另一方面,Haskell制定了一个严格而严格的语言。如果您想尝试一些新鲜有趣的内容,请尝试查看。
-
作弊作弊。功能将为您执行懒惰的按需I / O,不会提出任何问题。有什么收获?它(技术上)违反了参考透明度。纯粹的代码可以间接导致副作用,如果代码真的很复杂,有趣的事情可能会发生,包括副作用的排序。
hGetContents
和朋友已实现,这正是它所说的。在使用unsafePerformIO
的时候,你的面孔很可能会炸毁你,但应该考虑到自己的警告。
I'm trying to get a deeper understanding of laziness in Haskell.
I was imagining the following snippet today:
data Image = Image { name :: String, pixels :: String }
image :: String -> IO Image
image path = Image path <$> readFile path
The appeal here is that I could simply create an Image instance and pass it around; if I need the image data it would be read lazily - if not, the time and memory cost of reading the file would be avoided:
main = do
image <- image "file"
putStrLn $ length $ pixels image
But is that how it actually works? How is laziness compatible with IO? Will readFile be called regardless of whether I access pixels image
or will the runtime leave that thunk unevaluated if I never refer to it?
If the image is indeed read lazily, then isn't it possible I/O actions could occur out of order? For example, what if immediately after calling image
I delete the file? Now the putStrLn call will find nothing when it tries to read.
Short answer: It isn't.
Long answer: IO
actions are strictly sequenced, for pretty much the reasons you're thinking of. Any pure computations done with the results can be lazy, of course; for instance if you read in a file, do some processing, and then print out some of the results, it's likely that any processing not needed by the output won't be evaluated. However, the entire file will be read, even parts you never use. If you want lazy I/O, you have roughly two options:
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 usingunsafeInterleaveIO
, which is... exactly what it says on the tin. It's nowhere near as likely to blow up in your face as usingunsafePerformIO
, but consider yourself warned.
这篇关于Haskell中的惰性和I / O如何协同工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!