Programming languages 什么是按需呼叫?

Programming languages 什么是按需呼叫?,programming-languages,evaluation,call-by-value,evaluation-strategy,call-by-need,Programming Languages,Evaluation,Call By Value,Evaluation Strategy,Call By Need,我想知道什么叫需要 虽然我在维基百科上搜索并在这里找到了:, 但我不能完全理解。 如果有人能用一个例子来解释并指出按值调用的区别,那将是一个很大的帮助。想象一个函数: fun add(a, b) { return a + b } 然后我们称之为: add(3 * 2, 4 / 2) 在按名称呼叫语言中,将对其进行评估,以便: a=3*2=6 b=4/2=2 返回a+b=6+2=8 该函数将返回值8 在按需调用(也称为惰性语言)中,其计算如下: a=3*2 b=4/2 返回a+b=3*2

我想知道什么叫需要

虽然我在维基百科上搜索并在这里找到了:, 但我不能完全理解。 如果有人能用一个例子来解释并指出按值调用的区别,那将是一个很大的帮助。

想象一个函数:

fun add(a, b) {
  return a + b
}
然后我们称之为:

 add(3 * 2, 4 / 2)
在按名称呼叫语言中,将对其进行评估,以便:

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

    在按需调用(也称为惰性语言)中,其计算如下:

  • a=3*2
  • b=4/2
  • 返回a+b=3*2+4/2
  • 函数将返回表达式
    3*2+4/2
    。到目前为止,几乎没有花费任何计算资源。整个表达式只有在需要它的值时才会被计算——比如说我们想要打印结果

    这为什么有用?有两个原因。首先,如果您意外地包含了死代码,它不会降低您的程序的重量,因此可以提高效率。其次,它允许做一些非常酷的事情,比如使用无限列表高效计算:

    fun takeFirstThree(list) {
      return [list[0], list[1], list[2]]
    }
    
    takeFirstThree([0 ... infinity])
    

    按名称调用语言将挂在那里,试图创建一个从0到无穷大的列表。惰性语言只会返回
    [0,1,2]
    一个简单但说明性的示例:

    function choose(cond, arg1, arg2) {
       if (cond)
          do_something(arg1);
       else
          do_something(arg2);
    }
    
    choose(true, 7*0, 7/0);
    
    现在让我们假设我们正在使用急切的评估策略,然后它将急切地计算
    7*0
    7/0
    。如果它是一种延迟求值策略(按需调用),那么它只会将表达式
    7*0
    7/0
    发送到函数,而不进行求值

    区别是什么?您可能希望执行
    do\u something(0)
    ,因为使用了第一个参数,尽管它实际上取决于评估策略:

    如果该语言急于求值,那么它将如前所述首先求值
    7*0
    7/0
    ,那么
    7/0
    是什么?除以零误差

    但是如果评估策略是惰性的,它将看到它不需要计算除法,它将调用
    do\u something(0)
    ,正如我们所期望的那样,没有错误


    在本例中,延迟评估策略可以避免执行过程中产生错误。以类似的方式,它可以避免执行它不会使用的不必要的计算(与这里没有使用
    7/0
    的方式相同)。

    假设我们有这个函数

    square(x) = x * x
    
    我们要计算
    平方(1+2)

    按值调用中,我们

  • square(1+2)
  • square(3)
  • 3*3
  • 9
  • 点名中,我们是这样做的

  • square(1+2)
  • (1+2)*(1+2)
  • 3*(1+2)
  • 3*3
  • 9
  • 注意,因为我们使用了两次参数,所以我们对它进行了两次评估。如果论证评估花费很长时间,那将是浪费。这就是需要解决的问题

    在“按需呼叫”中,我们执行以下操作:

  • square(1+2)
  • 设x*x中的x=1+2
  • 设x=3英寸x*x
  • 3*3
  • 9
  • 在第2步中,我们给它一个名称,而不是复制参数(如按名称调用)。然后在步骤3中,当我们注意到需要
    x
    的值时,我们计算
    x
    的表达式。只有到那时我们才能替换

    顺便说一句,如果参数表达式产生了更复杂的结果,比如闭包,那么可能会有更多的
    let
    s混洗来消除复制的可能性。正式规则写下来有点复杂

    请注意,对于诸如
    +
    *
    等基本操作的参数,我们“需要”值,但对于其他函数,我们采用“name,wait,and see”方法。我们可以说原始算术运算是“严格的”。它取决于语言,但通常大多数原始操作都是严格的

    还请注意,“评估”仍然意味着减少到一个值。函数调用总是返回值,而不是表达式。(另一个答案搞错了。)OTOH,惰性语言通常有惰性数据构造函数,这些构造函数可以包含根据需要(即提取时)计算的组件。这就是为什么您可以拥有一个“无限”列表——您返回的值是一个惰性数据结构。但按需调用与按值调用是与惰性与严格数据结构不同的问题。Scheme具有惰性数据构造函数(streams),尽管由于Scheme是按值调用的,所以构造函数是语法形式,而不是普通函数。Haskell是按名称调用的,但它有定义严格数据类型的方法


    如果它有助于思考实现,那么——name调用的一个实现就是将每个参数包装成一个thunk;当需要参数时,调用thunk并使用该值。按-need调用的一个实现类似,但thunk正在记忆化;它只运行一次计算,然后保存它,然后返回保存的答案。

    即使是渴望的语言也会这样做。我只是觉得这有误导性,因为即使是自称渴望的语言在这种情况下也不会给你一个被零除的错误-因为他们渴望,他们会首先计算布尔值,然后计算除法无意义的您所描述的“渴望”更像是一种评估一切的语言。我认为您可能会混淆“按名称调用”和“按值调用”以及“按需要调用”和“按名称调用”。我还认为这不准确。你所说的按名称调用,实际上不是按值调用吗?Haskell不是按需要调用,而是按名称调用吗?