【问题标题】:What is "pass-by-name" and how does it work exactly?什么是“按名称传递”,它是如何工作的?
【发布时间】:2010-10-24 16:43:31
【问题描述】:

我查看了 Wikipedia 并在 Google 上搜索过,但我仍然无法完全理解 ALGOL 60 中名称传递的工作原理。

【问题讨论】:

  • 你可以认为它们的工作方式类似于 c++ 中的内联函数

标签: parameter-passing pass-by-name language-history algol


【解决方案1】:

ALGOL 专为数学算法而设计。我喜欢将求和函数作为按名称调用的示例。

抱歉,我的 ALGOL 有点生疏,语法可能不正确。

.FUNCTION SUM(var,from,to,function)
.BEGIN
  .REAL sum =0;
  .FOR var = from .TO to .DO sum = sum + function;
  return sum;
.END

你可以像这样使用 sum

  Y = sum(x,1,4,sum(y,3,8,x+y));

在上面的内部 sum(y,3,8,x+y) 将生成一个未命名的函数以传递给外部 sum 调用。变量 x 和 y 不是按值传递,而是按名称传递。在变量的情况下,名称调用相当于C中的地址引用调用。涉及递归时会有点混乱。

借用了 ALGOL 机器。它们有 48 位字存储器和 3 个标志位。标志位通过 ALGOL 的名称实现了校准。它是一个堆栈机器,所以当函数被加​​载到堆栈上时,按名称 fag 的调用会导致它被调用。当表达式用作参数时,编译器会生成未命名的函数。变量将是一个简单的间接引用。写入函数会发生错误。

