Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 是什么让迭代者值得这么复杂?_Haskell - Fatal编程技术网

Haskell 是什么让迭代者值得这么复杂?

Haskell 是什么让迭代者值得这么复杂?,haskell,Haskell,首先,我非常了解迭代者的工作原理,因此我可能可以编写一个过于简单且有缺陷的实现,而无需参考任何现有的实现 我真正想知道的是,为什么人们觉得它们如此迷人,或者在什么情况下它们的好处证明了它们的复杂性。将它们与懒惰的I/O进行比较有一个非常明显的好处,但在我看来,这非常像一个稻草人。首先,我对懒惰的I/O从来没有感到舒服过,除了偶尔使用hGetContents或readFile之外,我都会避免使用它,大多数情况下都是在非常简单的程序中 在现实场景中,我通常使用传统的I/O接口,这些接口具有适合于任务

首先,我非常了解迭代者的工作原理,因此我可能可以编写一个过于简单且有缺陷的实现,而无需参考任何现有的实现

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

在现实场景中,我通常使用传统的I/O接口,这些接口具有适合于任务的控制抽象。在这种情况下,我看不到迭代者的好处,也看不到迭代者对于什么任务是合适的控制抽象。大多数时候,它们看起来更像是不必要的复杂性,甚至是适得其反的控制反转

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

我只是患了“非发明于此”综合症,还是我的不安是有根据的?或者,这完全是另一回事

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

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

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

迭代者走第三条路:

  • 高级抽象,如惰性IO
  • 资源的显式词汇范围,如严格IO
当您有复杂的IO处理任务,但资源使用限制非常严格时,这是合理的。web服务器就是一个例子


事实上,它是围绕epoll之上的iteratee IO构建的。

至于为什么人们会觉得它们如此迷人,我认为是因为它们是如此简单的想法。Haskell cafe上关于迭代对象的指称语义的讨论已经演变成一种共识,即它们太简单了,几乎不值得描述。我从那条线中看到了“只不过是一个带有暂停按钮的华丽的左折叠”这句话。喜欢Haskell的人往往喜欢简单、优雅的结构,因此iteratee的想法可能非常吸引人

对我来说,iteratees的主要好处是

  • 可组合性。不仅可以组合迭代器,还可以组合枚举数。这是非常强大的
  • 安全的资源使用。资源(主要是内存和句柄)无法脱离其本地范围。与严格的I/O相比,不清理更容易产生空间泄漏
  • 效率高。迭代对象可以是高效的;与懒惰I/O和严格I/O竞争,或优于两者
  • 我发现迭代器在处理来自多个源的单个逻辑数据时提供了最大的好处。此时,可组合性最为有用,而使用严格I/O的资源管理最为烦人(例如嵌套
    alloca
    s或
    括号
    s)

    例如,在正在工作的音频编辑器中,声音数据的单个逻辑块是多个音频文件中的一组偏移量。我可以通过这样做(从内存中,但我认为这是正确的)来处理单个声音块:

    在我看来,这似乎清晰、简洁、优雅,比同等的严格I/O更为重要。迭代器也足够强大,可以整合我想要做的任何处理,包括编写输出,所以我觉得这非常好。如果我使用惰性I/O,我可以得到同样优雅的东西,但是额外的注意确保资源被消耗和GC'd将超过IMO的优势

    我还喜欢您需要显式地在iteratees中保留数据,这避免了臭名昭著的
    mean-xs=sum-xs/length-xs
    空间泄漏


    当然,我并不是什么都使用迭代器。作为一种替代方法,我非常喜欢使用*
    习惯用法,但是当您有多个需要嵌套的资源时,这些资源会变得非常复杂。

    本质上,这是关于以函数式的方式正确有效地执行IO。就这些,真的

    使用带有严格IO的准命令式样式,正确和高效是非常容易的。函数式风格在惰性IO中很容易实现,但从技术上讲,它是欺骗(在后台使用
    unsafeInterleaveIO
    ),并且可能在资源管理和效率方面存在问题

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

    但当涉及到IO时,这就分崩离析了。假设您的初始数据是一个文件句柄,“递归扩展”步骤是从中读取一行,并且您不能一次将整个文件读入内存。这迫使在读取下一行之前对每一行执行整个读取-转换-重新组合过程,因此,它们不是干净的“展开、映射、折叠”结构,而是使用严格的IO将它们混合到显式递归的一元函数中

    迭代者提供了一种替代结构来解决相同的问题。提取“转换和重新组合”步骤,并将其转换为表示当前计算状态的数据结构,而不是函数。“递归扩展”步骤i
    enumSound :: MonadIO m => Sound -> Enumerator s m a
    enumSound snd = foldr (>=>) enumEof . map enumFile $ sndFiles snd