是什么让Iteratees值得复杂?

mok*_*kus 24 haskell

首先,我明白了如何 iteratees的,不够好,我大概可以写一个简单的和越野车实现无再参考任何现有的.

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

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

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

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

Joh*_*n L 16

至于为什么人们觉得它们如此迷人,我认为因为它们是如此简单的想法.在最近的讨论有关iteratees一个指称语义上的Haskell咖啡厅移交到他们是如此简单,他们是勉强为之描述一个共识.短语"只有一个带有暂停按钮的美化左手折叠"从那个帖子向我伸出.喜欢Haskell的人倾向于喜欢简单,优雅的结构,所以迭代的想法可能非常吸引人.

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

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

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

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

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

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

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

当然,我不会将迭代用于一切.作为一种替代方案,我非常喜欢这个with*习惯用法,但是当你有多个需要嵌套的资源时,它会非常快速地复杂化.


C. *_*ann 8

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

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

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

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

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

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

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

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

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


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