作为内置语言特性,懒惰有什么实际用处?

11

很明显,一个想要懒惰计算的函数式编程语言需要是纯函数的。我正在探讨相反的问题:如果一种语言想要是纯函数的,那么懒计算是否有很大优势?Haskell 的一位设计者提出了一个观点,即它可以消除诱惑;但我正在权衡更具体的优势。

假设你想要进行函数式编程,有哪些使用情况下内置的懒计算使你能够更清晰、简单或简洁地表达呢?

简单地说:为什么懒计算非常重要,以至于你想要将其构建到语言中?

(我正在寻找更面向应用而不是演示的使用案例——我知道你可以通过过滤自然数的无限列表来生成一个质数的无限列表,但谁会写十遍“产生质数”的代码……)


这是一个有趣的话题,但你用了一种不太恰当的方式来提问,实际上是要求列举任何可能想象到懒惰求值有用的情况。如果你正在设计一种语言,最好专注于你试图做出的具体决策,并请求帮助权衡各种选择,以便你能做出明智的选择。 - Shog9
我不知道你是否能在这里得到好的答案,这个问题更适合编程语言设计者而不是程序员。这是一种关于计算机科学的网站更适合的问题。我推荐阅读维基百科上引用的文章(而不是维基百科本身),特别是“为什么函数式编程很重要”。 - Gilles 'SO- stop being evil'
rwallace:根据@Gilles的编辑,我进一步修订了问题的懒惰性作为内置功能的强调(使用示例来说明而不是以示例为目标),并重新开放,假设这是可以接受的。如果您不同意,请讨论。 - Shog9
没问题,谢谢,我同意这些编辑,它们澄清了问题。至于维基百科上引用的文章,它们确实包含了很多有用的信息,但如果它们包含了关于惰性作为语言特性在实际使用案例方面的讨论,我可能错过了。 - rwallace
3个回答

19

"Nothing is evaluated until it is needed at another place"被简化为一个隐喻,它不能涵盖惰性求值的所有方面(例如它没有提到严格现象)。

从理论角度来看,在设计纯语言时有三种方式(当然,如果它是基于某种λ演算而不是更奇特的求值模型),分别是严格、非严格和全面。

它们各自都有优点和缺点,因此您需要阅读相应的研究论文。

在这三种语言中,全面的语言是最纯的。在另外两种语言中,无终止状态可以看作是一种副作用,所以必须建立严格性和完整性分析器来保持实现的效率。这两个分析是不可判定的,因此分析器永远无法完全。

然而,总体语言最不具表现力:总体语言不可能是图灵完备的。获得足够的表现力的常见方法是具有内置的良好递归证明系统,构建它并不比构建非全面语言的分析器容易多少。

从实际角度来看,非严格语义使您更容易定义控制抽象,因为控制结构本质上是非严格的。在严格语言中,您仍然需要一些具有非严格语义的地方。例如,在严格语言中,“if”结构即使具有非严格语义。

因此,如果您的语言是严格的,则控制结构是一种特殊情况。相反,非严格语言可以统一为非严格 - 它没有对严格结构的固有需求。

至于“谁写了那十遍午餐前” - 使用Haskell进行项目的任何人都会这样做。我认为使用语言(在您的情况下是非严格语言)开发一个非玩具项目是了解其优缺点的最佳途径。

以下是通过非玩具示例说明惰性的一些通用用例:

  1. 当控制流难以预测时。考虑属性文法,如果没有惰性,你必须对属性执行拓扑排序以解决依赖关系。每次依赖图发生变化时重新排序代码是不切实际的。在Haskell中,您可以实现属性文法形式而无需显式排序,并且至少有两个实际的实现在Hackage上。属性文法在编译器构建中具有广泛应用。

  2. “生成和搜索”方法可解决许多优化问题。在严格语言中,您必须交替生成和搜索,而在Haskell中,您只需组合单独的生成和搜索函数,您的代码仍然在语法上模块化,但在运行时交错。考虑旅行商问题(TSP),当您生成所有可能的路径并使用分支限界算法搜索它们时。请注意,分支限界算法仅检查一些路径的前几个城市,仅生成必要部分的路线。 TSP即使在其最纯粹的形式中也有几个应用,例如规划、物流和微芯片制造。稍加修改后,它出现在许多领域的子问题中,例如DNA序列。

  3. 惰性代码具有非模块化的控制流,因此单个函数可以具有许多可能的控制流,具体取决于它执行的环境。这种现象可以看作是某种“控制流多态性”,因此惰性控制流抽象比其严格对应物更通用,并且高阶函数的标准库在惰性语言中更加有用。考虑Python生成器、循环和列表迭代器:在Haskell中,列表函数涵盖了所有三种用例,由于惰性,控制流适应不同的使用场景。它不仅限于列表-请考虑Data.Arrow和iteratees,State monad的惰性和严格版本等。还要注意,非模块化的控制流既是优点也是缺点,因为它使得性能推理更加困难。

  4. 惰性可能无限的数据结构超出了玩具示例的范畴。请参见Conal Elliott关于使用tries记忆化高阶函数的作品。无限数据结构出现为无限搜索空间(见2),无限循环和永不耗尽的Python生成器(见3)。


所提到的隐喻来源似乎是 Henderson 和 Morris 在 POPL'76 上发表的《一个懒惰求值器》。 - nponeccop

2

Mac OS X的Core Image是懒惰求值(lazy evaluation)的一个很好的实际示例。

基本上,Core Image允许您创建一个图像生成器和滤镜的有向无环图。直到过程的最后一步——物质化时才实际进行评估。当您请求材料化Core Image图时,最终图像帧通过图形变换向后传播,从而最小化需要评估的实际像素值的数量。


2
这一点在休斯经典著作《为何函数式编程很重要》中有详细的讨论。休斯认为惰性计算可以提高模块化能力,并举了许多易于理解的例子。

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