按需调用 vs 按名字调用

21

我不理解按名称调用和按需调用之间的区别。据我了解,按需调用方法还原返回的答案。但它如何帮助我们,并且结果之间是否有任何根本差异?

例如,

begin integer n;
  procedure foo(e, n);
  integer e, n;
  begin
    for n := 1 step 1 until 10 do begin
      prints(`;;; the value of e is ');
      printnln(e)
    end
  end;
  foo(2 * n, n)
end

所以在按名称调用中,据我了解,我们将得到:

;;; the value of e is 2
;;; the value of e is 4
;;; the value of e is 8

等等,这是因为我们向e传递了2*n,而且每次都要使用新的i来评估e

在需要调用时会发生什么?

5个回答

11
你似乎因为思考在命令式的上下文中而感到困惑。关于声明式和函数式语言以及lambda演算,对于按需调用(call-by-need)和按值调用(call-by-value)的讨论,通常会引起这种困惑。
你可以在此关于求值策略的文章中看到,按名字调用(call-by-name)和按需调用(call-by-need)都被认为是惰性求值(lazy evaluation)策略。惰性求值意味着当将一个表达式作为参数传递给函数时,在进入函数体之前不会对其进行求值,而只有在第一次访问/读取该表达式时才会进行求值。如果该表达式的结果从未在内部使用,则永远不会被求值。
例如,Java中的? : 操作符是惰性的,如下面的代码所示:
String test(Object obj)
{
    return 1 == 2 ? obj.toString() : "Hello World";
}

test(null); // this won't throw a NullPointerException

按需调用是大多数函数式语言中具有子集的基本功能。在纯函数式语言中,每个函数都必须是引用透明的,即它们不能有副作用。这些纯函数具有这样的特性:对于某些给定的输入,无论被调用多少次,它们始终返回相同的输出,并且它们永远不会改变“世界状态”中的任何内容。它们的行为就像写在纸上的数学函数。

正如您已经意识到的那样,当调用非纯函数时,“按需调用”策略是不可行的,因为您很可能对由连续调用引起的副作用感兴趣。另一方面,在纯函数语言中使用它时,它成为了性能的重要特性(请参见下面的第二个示例)。此外,请参阅这些关于图形缩减记忆化概念的维基页面。
现实世界的例子
第一个例子是一个常用系统Apache Ant,它使用图形缩减。Ant不会对目标进行两次评估。这种设计使得草拟一个声明性的构建计划变得方便。
第二个例子是一个好的记忆化演示,如果你想看到它,请在GHC解释器中输入这个Haskell代码。
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.

注意。 你可能也听说过按值调用的评估策略。与按名称调用按需调用相比,按值调用是一种严格的评估策略。它类似于按名称调用,因为多次调用会导致多次评估。这是程序员最常见的范式,习惯于类似C#或Java的命令式语言。


9
Call-by-name是一种函数调用规则,当调用接收函数foo时,不会对foo的参数进行求值,而是在幕后传递一个适当的对象给foo,该对象将允许它评估所需的参数;或者等价地说,评估通过宏替换进行。如果一个参数被需要多次,它将被评估多次。参见:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name Call-by-need与Call-by-name非常相似,只是传递的对象是一个“承诺”,并且最多只会被评估一次;在后续引用参数时,将使用备忘录值。参见:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need

5
首先,call-by-need和call-by-name都是实现惰性求值的方式,因此需要了解什么是惰性求值...
call-by-need可能是实现延迟计算的预期方式。第一次“真正”需要该值时,您计算它并为将来访问缓存计算出的值。
当您使用call-by-name时,不执行此类缓存,每次在函数体中使用函数参数时都会评估它们。可能认为这没有意义,但对于实现某些语言的流程控制结构很有用,比如while。
def myWhile (cond : => Boolean, body : => Unit) {
  if (cond) { body ; myWhile (cond, body) }
}

然后你可以调用函数myWhile,

var x = 3

myWhile (x != 0, {
  print (x)
  x = x - 1   
})

如果我们在上面的例子中使用按名称调用,表达式“cond”不会被缓存,并且每次需要时都会被评估。

0

按名称调用和按需调用都像宏展开。但是在按需调用中,一旦计算出值,它会将该值存储起来,并在下一次使用此值。

例如:在2003年的门户网站上有一个问题。

global int i=100,j=5;
void P(x){
    int i=10;
    printf("%d",x+10);
    i=200;
    j=20;
    printf("%d",x);}

main(){P(i+j);}

按名称调用: 在函数中,将x替换为i+j 因此第一次打印将是i+j+10=10+5+10=25下一个打印将是i+j=200 + 20
输出:25,220

按需调用: 在x的位置上,它变成了i+j,并在第一次打印后,x即i+j值变为15,它对其进行了备忘。 所以输出将是
输出:25,15


-2
在需要调用时,我们进入循环,并仅评估一次该值。因此,在上面的代码中,我们将在循环内复制(2 * n)(宏样式),并且我们将仅评估表达式一次(不像按名称调用)。因此,在第一次迭代中,我们将获得e = 2。这也将是下一次迭代中e的值,并且输出将为:
;;; the value of e is 2
;;; the value of e is 2
;;; the value of e is 2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接