Ada*_* Sh 17 evaluation lazy-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
Run Code Online (Sandbox Code Playgroud)
因此,按照我的理解,在按名称呼叫时,我们将获得:
;;; the value of e is 2
;;; the value of e is 4
;;; the value of e is 8
Run Code Online (Sandbox Code Playgroud)
等等.这是因为我们通过2*n对e,并e与新的评估i每次.在需要召唤时会发生什么?
Dan*_*yes 10
你的困惑似乎源于你在一个命令式的背景下思考的事实.讨论的call-by-需要对呼叫按值大多出现有关声明和函数式语言,和演算.
您可以在本文中看到评估策略,即按名称调用和按需调用都被视为惰性评估策略.延迟评估意味着当表达式作为参数传递给函数时,在进入函数体之前不会对其进行求值,而只是在函数内第一次访问/读取时才进行求值.如果从不在内部使用此类表达式的结果,则永远不会对其进行求值.
例如,? :运算符在Java中是惰性的,如下面的代码所示:
String test(Object obj)
{
return 1 == 2 ? obj.toString() : "Hello World";
}
test(null); // this won't throw a NullPointerException
Run Code Online (Sandbox Code Playgroud)
按需调用是具有纯子集的大多数函数语言的基本特征.在纯函数式语言中,每个函数都必须是引用透明的,即它们不能有副作用.这样的纯函数具有这样的特性:对于某些给定的输入,它们总是返回相同的输出,无论调用多少次,并且它们在"世界状态"中永远不会改变任何东西.它们的行为就像写在纸上的数学函数一样.
正如你已经意识到,调用-需要调用非纯函数时的策略是行不通的,因为你是最有可能感兴趣的副作用,由于连续的调用.另一方面,当在纯函数语言中使用时,它成为性能的基本特征(参见下面的第二个示例).另外,请参阅这些wiki页面,了解Graph Reduction和Memoization的概念.
第一.使用图缩减的常用系统的一个示例是Apache Ant.Ant不会两次评估目标.此设计可以方便地绘制声明性构建计划.
第二.如果你想看到一个很好的memoization演示,输入这个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.
Run Code Online (Sandbox Code Playgroud)
注意.您可能还听说过按值调用的评估策略.与此相反的call-by-名称和调用-需要,调用-值是一个严格的评估策略.它像调用式的名字在这个意义上,多次调用导致多次评估.对于习惯于C#或Java等命令式语言的程序员来说,这是最常见的范例.
Call-by-name是一个函数调用规则,当调用一个接收函数foo时,而不是foo被赋值的参数,foo接收(在幕后)一个适当的对象,允许它评估它的参数需要; 或者等价地,通过宏观替代进行评估.如果一个参数需要多次,则将对其进行多次评估.请参阅:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name
按需调用是一样的,除了传递的对象是一个承诺,并且将被评估不超过一次; 在对参数的后续引用中,使用记忆值.请参阅:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_need
首先,无论是call-by-need还是call-by-name都代表了惰性求值的实现方式,所以有必要知道什么是惰性求值......
例如,您可以在 Haskell 中看到按需要调用,在 Scala 中看到按名称调用。按需要调用可能是实现惰性的预期方式。当您第一次“真正”需要该值时,您会计算它并缓存计算值以供将来访问。
当您使用按名称调用时,您不会执行这种缓存,而是每次在函数体中使用函数参数时都会对其求值。可能会认为它没有意义,但是将一些流控制结构实现为语言的函数是有用的,比如说一段时间。
def myWhile (cond : => Boolean, body : => Unit) {
if (cond) { body ; myWhile (cond, body) }
}
Run Code Online (Sandbox Code Playgroud)
然后你可以调用函数 myWhile,
var x = 3
myWhile (x != 0, {
print (x)
x = x - 1
})
Run Code Online (Sandbox Code Playgroud)
如果我们在上面的示例中有按名称调用,则表达式“cond”不会被缓存,并且每次需要时都会对其进行求值。
Ada*_* Sh -2
在按需调用中,我们进入循环,并且仅对值求值一次。因此,在上面的代码中,我们将(2*n)在循环内部进行复制(宏样式),并且我们将仅对表达式求值一次(不像按名称调用)。因此,在第一次迭代中我们将得到e=2. 这将是下一次迭代中的值e,输出将为:
;;; the value of e is 2
;;; the value of e is 2
;;; the value of e is 2
Run Code Online (Sandbox Code Playgroud)