按名称呼叫与宏扩展呼叫

时间:2017-06-12 02:30:18

标签: evaluation

在非严格评估语言中,使用按名称调用调用vs 调用宏扩展有什么区别和优缺点?

您能提供一个解释两种评估策略的例子吗?

谢谢!

1 个答案:

答案 0 :(得分:2)

按姓名呼叫:

按名称调用是一种评估策略,其中函数的参数在调用函数之前不会被计算 - 而是直接替换到函数体中(使用捕获避免替换)然后在它们被评估时进行评估出现在函数中。如果函数体中没有使用参数,则永远不会计算参数;如果多次使用,则每次出现时都会重新评估。 (参见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); }宏遇到一个问题,称为变量捕获。如果一个   macro定义了一个已在环境中定义的变量v   调用者,并将v作为参数传递给宏,正文   的宏将无法区分v的一次出现   另一个。例如,下面的程序有一个定义a的宏   变温。 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   在函数f内,第一个赋值,例如b = 5,在变量i中存储5。第二个赋值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的后代,也可以通过名称实现调用,我们可以看到   这个例子。按名称调用总是会导致对...的评估   参数,即使多次使用此参数。这个   在引用透明的语言中,行为可能是浪费的,   因为,在这些语言中,变量是不可变的。