【讨论】:

    【解决方案2】:

    对于未来的人:

    编程语言中的概念 John C. Mitchell 也很有帮助。

    姓名传递。也许是最奇怪的 回想起来,Algol 60 的特点是 名称传递的使用。在 按名称传递,结果 过程调用与 形式参数被代入 程序的主体。这条规则 用于定义过程的结果 通过复制过程调用和 代替形式参数 称为 Algol 60 复制规则。 虽然复制规则适用于 纯函数程序,如 通过 λ 的 β 减少来说明 微积分,与方的相互作用 对形式参数的影响是 有点怪。这是一个例子 显示技术的程序 作为 Jensen 的设备:传递一个 表达式及其包含的变量 到一个程序,使该程序 可以使用一个参数来改变 对方所指的位置:


     begin integer i;
            integer procedure sum(i, j);
                integer i, j;
                    comment parameters passed by name;
                begin integer sm; sm := 0;
                    for i := 1 step 1 until 100 do sm := sm + j;
                    sum := sm
                end;
            print(sum(i, i*10 ))
     end
    

    在这个程序中,程序 sum(i,j) 将 j 的值相加为 i 从 1 到 100。如果你看 代码,你会发现 程序没有意义,除非 我的变化导致了一些变化 j的值;否则,程序 只计算 100*j。在通话中 sum(i, i*10) 此处显示,for 循环 程序体中的总和 当 i 从 1 变为时 i*10 的值 100.

    【讨论】:

      【解决方案3】:

      我假设您的意思是 ALGOL 60 中的按名称调用。

      Call-by-name 与 call-by-reference 类似,您可以更改传入参数的值。它与按引用调用的不同之处在于,在调用过程之前评估参数,而是延迟评估。即,仅在实际使用参数时才对其进行评估。

      例如,假设我们有一个过程f(x, y),我们将它传递给ii/2,其中i 最初等于10。如果fx 设置为42,然后评估y,它将看到21 的值(而通过引用调用或按值调用它仍然会看到5)。这是因为表达式 i/2 直到 y 被计算后才会被计算。

      在许多方面,这似乎表现为参数的文字文本替换(通过重命名以避免名称冲突)。然而,在实践中,这是使用“thunk”(基本上是闭包)来实现传入的表达式。

      Jensen's Device 上的 Wikipedia 文章显示了一些使用名称调用的有趣示例。这是其中之一:

      real procedure Sum(k, l, u, ak)
           value l, u;
           integer k, l, u;
           real ak;
           comment k and ak are passed by name;
       begin
           real s;
           s := 0;
           for k := l step 1 until u do
               s := s + ak;
           Sum := s
       end;
      

      在程序中,索引变量k和求和项ak是 按名称传递。按名称调用使过程能够更改值 在 for 循环执行期间的索引变量。叫名字 还会导致在每次迭代期间重新评估 ak 参数 循环。通常,ak 将取决于变化(副作用) k.

      例如,计算实数的前 100 项之和的代码 数组V[] 将是:

      Sum(i, 1, 100, V[i]).
      

      【讨论】:

      • 要明确一点... 引用调用作为引用传递只是同一概念的不同术语吗?
      • @orrymr 是的,they're the same thing。我不是 100% 确定,但“引用调用”似乎是较旧的术语,因此您会看到它用于讨论 Algol 和 Pascal 等语言。进入 C++ 及更高版本,“按引用传递”(更有意义,恕我直言)是人们通常使用的术语。
      【解决方案4】:

      我知道我加入俱乐部的时间很晚,这不一定是一个答案,但我确实想补充一点可以帮助澄清一点的事情。我一直认为 Algol pass-by-name 与 C++ 预处理器指令(特别是宏)在编译期间用实际代码段替换某些函数/变量的名称时类似的过程。 pass-by-name 实质上是将形参的名字替换为实参,然后执行。我从来没有用 Algol 写过,但我听说 pass-by-name 将与 C++ 的 pass-by-reference 具有相同的结果。

      【讨论】:

        【解决方案5】:

        其实,点名,不仅仅是一种历史奇闻。您可以在 Windows 批处理文件(以及无数其他脚本语言)中进行名称调用。了解它是如何工作的,以及如何在编程中有效地使用它可以为问题提供巧妙的解决方案。我知道它只是传递字符串以供以后扩展,但可以对其进行操作以产生与按名称调用类似的效果。

        call :assign x 1
        exit /b
        :assign
        setlocal enabledelayedexpansion
        (endlocal
        :: Argument 1 is the name of the variable
        set %1=%2
        )
        exit /b
        

        【讨论】:

          【解决方案6】:

          您可以以变量的符号形式传递“名称”,这样就可以同时更新和访问它。例如,假设您想将 int 类型的变量 x 增加三倍:

          start double(x);
          real x;
          begin
          x : = x * 3
          end;
          

          【讨论】:

          • 是的,但是这个例子和 pass-by-reference 一样好。 (是的,我现在看到日期了)
          【解决方案7】:

          Flatlander 有一个关于它在 Scala here 中如何工作的启发性示例。假设你想实现 while

          def mywhile(condition: => Boolean)(body: => Unit): Unit =
            if (condition) {
              body
              mywhile(condition)(body)
            }
          

          我们可以这样称呼它:

          var i = 0
          mywhile (i < 10) {
            println(i)
            i += 1
          }
          

          Scala 不是 Algol 60,但它也许能提供一些启示。

          【讨论】:

            【解决方案8】:

            我在Pass-By-Name Parameter Passing 找到了一个很好的解释。本质上,在将实际参数文本替换到函数体中之后,函数体在调用时被解释。从这个意义上说,求值方法类似于 C 预处理器宏的求值方法。

            通过将实际参数代入函数体,函数体既可以读写给定的参数。从这个意义上说,评估方法类似于传递引用。不同之处在于,由于使用 pass-by-name 参数在函数内部被 求值,所以像 a[i] 这样的参数依赖于函数内部 i 的当前值,而不是引用调用函数之前a[i] 处的值。

            我在上面链接的页面有更多示例说明传递名称既有用又危险的地方。通过名称传递实现的技术如今在很大程度上被其他更安全的技术所取代,例如传递引用和 lambda 函数。

            【讨论】:

            • @Greg Hewgill,你能再解释一下 pass-by-reference 和 pass-by-name 之间的区别吗?
            • 好的,添加了一些东西,希望对您有所帮助。
            • 所以,不是传递值或引用,而是将文字表达式“复制”到函数体中?
            • @Paperino:是的,没错。就好像函数在运行时使用给定的实际参数再次编译(尽管实现是使用 thunk 完成的,如那篇文章中所述)。
            • 与C预处理器有什么区别?
            猜你喜欢
            • 2010-11-21
            • 2019-09-28
            • 2013-02-13
            • 2018-06-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-08
            相关资源
            最近更新 更多