【问题标题】:Call by name vs call by macro expansion按名称调用与按宏扩展调用
【发布时间】:2017-06-12 02:30:18
【问题描述】:

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

您能否提供一个解释这两种评估策略的示例?

谢谢!

【问题讨论】:

    标签: evaluation


    【解决方案1】:

    按姓名呼叫:

    按名称调用是一种求值策略,其中函数的参数在调用函数之前不求值——而是直接将它们替换到函数体中(使用避免捕获替换),然后在它们被调用时留待求值出现在函数中。如果函数体中没有使用参数,则永远不会评估该参数;如果多次使用,每次出现都会重新评估。 (参见 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 的后代,也实现了按名称调用,正如我们在 这个例子。按名称调用总是会导致对 参数,即使该参数被多次使用。这 在引用透明的语言中,行为可能是浪费的, 因为,在这些语言中,变量是不可变的。

    【讨论】:

    • @sugansoft 您能否查看我的类似帖子:stackoverflow.com/questions/56716884/…。我试图找出 call by macro expansioncall by name 方法在我的示例中是否给出不同的输出结果。如果不是,我想看看 按名称调用按宏扩展调用 不同的示例。
    猜你喜欢
    • 2015-06-07
    • 2021-09-11
    • 1970-01-01
    • 2013-10-02
    • 1970-01-01
    • 1970-01-01
    • 2013-12-15
    相关资源
    最近更新 更多