是什么让Iteratees值得复杂?

时间:2010-09-24 15:58:24

标签: haskell

首先,我理解了迭代的如何,这足以让我可以编写一个简单的错误实现,而不会回顾任何现有的实现。

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

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

我读过很多关于它们的文章以及使用它们的消息来源,但还没有找到一个引人注目的例子,实际上让我想到了“哦,是的,我已经习惯了他们也在那里。“也许我只是没有读过正确的。或许还有一个尚未设计的界面,比我见过的任何一个都简单,这会使他们感觉不像瑞士军用电锯。

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

3 个答案:

答案 0 :(得分:16)

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

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

  1. 组合性。不仅可以组成迭代,而且枚举器也可以。这非常强大。
  2. 安全的资源使用情况。资源(主要是内存和句柄)无法逃避其本地范围。与严格的I / O相比,通过不清理更容易造成空间泄漏。
  3. 高效。 Iteratees可以高效;与懒惰I / O和严格I / O竞争或更好。
  4. 我发现迭代器在处理来自多个源的单个逻辑数据时提供了最大的好处。这是可组合性最有用的时候,严格I / O的资源管理最烦人(例如嵌套allocabracket s)。

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

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

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

    我也喜欢你需要在迭代中显式保留数据,这样可以避免臭名昭着的mean xs = sum xs / length xs空间泄漏。

    当然,我不会将迭代用于一切。作为替代方案,我非常喜欢with*成语,但是当您有多个需要嵌套的资源时,它们会非常快速地复杂化。

答案 1 :(得分:8)

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

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

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

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

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

这提供了哪些好处?除其他外:

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

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

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


有点相关:您可能想要阅读this discussion about functional-style IO。 Iteratees没有被提及太多,但核心问题非常相似。特别是this solution,它非常优雅,甚至比抽象增量IO中的迭代更进一步

答案 2 :(得分:4)

  

在什么情况下,他们的好处证明了他们的复杂性

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

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

Iteratees第三种方式:

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

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

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