按需要拨打电话按姓名拨打电话

时间:2012-01-02 14:11:19

标签: lazy-evaluation evaluation

我不明白Call-by-name和Call-by-need之间的差异。据我所知,Call-by-need方法恢复了返回的答案。但它如何帮助我们,结果之间是否存在根本差异?

例如,

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

等等。这是因为我们将2*n传递给e,并且e每次都使用新的i进行评估。 在需要召唤时会发生什么?

5 个答案:

答案 0 :(得分:10)

您的混淆似乎源于您在imperative背景下思考的事实。关于按需调用按值调用的讨论主要涉及声明和函数语言以及lambda演算。

您可以在本文中看到evaluation strategies 按名称调用按需调用被视为延迟评估< / em>策略。延迟评估意味着当表达式作为参数传递给函数时,在进入函数体之前不会对其进行求值,而只在第一次在函数内部访问/读取时才对其进行求值。如果这种表达式的结果从未在内部使用过,那么它将永远不会被评估。

例如,? :运算符在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具有这样的属性:对于某些给定的输入,无论调用多少次,它们总是返回相同的输出,并且它们在“世界状态”中永远不会改变任何东西。它们的行为就像写在纸上的数学函数一样。

正如您已经意识到的那样,在调用非纯函数时,按需调用策略是不可行的,因为您很可能对由于连续调用引起的副作用感兴趣。另一方面,当在纯函数语言中使用时,它成为性能的基本特征(参见下面的第二个示例)。另外,请参阅这些维基页面,了解Graph ReductionMemoization的概念。

真实世界的例子

首先。使用图表缩减的常用系统的一个示例是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.

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

答案 1 :(得分:8)

按名称调用是一个函数调用规则,当调用接收函数foo时,foo接收(在幕后)一个允许它评估的适当对象,而不是foo的参数。它需要的参数;或者等价地,通过宏观替代进行评估。如果一个参数需要多次,则将对其进行多次评估。请参阅:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name

按需调用大致相同,只是传递的对象是 promise ,并且将被评估不超过一次;在对参数的后续引用中,使用记忆值。请参阅:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need

答案 2 :(得分:2)

首先,无论是按需调用还是按名称调用都代表了实现延迟评估的方法,因此有必要知道什么是lazy-evaluation ......

例如,您可以在Haskell中看到按需调用,在Scala中可以看到按名称调用。

按需拨打可能是实施懒惰的预期方式。第一次“真正”需要该值时,您计算了它,并为计算出的值进行了缓存以供将来访问。

当你有 call-by-name 时,你不执行这种缓存,每次在函数体中使用它们时都会计算函数参数。有可能认为它没有意义,但实现一些流控制结构作为语言的功能是有用的,让我们说一段时间。

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”不会被缓存,并且每次都需要进行评估。

答案 3 :(得分: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
  的输出:25220

需要电话: 代替x,它变为i + j,并且在第一次打印之后,x即i + j值变为15,它将其记忆。 所以输出将是 的输出:25,15

答案 4 :(得分:-2)

在按需调用时,我们进入循环,并仅评估一次值。所以在上面的代码中,我们将在循环内部复制(2*n)(宏样式),我们将只计算一次表达式(不像名称调用)。 因此,在第一次迭代中,我们将获得e=2。这也是下一次迭代中e的值,输出将是:

;;; the value of e is 2
;;; the value of e is 2
;;; the value of e is 2