我已经检查了 Wikipedia 并在 Google 上搜索过,但我仍然无法理解名称传递在 ALGOL 60 中的工作原理。
8 回答
我在Pass-By-Name Parameter Passing找到了一个很好的解释。本质上,在将实际参数文本替换到函数体中之后,函数体在调用时被解释。从这个意义上说,评估方法类似于 C 预处理器宏的评估方法。
通过将实际参数代入函数体,函数体既可以读取也可以写入给定的参数。从这个意义上说,评估方法类似于传递引用。不同之处在于,由于使用 pass-by-name 参数是在函数内部计算的,所以参数如a[i]
取决于i
函数内部的当前值,而不是引用a[i]
函数调用之前的值。
我在上面链接的页面有更多示例说明传递名称既有用又危险的地方。通过名称传递实现的技术如今在很大程度上被其他更安全的技术所取代,例如传递引用和 lambda 函数。
我假设您的意思是 ALGOL 60 中的按名称呼叫。
Call-by-name 与 call-by-reference 类似,您可以更改传入参数的值。它与按引用调用的不同之处在于,在调用过程之前不对参数进行评估,而是延迟评估。也就是说,仅在实际使用参数时才对其进行评估。
例如,假设我们有一个过程f(x, y)
并且我们通过它并且i
最初等于。如果设置为然后评估,它将看到该值(而通过引用调用或按值调用它仍然会看到)。这是因为表达式在被评估之前不会被评估。i/2
i
10
f
x
42
y
21
5
i/2
y
在许多方面,这似乎表现为参数的文字文本替换(通过重命名以避免名称冲突)。然而,在实践中,这是使用“thunk”(基本上是闭包)来实现传入表达式的。
Jensen 设备上的 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]).
对于未来的人:
John C. Mitchell 的《编程语言概念》也很有帮助。
按名称传递. 回想起来,也许 Algol 60 最奇怪的功能是使用传递名称。在 pass-by-name 中,过程调用的结果与将形参替换到过程主体中的结果相同。通过复制过程并替换形式参数来定义过程调用结果的规则称为 Algol 60 复制规则。尽管复制规则适用于纯函数程序,如 lambda 演算中的 β 减少所示,但与形式参数的副作用的交互有点奇怪。下面是一个示例程序,展示了一种称为 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) 在 i 从 1 到 100 时将 j 的值相加。如果你看一下代码,你会意识到这个过程是没有意义的,除非改变 i 会导致j的值;否则,程序只计算 100*j。在此处显示的调用 sum(i, i*10) 中,当 i 从 1 变为 100 时,过程体 sum 中的 for 循环将 i*10 的值相加。
其实,点名,不仅仅是一种历史奇闻。您可以在 Windows 批处理文件(以及无数其他脚本语言)中进行名称调用。了解它是如何工作的,以及如何在编程中有效地使用它可以为问题提供巧妙的解决方案。我知道它只是传递字符串以供以后扩展,但可以对其进行操作以产生与按名称调用类似的效果。
call :assign x 1
exit /b
:assign
setlocal enabledelayedexpansion
(endlocal
:: Argument 1 is the name of the variable
set %1=%2
)
exit /b
您可以以变量的符号形式传递“名称”,这样可以同时更新和访问它。例如,假设您想将类型为 int 的变量 x 增加三倍:
start double(x);
real x;
begin
x : = x * 3
end;
Flatlander 在这里有一个关于它如何在 Scala 中工作的启发性示例。假设你想实现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,但它也许能提供一些启示。
ALGOL 专为数学算法而设计。我喜欢 summation 函数作为按名称调用的示例。
抱歉,我的 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 的调用会导致它被调用。当表达式用作参数时,编译器会生成未命名的函数。变量将是一个简单的间接引用。写入函数会发生错误。
我知道我加入俱乐部很晚,这不一定是一个答案,但我确实想补充一件事,可以帮助澄清一点。我一直认为 Algol pass-by-name 与 C++ 预处理器指令(特别是宏)在编译期间用实际代码段替换某些函数/变量的名称时类似的过程。pass-by-name 实质上是将形参的名字替换为实参,然后执行。我从来没有用 Algol 写过,但我听说 pass-by-name 将与 C++ 的 pass-by-reference 具有相同的结果。