非惰性分支的GHC

13

我听说有一种 GHC 的分支可以默认编译成严格模式,但可以通过注释启用惰性模式。(如果我没记错的话,他说一个金融公司开发了这个分支,并将其用于生产代码中。)这是真的吗?我找不到它。

此人还建议认为严格求值比惰性求值更实用(默认情况下),而这种观点越来越被接受。

我在 Haskell 邮件列表中没有确认这一点,但可能是因为那里的人们并不是那么注重实践?

我在严格 Haskell 中找到的都是明确的东西,比如 $!rnf。虽然我觉得惰性求值非常优雅,但我想在 Haskell 中开发一个程序,避免空间泄漏并获得可预测的性能。

免责声明:我并不是在为严格性辩护,我只是想看看严格的 Haskell 或类似的东西。


1
通过(尽管愚蠢的表达方式)粗鲁的惊喜,我指的是空间泄漏和运行时的不可预测性。实际上,我还没有遇到过这些问题,但我认为这些问题是真实存在的,对吧?(我目前正在评估要使用哪种语言。) - chs
2
我认为这可能被称为“Mu”?Lennart Augustsson可能在某个地方提到过它。 - jtobin
2
瑞士信贷银行确实建立了一个基于Haskell的金融建模系统,名为“天堂项目”。我相信奥古斯特森教授可能曾参与其中。我还相信,包括奥古斯特森教授在内的许多团队成员随后转移到渣打银行... - circular-ruin
1
太棒了,jtobin和circular-ruin提供的信息非常有用!非常感谢,我正在调查。 - chs
1
在2002年,Jan-Willem Maessen发表了一篇关于“Eager Haskell”的论文(现在已经失效)。更多关于“Eager Haskell”的内容可以在这里找到:http://csg.csail.mit.edu/pubs/haskell.html。看起来它已经沉寂了很久了。 - David Tonhofer
显示剩余4条评论
5个回答

12

你正在寻找Disciple

在Haskell中,有两种需要区分的懒惰性。一种是懒惰I / O,这是一种可耻的东西,并且可以通过迭代器库(包括我的pipes库)来解决。另一种是纯计算中的懒惰性,这仍然有争议,但我将尝试总结懒惰性的主要优点,因为您已经熟悉缺点:

懒惰性更有效率

一个简单的例子是:

any = foldr (||) False

any用于判断列表中是否有任何一个值为True。它仅在第一个True元素之前对元素进行计算,因此列表长度很长不会有影响。

惰性计算只计算必要的部分,这意味着如果将两个惰性计算链接在一起,实际上可以提高结果计算的时间复杂度。这篇 Stack Overflow 评论 给出了另一个很好的例子。

实际上这也是解释为什么迭代器库非常节约资源的原因。它们只做必要的工作来生成结果,这导致内存和磁盘使用非常高效,同时语义也非常易于使用。

惰性计算本质上更具可组合性

已经在严格和函数式语言中编程的人都知道这一点,但我在开发pipes库时无意中证明了这一点。在该库中,惰性版本是唯一允许 Category 实例的版本。Pipes 在任何单子上都能工作,包括纯的 Identity 单子,因此我的证明也适用于纯代码。

这正是我相信惰性计算在编程中真正的未来的原因,但我仍然认为,是否正确地实现了 Haskell 的惰性计算仍然是一个开放的问题。


