我想知道什么是需要的电话。
虽然我在维基百科搜索并在此处找到了http://en.wikipedia.org/wiki/Evaluation_strategy, 但无法正确理解。 如果有人能用一个例子来解释并指出与按值调用的区别,那将是一个很大的帮助。
答案 0 :(得分:29)
假设我们有函数
square(x) = x * x
我们要评估square(1+2)
。
在按值调用中,我们执行
square(1+2)
square(3)
3*3
9
在按名称调用中,我们执行
square(1+2)
(1+2)*(1+2)
3*(1+2)
3*3
9
请注意,由于我们使用了两次参数,因此我们对其进行了两次评估。如果论证评估需要很长时间,那将是浪费。这就是需要修复的问题。
在按需拨打中,我们执行以下操作:
square(1+2)
let x = 1+2 in x*x
let x = 3 in x*x
3*3
9
在第2步中,我们不是复制参数(比如在call-by-name中),而是给它起一个名字。然后在第3步中,当我们发现需要 x
的值时,我们会评估x
的表达式。只有这样我们才能替代。
let
s shuffling来消除复制的可能性。记下正式规则有点复杂。
请注意,我们“需要”基本操作的参数值,如+
和*
,但对于其他函数,我们采用“名称,等待和查看”方法。我们可以说原始算术运算是“严格的”。它取决于语言,但通常最原始的操作是严格的。
另请注意,“评估”仍然意味着减少到一个值。函数调用始终返回值,而不是表达式。 (其中一个答案是错误的。)OTOH,懒惰语言通常具有惰性数据构造函数,它可以具有在需要时评估的组件,即,在提取时。这就是你可以拥有一个“无限”列表的方式---你返回的值是一个懒惰的数据结构。 但是,按需调用和按值调用是懒惰与严格数据结构的单独问题。 Scheme具有惰性数据构造函数(流),尽管由于Scheme是按值调用的,构造函数是句法形式,而不是普通函数。 Haskell是按名称调用的,但它有定义严格数据类型的方法。
如果考虑实现是有帮助的,那么call-by-strong> name 的一个实现是将每个参数包装在thunk中;当需要参数时,你调用thunk并使用该值。 call-by-strong> need 的一个实现类似,但是thunk正在记忆;它只运行一次计算,然后保存它,然后只返回保存的答案。
答案 1 :(得分:10)
想象一个功能:
fun add(a, b) {
return a + b
}
然后我们称之为:
add(3 * 2, 4 / 2)
在按名称调用语言中,将对其进行评估:
a = 3 * 2 = 6
b = 4 / 2 = 2
return a + b = 6 + 2 = 8
该函数将返回值8
。
在按需调用(也称为惰性语言)中,这样评估如下:
a = 3 * 2
b = 4 / 2
return a + b = 3 * 2 + 4 / 2
该函数将返回表达式3 * 2 + 4 / 2
。到目前为止,几乎没有花费任何计算资源。只有在需要它的值时才会计算整个表达式 - 比方说我们要打印结果。
为什么这有用?有两个原因。首先,如果您不小心包含死代码,它不会降低您的程序权重,因此可以更高效。其次它允许做很酷的事情,比如用无限列表进行有效计算:
fun takeFirstThree(list) {
return [list[0], list[1], list[2]]
}
takeFirstThree([0 ... infinity])
在尝试创建从0到无穷大的列表时,将按名称语言挂起。惰性语言只会返回[0,1,2]
。
答案 2 :(得分:3)
一个简单而又说明性的例子:
function choose(cond, arg1, arg2) {
if (cond)
do_something(arg1);
else
do_something(arg2);
}
choose(true, 7*0, 7/0);
现在我们假设我们正在使用热切的评估策略,然后热切地计算7*0
和7/0
。如果它是一个惰性评估策略(按需调用),那么它只会将表达式 7*0
和7/0
发送到函数而不进行评估。
区别?你会期望执行do_something(0)
,因为第一个参数被使用,虽然它实际上取决于评估策略:
如果语言急切地评估,那么它将如上所述首先评估7*0
和7/0
,以及7/0
是什么?除零错误。
但是如果评估策略是懒惰的,它会发现它不需要计算除法,它会按照我们的预期调用do_something(0)
,没有错误。
在此示例中,延迟评估策略可以将执行保存为产生错误。以类似的方式,它可以保存执行不执行不会使用的不必要的评估(与此处不使用7/0
的方式相同)。