为什么惰性求值很有用?

46 浏览
0 Comments

为什么惰性求值很有用?

我一直在想为什么惰性求值有用。至今为止,没有人以一种让我理解的方式解释过它; 大多数时候都是“相信我”的问题。

注意:我不是指记忆化。

admin 更改状态以发布 2023年5月23日
0
0 Comments

我发现惰性求值在很多方面都很有用。

首先,所有现有的惰性语言都是纯的,因为在惰性语言中推理副作用非常困难。

纯语言让你使用等式推理来推理函数定义。

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法则,而与之相比,工作流会带来大量的严格负担。)

0
0 Comments

惰性求值主要是因为它可以更有效率--如果值不需要被使用,就不需要计算。例如,我可能会将三个值传递给一个函数,但是根据条件表达式的顺序,只有一个子集实际上会被使用。在像C这样的语言中,所有三个值都将被计算; 但在Haskell中,只计算必要的值。

它还允许一些很酷的东西,比如无限列表。在像C这样的语言中,我不能拥有一个无限的列表,但在Haskell中,这不是问题。在某些数学领域中经常使用无限列表,因此具有操作它们的能力是有用的。

0