什么是需要的电话?

时间:2011-04-02 21:37:10

标签: programming-languages evaluation

我想知道什么是需要的电话。

虽然我在维基百科搜索并在此处找到了http://en.wikipedia.org/wiki/Evaluation_strategy, 但无法正确理解。 如果有人能用一个例子来解释并指出与按值调用的区别,那将是一个很大的帮助。

3 个答案:

答案 0 :(得分:29)

假设我们有函数

square(x) = x * x

我们要评估square(1+2)

按值调用中,我们执行

  1. square(1+2)
  2. square(3)
  3. 3*3
  4. 9
  5. 按名称调用中,我们执行

    1. square(1+2)
    2. (1+2)*(1+2)
    3. 3*(1+2)
    4. 3*3
    5. 9
    6. 请注意,由于我们使用了两次参数,因此我们对其进行了两次评估。如果论证评估需要很长时间,那将是浪费。这就是需要修复的问题。

      按需拨打中,我们执行以下操作:

      1. square(1+2)
      2. let x = 1+2 in x*x
      3. let x = 3 in x*x
      4. 3*3
      5. 9
      6. 在第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)

在按名称调用语言中,将对其进行评估:

  1. a = 3 * 2 = 6
  2. b = 4 / 2 = 2
  3. return a + b = 6 + 2 = 8
  4. 该函数将返回值8

    在按需调用(也称为惰性语言)中,这样评估如下:

    1. a = 3 * 2
    2. b = 4 / 2
    3. return a + b = 3 * 2 + 4 / 2
    4. 该函数将返回表达式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*07/0。如果它是一个惰性评估策略(按需调用),那么它只会将表达式 7*07/0发送到函数而不进行评估。

区别?你会期望执行do_something(0),因为第一个参数被使用,虽然它实际上取决于评估策略:

如果语言急切地评估,那么它将如上所述首先评估7*07/0,以及7/0是什么?除零错误。

但是如果评估策略是懒惰的,它会发现它不需要计算除法,它会按照我们的预期调用do_something(0),没有错误。

在此示例中,延迟评估策略可以将执行保存为产生错误。以类似的方式,它可以保存执行不执行不会使用的不必要的评估(与此处不使用7/0的方式相同)。