一个非常好地涉及这个主题的视频讲座可以免费在此处获得(http://videoag.fsmpi.rwth-aachen.de/?view=player&videoid=2034#content)。 (注意:该视频是英文的,我与Aachen大学或该视频没有任何关联) - dflemstr
2
我认为懒惰的主要优点是它提供了灵活性和关注点分离。在语言中作为默认选项也是必要的,这样才能被视为声明式语言。在理想的 Haskell 中,其评估模型只是实现细节。 - jberryman
@jberryman 对的,我想说的是,“灵活性”和“关注点分离”的这种直觉可以通过仅仅说只有惰性形成一个范畴来变得严谨。 - Gabriella Gonzalez
"有懒惰的I/O,这是一种可恶的东西。" 啊。我对Haskell还很陌生,但是阅读关于懒惰I/O的内容让我想到了一个由管道连接的流处理代理网络的图像,在幕后构建,并在需要输出时以其所有(非确定性)荣耀开始运行。不确定实际操作是否是这样。 - David Tonhofer

5
听起来你听说过 Robert Ennals 基于 GHC 的“spec_eval”分支,他在他的 博士论文中介绍了关于 speculative evaluation 技术。这个分支中使用了 speculative evaluation 技术。因为 Haskell 是非严格语言,而不是显式懒惰语言,所以 spec_eval 在实际运行时变得更加严格,速度更快,但需要对 GHC 进行大量修改,因此从未合并到主干。
其实这个问题之前已经在 Stack Overflow 上被讨论过了。

3

有些人已经说过为什么你不应该回避Haskell的惰性,但我感觉原问题仍未得到解答。

Haskell中的函数应用是非严格的;也就是说,只有在需要时才会评估函数参数。

~ Haskell Report 2010 > Predefined Types and Classes # Strict Evaluation

然而,这有点误导人。实现 可以 在所需前对函数参数进行评估,但只有在一定程度上。您必须保留非严格的语义:因此,如果一个参数的表达式结果为无限循环,并且该参数未被使用,则具有该参数的函数调用必须不会进入无限循环。

因此,您可以以非完全“惰性”的方式实现Haskell,但它仍不能是“严格”的。这乍一看似乎是矛盾的,但事实并非如此。您可能想查看几个相关主题:

  • 热心的 Haskell,一个使用急切求值的 Haskell 编程语言实现。我相信这就是你想要的(尽管它不是 GHC 的一个分支)。
  • 推测执行和推测并行性(例如,请参阅speculation包)。
  • 乐观求值,SPJ 关于使用严格优化加速 GHC 的论文。

3
我听说有一个 GHC 分支,默认编译成严格代码,但可以通过注释启用惰性计算。您可以尝试使用 GHC 8.0.2、8.2.2 或 8.4.1,也就是最近的三个版本之一。它们有一个“{-# LANGUAGE Strict #-}”指令,可用于数值代码等。
(金融公司开发了这个分支并将其用于生产代码。)这是真的吗?我找不到它。
渣打确实开发了自己的 Haskell 编译器。我不认为他们会向公众提供它。我不确定它是否默认为严格模式。
此人还建议认为严格评估比惰性评估更实用。
这段话缺乏意义,也没有证据支持。事实上,llvm-hs-pure包选择使用严格版本的状态单子而不是惰性版本引入了一个bug。此外,像parallel-io包这样的东西也无法工作。

我想在Haskell中开发一个程序,希望避免空间泄漏,并且希望具有可预测的性能。

在过去两年中,我没有因惰性而造成的空间泄漏问题。我建议您使用基准测试和分析您的应用程序。编写具有可预测性能的Haskell要比添加严格的注释并希望程序仍然能够编译要容易得多。了解您的程序、分析和学习函数数据结构将为您服务更好,而不是盲目地添加编译器pragma以提高程序的性能。

2
如果我理解正确的话,严格的Haskell可能没有我们所知道的单子I/O。在Haskell中的想法是所有的Haskell代码都是纯的(这包括 IO 操作,类似于State Monad),并且 "main" 会给运行时一个类型为 IO() 的值,然后重复强制执行序列化操作 >>=。
作为Tekmo帖子的反面观点,你可以查看Robert Harper的博客及相关内容。 * http://existentialtype.wordpress.com/2011/04/24/the-real-point-of-laziness/ 两者都可以实现。
根据我的经验,懒惰初始很难掌握,但你习惯了就好了。
关于推广懒惰的经典文章是Hughes的"Why Functional Programming Matters",你应该能轻松找到它。

6
单子化的IO操作不依赖于惰性求值。 - is7s
1
你不需要强制使用 >>=getLine >>= putStrLn 是一个完全自包含的表达式,产生一个执行它所说的操作的动作,你可以在实际执行 IO 动作之前完全评估它。 - Asherah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接