为了账号安全,请及时绑定邮箱和手机立即绑定

惰性I / O有什么不好?

惰性I / O有什么不好?

12345678_0001 2019-12-17 15:16:58
我通常听说生产代码应避免使用惰性I / O。我的问题是,为什么?在玩弄之外使用惰性I / O可以吗?是什么使替代方法(例如枚举数)更好?
查看完整描述

3 回答

?
月关宝盒

TA贡献1772条经验 获得超5个赞

惰性IO的问题在于,释放所获取的任何资源在某种程度上都是不可预测的,因为这取决于程序如何使用数据-其“需求模式”。一旦您的程序删除了对该资源的最后一个引用,GC最终将运行并释放该资源。

惰性流是一种非常方便的编程风格。这就是为什么Shell管道如此有趣和流行的原因。

但是,如果资源有限(例如在高性能方案中,或预期扩展到机器极限的生产环境中),则依靠GC清理可能是不足的保证。

有时您必须急于释放资源,以提高可伸缩性。

那么,什么是懒惰IO的替代方案,而不是放弃增量处理(反过来又会消耗太多资源)呢?好吧,我们有foldl基于处理的(又称为迭代器或枚举器),由Oleg Kiselyov在2000年代后期引入,并且此后被许多基于网络的项目所普及。

而不是像惰性流一样处理数据或成批处理,我们取而代之的是对基于块的严格处理进行抽象,并确保一旦读取了最后一个块,就最终确定了资源。这是基于Iteratee的编程的本质,并且提供了非常好的资源约束。

基于iteratee的IO的缺点是它的编程模型有些笨拙(大致类似于基于事件的编程,而不是基于线程的漂亮控制)。在任何编程语言中,它绝对是一种先进的技术。对于绝大多数编程问题,惰性IO完全令人满意。但是,如果您要打开许多文件,或在许多套接字上交谈,或以其他方式同时使用许多资源,则iteratee(或枚举器)方法可能很有意义。


查看完整回答
反对 回复 2019-12-17
?
慕森卡

TA贡献1806条经验 获得超8个赞

Dons提供了一个很好的答案,但是他遗漏了(对我而言)迭代对象最引人注目的功能之一:由于必须明确保留旧数据,它们使推理空间管理变得更加容易。考虑:


average :: [Float] -> Float

average xs = sum xs / length xs

这是众所周知的空间泄漏,因为xs必须将整个列表保留在内存中才能计算sum和length。通过创建折页可以使有效的消费者受益:


average2 :: [Float] -> Float

average2 xs = uncurry (/) <$> foldl (\(sumT, n) x -> (sumT+x, n+1)) (0,0) xs

-- N.B. this will build up thunks as written, use a strict pair and foldl'

但是必须为每个流处理器执行此操作有点不方便。有一些概括(Conal Elliott-Beautiful Fold Zipping),但似乎没有流行。但是,迭代器可以使您获得类似的表达水平。


aveIter = uncurry (/) <$> I.zip I.sum I.length

这并不像折叠一样有效,因为该列表仍然需要重复多次,但是它是按块收集的,因此可以有效地对旧数据进行垃圾收集。为了破坏该属性,必须显式保留整个输入,例如使用stream2list:


badAveIter = (\xs -> sum xs / length xs) <$> I.stream2list

作为编程模型的迭代状态尚在开发中,但是比一年前要好得多。我们所学的组合子是有用的(例如zip,breakE,enumWith),并且要少一些,其结果是,内置iteratees和组合程序提供持续更表现力。


就是说,唐斯(Dons)是个高级技术,这是正确的。我当然不会将它们用于每个I / O问题。


查看完整回答
反对 回复 2019-12-17
?
阿晨1998

TA贡献2037条经验 获得超6个赞

最近在haskell-cafe上,Oleg Kiseljov指出unsafeInterleaveST(用于在ST monad中实现惰性IO)非常不安全-破坏了公式推理。他表明,它允许构造bad_ctx :: ((Bool,Bool) -> Bool) -> Bool 这样


> bad_ctx (\(x,y) -> x == y)

True

> bad_ctx (\(x,y) -> y == x)

False

即使==是可交换的。


惰性IO的另一个问题:实际的IO操作可以推迟到为时已晚,例如在关闭文件之后。从Haskell Wiki引用-惰性IO问题:


例如,一个常见的初学者错误是在完成读取文件之前关闭文件:


wrong = do

    fileData <- withFile "test.txt" ReadMode hGetContents

    putStr fileData

问题是withFile在强制fileData之前关闭了句柄。正确的方法是将所有代码传递给withFile:


right = withFile "test.txt" ReadMode $ \handle -> do

    fileData <- hGetContents handle

    putStr fileData

在这里,数据在withFile完成之前被消耗掉。


这通常是出乎意料的,并且容易出错。


查看完整回答
反对 回复 2019-12-17
  • 3 回答
  • 0 关注
  • 340 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信