【问题标题】:Call by need vs call by name按需求呼叫与按名称呼叫
【发布时间】:2012-01-31 21:02:41
【问题描述】:

我不明白点名呼叫和按需呼叫之间的区别。据我了解,按需调用方法会恢复返回的答案。但是它如何帮助我们,结果之间有什么根本区别吗?

例如,

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 进行评估。 按需调用会发生什么?

【问题讨论】:

    标签: lazy-evaluation evaluation


    【解决方案1】:

    您的困惑似乎源于您在imperative 上下文中思考的事实。 call-by-need 与 call-by-value 的讨论主要涉及声明性和函数式语言以及 lambda 演算。

    您可以在这篇关于 evaluation strategies 的文章中看到 call-by-name 和 call-by-need 都被视为 惰性评估 策略。惰性求值是指将表达式作为参数传递给函数时,在进入函数体之前不求值,而仅在函数内部第一次访问/读取时才求值。如果此类表达式的结果从未在内部使用过,则永远不会对其求值。

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

    正如您已经意识到的那样,call-by-need 策略在调用非纯函数时是不可行的,因为您很可能对连续调用的副作用感兴趣。另一方面,当在纯函数式语言中使用时,它成为性能的基本特征(参见下面的第二个示例)。另外,请参阅这些关于 Graph Reduction 和 Memoization 概念的 wiki 页面。

    现实世界的例子

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

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

    【讨论】:

      【解决方案2】:

      Call-by-name 是一种函数调用规则,当调用接收函数 foo 时,而不是评估 foo 的参数,foo 接收(在幕后)一个允许它评估的适当对象它需要的参数;或者等效地,评估通过宏替换进行。如果多次需要一个参数,则将对其进行多次评估。见:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name

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

      【讨论】:

        【解决方案3】:

        首先,无论是call-by-need还是call-by-name都代表了实现惰性求值的方式,所以有必要知道lazy-evaluation是什么...

        例如,您可以在 Haskell 中看到 call-by-need,在 Scala 中看到 call-by-name。

        call-by-need 可能是实现惰性的预期方式。当您第一次“真正”需要该值时,您计算了它并缓存了计算值以供将来访问。

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

        【讨论】:

          【解决方案4】:

          按名称调用和按需要调用都类似于宏扩展。但是在按需要调用时,一旦对值进行评估,它就会记住该值并下次使用该值。 例如:-gate 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 它记住了它。 所以输出将是
          output:25,15

          【讨论】:

            【解决方案5】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-02-27
              • 2018-02-15
              • 1970-01-01
              • 2018-02-24
              • 1970-01-01
              • 2012-06-30
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多