为什么惰性求值很有用?
为什么惰性求值很有用?
我一直在想为什么惰性求值有用。至今为止,没有人以一种让我理解的方式解释过它; 大多数时候都是“相信我”的问题。
注意:我不是指记忆化。
我发现惰性求值在很多方面都很有用。
首先,所有现有的惰性语言都是纯的,因为在惰性语言中推理副作用非常困难。
纯语言让你使用等式推理来推理函数定义。
foo x = x + 3
不幸的是,在非惰性环境中,与在惰性环境中相比,更多的语句会失败,因此这在像ML这样的语言中不太有用。但在惰性语言中,你可以安全地推理相等性。
其次,在Haskell这样的惰性语言中,许多像ML中的“值限制”之类的东西都是不需要的。这导致语法的大量简化。像ML这样的语言需要使用关键字像var或fun。在Haskell中,这些东西都会折叠成一个概念。
第三,惰性让你编写可以分开理解的非常函数化的代码。在Haskell中,常见的写法是将函数体写成:
foo x y = if condition1 then some (complicated set of combinators) (involving bigscaryexpression) else if condition2 then bigscaryexpression else Nothing where some x y = ... bigscaryexpression = ... condition1 = ... condition2 = ...
这让你可以“自上而下”地理解一个函数的主体。像ML这样的语言强制你使用一个被严格求值的let
。因此,你不敢将let子句从函数的主体中“提升”,因为如果它很昂贵(或者有副作用),你不希望它总是被求值。Haskell可以明确地将细节推迟到where子句中,因为它知道该子句的内容只会在需要时被求值。
在实践中,我们倾向于使用守卫并进一步简化为:
foo x y | condition1 = some (complicated set of combinators) (involving bigscaryexpression) | condition2 = bigscaryexpression | otherwise = Nothing where some x y = ... bigscaryexpression = ... condition1 = ... condition2 = ...
第四,惰性有时提供了某些算法更优雅的表达方式。在Haskell中,惰性“快速排序”是一个一行代码的东西,而且它有一个好处,就是如果你只查看前几个项目,你只需要按比选择这些项目的成本计算成本。没有什么能阻止你严格地这样做,但你可能需要每次重新编写算法以达到相同的渐近性能。
第五,惰性计算允许你在语言中定义新的控制结构。在严格语言中,你无法编写像“if..then..else..”这样的结构。如果你尝试定义一个函数,如下所示:
if' True x y = x if' False x y = y
在严格语言中,无论条件值如何,两个分支都将被计算。如果考虑循环,情况会更糟。所有严格的解决方案都需要语言提供某种引用或显式lambda构造。
最后,同样,在这方面,处理副作用的一些最好的类型系统机制,例如monad,真正有效地只能在惰性环境中表达。这可以通过比较F#的工作流和Haskell Monads的复杂性来证明。(你可以在严格语言中定义一个monad,但不幸的是由于缺乏惰性,你经常会违反一个或两个monad法则,而与之相比,工作流会带来大量的严格负担。)