繁体   English   中英

是什么让Iteratees值得复杂?

[英]What makes Iteratees worth the complexity?

首先,我理解iteratees的怎么样 ,不够好,我大概可以写一个简单的和越野车实现无再参考任何现有的。

我真正想知道的是为什么人们似乎发现它们如此迷人,或者在什么情况下它们的好处证明了它们的复杂性。 将它们与懒惰的I / O进行比较有一个非常明显的好处,但这对我来说似乎非常像一个稻草人。 我从来没有对懒惰的I / O感到满意,除了偶尔的hGetContentsreadFile之外我都避免使用它,主要是在非常简单的程序中。

在实际场景中,我通常使用传统的I / O接口和适合任务的控件抽象。 在那种情况下,我只是没有看到迭代者的好处,或者他们是一个适当的控制抽象的任务。 大多数时候,他们看起来更像是不必要的复杂性,甚至是适得其反的控制倒置。

我已经阅读了很多关于它们的文章和使用它们的资料,但还没有找到一个令人信服的例子,实际上让我想到了“哦,是的,我也曾在那里使用它们“。 也许我只是没有读过正确的。 或许还有一个尚未设计的界面,比我见过的任何一个都简单,这会使他们感觉不像瑞士军用链锯。

我只是患有非发明的综合症或者我的不安是否有充分根据? 或者它可能完全不同于其他东西?

至于为什么人们觉得它们如此迷人,我认为因为它们是如此简单的想法。 最近关于Haskell-cafe关于迭代的指称语义的讨论转变为一种共识,即它们非常简单,几乎不值得描述。 短语“只有一个带有暂停按钮的美化左手折叠”从那个帖子向我伸出。 喜欢Haskell的人倾向于喜欢简单,优雅的结构,所以迭代的想法可能非常吸引人。

对我来说,迭代的主要好处是

  1. 组合性。 不仅可以组成迭代,而且枚举器也可以。 这非常强大。
  2. 安全的资源使用。 资源(主要是内存和句柄)无法逃避其本地范围。 与严格的I / O相比,通过不清理更容易造成空间泄漏。
  3. 高效。 Iteratees可以高效; 与懒惰I / O和严格I / O竞争或更好。

我发现迭代器在处理来自多个源的单个逻辑数据时提供了最大的好处。 这是可组合性最有用的,并且具有严格I / O的资源管理最烦人(例如嵌套allocabracket )。

例如,在正在进行中的音频编辑器中,单个逻辑声音数据块是一组偏移到多个音频文件中的。 我可以通过做这样的事情处理那一小块声音(从记忆中,但我认为这是正确的):

enumSound :: MonadIO m => Sound -> Enumerator s m a
enumSound snd = foldr (>=>) enumEof . map enumFile $ sndFiles snd

这对我来说似乎清晰,简洁,优雅,远远超过了同等严格的I / O. Iteratees也足够强大,可以包含我想要做的任何处理,包括写输出,所以我发现这非常好。 如果我使用懒惰的I / OI可以获得优雅的东西,但要特别注意确保资源被消耗并且GC会超过IMO的优势。

我也喜欢你需要在迭代中明确地保留数据,这避免了臭名昭着的mean xs = sum xs / length xs space leak。

当然,我不会将迭代用于一切。 作为一种替代方案,我非常喜欢with* idiom,但是当你有多个需要嵌套的资源时,它会很快变得复杂。

从本质上讲,它是关于正确有效地执行功能样式的 IO。 这就是全部,真的。

使用具有严格IO的准命令式风格,可以轻松实现正确和高效。 懒惰IO的功能风格很容易,但它在技术上是作弊(在引擎盖下使用unsafeInterleaveIO ),并且可能存在资源管理和效率方面的问题。

非常非常通用的术语,许多纯函数代码遵循一种获取数据的模式,递归地将其扩展为更小的片段,以某种方式转换片段,然后将其重新组合成最终结果。 该结构可以是隐式的(在程序的调用图中)或遍历的显式数据结构。

但是当IO涉及时,这就会崩溃。 假设您的初始数据是文件句柄,“递归扩展”步骤正在从中读取一行,并且您无法立即将整个文件读入内存。 这会强制在读取下一行之前对每一行执行整个读取 - 转换 - 重组过程,因此,不使用干净的“展开,映射,折叠”结构,而是使用严格的IO将它们混合成明确的递归monadic函数。

迭代器提供了一种替代结构来解决同样的问题。 提取“变换和重新组合”步骤,而不是作为函数 ,将其改变为表示计算的当前状态的数据结构 “递归扩展”步骤负责获得数据并将其提供给(否则是被动的)迭代。

这提供了什么好处? 除其他事项外:

  • 因为iteratee是执行计算的单个步骤的被动对象,所以它们可以以不同的方式轻松组合 - 例如,交错两个迭代而不是顺序运行它们。
  • 迭代器和枚举器之间的接口是纯粹的,只是正在处理的值流,因此纯函数可以在它们之间自由拼接。
  • 数据源和计算忽略了彼此的内部工作,将输入和资源管理与处理和输出分离。

最终结果是程序可以具有更接近纯功能版本的高级结构,具有许多与组合性相同的好处,同时具有与更强制性的严格IO版本相当的效率。

至于“值得复杂”吗? 嗯,这就是事情 - 他们真的不是那么复杂,只是有点新奇和陌生。 这个想法一直在流动,几年,几年? 当人们在较大的项目中使用基于iteratee的IO(例如,使用Snap之类的东西)以及更多的示例/教程出现时,给它一些时间来摆脱困境。 事后看来,当前的实现可能在边缘看起来非常粗糙。


有点相关:您可能想阅读有关功能样式IO的讨论 Iteratees没有被提及太多,但核心问题非常相似。 特别是这个解决方案 ,它非常优雅,甚至比抽象增量IO中的迭代更进一步

在什么情况下他们的利益证明了他们的复杂性

每种语言都有严格(经典)的IO,其中所有资源都由用户管理。 Haskell还提供无处不在的惰性IO,其中所有资源管理都委托给系统。

但是,这可能会产生问题,因为资源范围取决于运行时需求属性。

Iteratees第三种方式:

  • 高级抽象,如懒惰的IO。
  • 显式的,词汇式的资源范围,如严格的IO。

当您具有复杂的IO处理任务时,这是合理的,但资源使用的界限非常紧张。 一个例子是Web服务器。

实际上, Snap是围绕epoll上的iteratee IO构建的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM