【发布时间】:2017-06-12 02:30:18
【问题描述】:
在非严格的评估语言中,使用名称调用与宏扩展调用有什么区别和优点/缺点?
您能否提供一个解释这两种评估策略的示例?
谢谢!
【问题讨论】:
标签: evaluation
在非严格的评估语言中,使用名称调用与宏扩展调用有什么区别和优点/缺点?
您能否提供一个解释这两种评估策略的示例?
谢谢!
【问题讨论】:
标签: evaluation
按姓名呼叫:
按名称调用是一种求值策略,其中函数的参数在调用函数之前不求值——而是直接将它们替换到函数体中(使用避免捕获替换),然后在它们被调用时留待求值出现在函数中。如果函数体中没有使用参数,则永远不会评估该参数;如果多次使用,每次出现都会重新评估。 (参见 Jensen 的设备。)
名称调用评估有时比值调用评估更可取。如果函数的参数未在函数中使用,则按名称调用将通过不评估参数来节省时间,而按值调用将无论如何都对其进行评估。如果参数是非终止计算,则优势是巨大的。但是,当使用函数参数时,按名称调用通常会更慢,需要诸如 thunk 之类的机制。
早期使用的是 ALGOL 60。今天的 .NET 语言可以使用委托或表达式参数模拟名称调用。后者导致为函数提供抽象语法树。 Eiffel 提供了代理,它们代表需要时要评估的操作。 Seed7 提供按名称调用和函数参数。
宏调用:
宏扩展调用类似于名称调用,但使用文本替换而不是捕获避免替换。如果使用不当,宏替换可能会导致变量捕获并导致不良行为。卫生宏通过检查和替换不是参数的阴影变量来避免这个问题。
注意:在非严格的评估语言中
宏调用示例:
通过宏扩展调用:许多编程语言,包括 C、lisp 和方案,为开发人员提供一种机制来添加新的语法 称为宏的核心语言语法。宏扩展为代码 通过宏预处理器。这些宏可能包含参数,其中 被复制到预处理器生成的最终代码中。作为一个 例如,下面的 C 程序通过宏实现了交换功能:
#define SWAP(X,Y) {int temp=X; X=Y; Y=temp;} int main() { int a = 2; int b = 3; printf("%d, %d\n", a, b); SWAP(a, b); printf("%d,
> %d\n", a, b); }
这个宏实现了一个有效的交换例程。
预处理程序将如下面的代码所示。因为身体 宏直接复制到调用程序的文本中, 它在该程序的上下文中运行。换句话说,宏 将直接引用它接收的变量名,而不是 他们的价值观。
int main() { int a = 2; int b = 3; printf("%d, %d\n", a, b); {
> int tmp = (a); (a) = (b); (b) = tmp; }; printf("%d, %d\n", a, b); }
作为参数传递给宏的表达式每次都会被计算 它们在宏的主体中使用的时间。如果论点从不 使用,那么它根本不被评估。例如,程序 下面将增加变量 b 两次:
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) int main() { int a = 2, b = 3; int c = MAX(a, b++); printf("a = %d, b = %d, c = %d\n", a, b, c); }宏遇到一个问题,称为变量捕获。如果一个 宏定义了一个已经在环境中定义的变量 v 调用者的,而v作为参数传递给宏,body 的宏将无法将 v 的出现与 另一个。例如,下面的程序有一个宏定义了一个 可变温度main 内部的调用导致变量 temp 定义 在这个函数里面被里面的定义捕获 宏的主体。
#define SWAP(X,Y) {int temp=X; X=Y; Y=temp;} int main() { int a = 2; int temp = 17; printf("%d, temp = %d\n", a, temp); SWAP(a, temp);
> printf("%d, temp = %d\n", a, temp); }
一旦这个程序被扩展
C 预处理器,我们得到下面的代码。该程序未能 交换变量 temp 和 a 的值:
int main() { int a = 2; int temp = 17; printf("%d, temp = %d\n",
> a, temp); {int temp=a; a=temp; temp=temp;}; printf("%d, temp =
> %d\n", a, temp); }
有很多惰性求值策略
避免变量捕获问题。最著名的两种技术 是按名称调用和按需要调用。
按名称调用示例:
按名称调用:在此评估策略中,实际参数仅为 评估是否在函数内部使用;但是,此评估使用 调用程序的上下文。例如,在下面的示例中, 取自 Weber 的书,我们有一个函数 g,它返回整数 6. 在函数 f 中,第一个赋值,例如 b = 5,将 5 存储在变量 i 中。第二个赋值,b = a,读取 i 的值, 当前为 5,并将其加 1。然后将该值存储在 i 中。
void f(by-name int a, by-name int b) { b=5; b=a; } int g() { int i = 3; f(i+1,i); return i; }很少有语言实现按名称调用评估策略。这 这些语言中最杰出的是 Algol。模拟,直接 Algol 的后代,也实现了按名称调用,正如我们在 这个例子。按名称调用总是会导致对 参数,即使该参数被多次使用。这 在引用透明的语言中,行为可能是浪费的, 因为,在这些语言中,变量是不可变的。
【讨论】: