【问题标题】:Call by Name with dynamic scoping使用动态范围按名称调用
【发布时间】:2013-02-06 09:08:40
【问题描述】:

我在静态/动态范围内遇到以下问题:

以下程序片段是用允许全局的编程语言编写的 变量,并且不允许函数的嵌套声明。

 global int i = 100, j = 5; 
 void P(x) { 
  int i = 10; 
  print(x + 10); 
  i = 200; 
  j = 20; 
  print (x); 
 } 
 main() {P(i + j);} 

第一季度。如果编程语言使用静态范围和按需调用 参数传递机制,上面程序打印的值 是

(A) 115, 220 (B) 25, 220 (C) 25, 15 (D) 115, 105

第二季度。如果编程语言使用动态作用域并按名称调用 参数传递机制,上面程序打印的值 是

(A) 115, 220 (B) 25, 220 (C) 25, 15 (D) 115, 105

我的想法:

在 Q1:因为它是静态范围并且根据需要调用,x 应该替换为 i + j。但这会导致本地名称冲突,因为已经有一个名为 i 的变量。所以它(全局 i)可能会被重命名,让我们说 i1,然后调用将是:

   first call: print(x+10) -> (i1 + j + 10) -> (100 + 5 + 10) -> 115
   second call: print(x) -> print(i1 + j) -> 105 (Already evaluated - call by need)

关于 Q2:在动态作用域中,首先在本地函数中搜索变量,然后在调用本地函数的函数中搜索,然后在调用该函数的函数中搜索,依此类推,直到调用栈。

按姓名调用:

print (i1 + j + 10) -> print (100 + 5 +10 ) -> 115

第二个电话将是

print(x) -> print(i1 + j) -> (100 + 20) = 120 // Evaluate again - Call be name.

这个答案正确吗? (选项中不存在) 有什么我想念的吗? (可能是动态绑定?)

【问题讨论】:

  • 你发现了吗?我遇到了同样的问题!!

标签: c scope dynamic-binding dynamic-scope callbyname


【解决方案1】:

第一季度

OP 的答案是正确的 (D)。事实上,由于在P的执行过程中没有修改全局i,所以按需调用值调用没有区别。

这是一个确实有所作为的例子:

global int i = 100, j = 5;

void IncreaseTheGlobal() {
    i = i + 1;            // static scoping means this is the GLOBAL i!
    print(i);
}

void P(x) {
    int i = 10;
    IncreaseTheGlobal();  // 101 (increased global i)
    print(i);             //  10 (local i)
    print(x);             // 106 (x is evaluated; picks up increased global i)
    IncreaseTheGlobal();  // 102 (2nd time increased global i)
    print(x);             // 106 (x not re-evaluated; unaffected by 2nd increase)
}

main() {
    print(i);             // 100 (original global i)
    P(i + j);
    print(i);             // 102 (new global i)
}

正如 OP 已经指出的那样,第一次评估 x 时,它会获取全局 i 在那个特定时刻所具有的任何值。在初始评估之后,x 不再受以后对全局 i 的修改的影响。

第二季度

按名称调用 通常用于宏语言。那么为什么不使用最著名的宏语言:C 预处理器呢?

#include <stdio.h>

int i = 100, j = 5;

#define print(num)  printf("%d\n", num)

#define P(x) {     \
    int i = 10;    \
    print(x + 10); \
    i = 200;       \
    j = 20;        \
    print(x);      \
}

main() {
    P(i + j);
}

编译、运行、查看:25、220。

按名称调用使用简单的搜索和替换;在P 的正文中,将每次出现的x 替换为i + j

int i = 10; 
print(i + j + 10);    // 10 + 5 + 10 = 25
i = 200;
j = 20;
print(i + j);         // 200 + 20 = 220

换句话说,i + j 内部的ij 只是在评估x 时获取范围内的任何内容的现值。

所以正确答案是B,对吧?嗯,几乎……正确答案取决于print 的实现。 假设print 也实践按名称调用并且print 定义了自己的局部变量i,那么这将极大地改变结果。试试这个:

#define print(num)  { int i = 0; printf("%d\n", num); }

结果现在变为 15、20。

这可能是动态作用域不利于代码可维护性的最重要原因。函数 print 中的实现更改(即使是更改局部变量名称这样微不足道的事情)可能会破坏更高级别的函数。

【讨论】:

  • 对不起,我错误地认为第一季度是关于按值调用;我进行了编辑,以使我的答案涵盖按需调用主题。
  • 很好的解释!!我正在尽我所能地投票!!
  • 解释很清楚......我理解第二个因为“按名称调用通常用于宏语言”。
【解决方案2】:

在第二部分是按名称调用

i=200 行会更新本地 i

现在调用 print(x) 时,它将被 print(i+j)=>print(200+20)=>220

替换

【讨论】:

    【解决方案3】:

    对于第一季度:

    int i = 10; 
    print(x + 10); // print (i + j + 10); prints 10 + 5 + 10 = 25; local i gets used here
    
    i = 200; 
    j = 20; 
    
    print (x); // print (i + j); call by need ensures, no reevaluation and i + j is 15. 
    

    所以,答案是 C - 25, 15

    【讨论】:

    • 在动态范围内可能是正确的,但 Q1 涉及静态范围。即使x 是在函数P 的范围内评估的,i + j 也是在找到i + j 的(词法)范围内评估的,即函数main。在 Haskell 中尝试:let { i=100; p x = let i=10 in x+10; main = p (i+5) } in main 计算结果为 115,而不是 25。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-27
    • 1970-01-01
    相关资源
    最近更新 更多