您的困惑似乎源于您在imperative 上下文中思考的事实。 call-by-need 与 call-by-value 的讨论主要涉及声明性和函数式语言以及 lambda 演算。
您可以在这篇关于 evaluation strategies 的文章中看到 call-by-name 和 call-by-need 都被视为 惰性评估 策略。惰性求值是指将表达式作为参数传递给函数时,在进入函数体之前不求值,而仅在函数内部第一次访问/读取时才求值。如果此类表达式的结果从未在内部使用过,则永远不会对其求值。
例如,? : 运算符在 Java 中是惰性的,如下代码所示:
String test(Object obj)
{
return 1 == 2 ? obj.toString() : "Hello World";
}
test(null); // this won't throw a NullPointerException
按需调用是大多数具有纯子集的函数式语言的基本特征。在purely functional language 中,每个函数都必须是referentially transparent,即它们不能有side-effects。这样的pure functions 具有这样的属性,即对于某些给定的输入,无论调用多少次,它们总是返回相同的输出,并且它们永远不会改变“世界状态”中的任何内容。它们的行为就像写在纸上的数学函数。
正如您已经意识到的那样,call-by-need 策略在调用非纯函数时是不可行的,因为您很可能对连续调用的副作用感兴趣。另一方面,当在纯函数式语言中使用时,它成为性能的基本特征(参见下面的第二个示例)。另外,请参阅这些关于 Graph Reduction 和 Memoization 概念的 wiki 页面。
现实世界的例子
首先。一个使用图缩减的常用系统示例是Apache Ant。 Ant 不会对目标进行两次评估。这种设计便于草拟声明性构建计划。
第二。如果你想看一个很好的记忆演示,把这个Haskell代码输入GHC解释器,看看会发生什么:
Prelude> let fibs = 0:1:(zipWith (+) fibs (tail fibs))
-- This defines the Fibonacci sequence.
Prelude> fibs !! 200000
-- Prints the 200,000th Fibonacci number,
-- takes several seconds to calculate.
Prelude> fibs !! 200000
-- Prints the same number as before,
-- but this time it returns immediately.
注意。您可能还听说过 call-by-value 评估策略。与 call-by-name 和 call-by-need 相比,call-by-value 是一种严格的评估策略。这就像 call-by-name,因为多次调用会导致多次评估。对于习惯于 C# 或 Java 等命令式语言的程序员来说,这是最常见的范例。