我认为 C# 具有词法范围,但为什么此示例显示动态范围行为
本文关键字:范围 显示 动态 为什么 词法 | 更新日期: 2023-09-27 18:10:58
var x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
输出为 3。根据 https://web.archive.org/web/20170426121932/http://www.cs.cornell.edu/~clarkson/courses/csci4223/2013sp/lec/lec12.pdf,我假设它是 2
关于词汇范围有一个微妙之处,PDF没有完全解释。 它的例子实际上有两个不同的变量命名为x
,它不会重新分配第一个x
的值(实际上函数式语言可能不允许突变(。
C# 是词法范围的 - 它在 lambda 的定义点查找x
,而不是在调用委托时查找。 但是:x
解析为变量,而不是值,并在调用时读取变量的值。
下面是一个更完整的示例:
int InvokeIt( Func<int, int> f )
{
int x = 2;
return f(1);
}
Func<int, int> DefineIt()
{
int x = 1;
Func<int, int> d = (y => x + y);
x = 3; // <-- the PDF never does this
return d;
}
Console.WriteLine(InvokeIt(DefineIt()));
lambda 绑定到存在于 DefineIt
中的 x
变量。 定义点的值(x = 1
(是无关紧要的。 该变量稍后设置为 x = 3
。
但它显然也不是动态范围,因为没有使用InvokeIt
内部的x = 2
。
这个问题是我在2013年5月20日博客的主题。谢谢你的好问题!
您误解了"词汇范围"的含义。让我们引用您链接到的文档:
函数的主体是在定义函数时存在的旧动态环境中计算的,而不是在调用函数时的当前环境中计算的。
这是您的代码:
int x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
现在,什么是"定义函数时存在的动态环境"? 将"环境"视为一个类。该类包含每个变量的可变字段。所以这和:
Environment e = new Environment();
e.x = 1;
Func<int,int> f = y => e.x + y;
e.x = 2;
Console.WriteLine(f(1));
评估 f
时,x
将在创建 f 时存在的环境 e 中查找。 该环境的内容已更改,但f
绑定到的环境是同一环境。(请注意,这实际上是 C# 编译器生成的代码!当您在 lambda 中使用局部变量时,编译器会生成一个特殊的"环境"类,并将局部变量的每个用法转换为字段的用法。
让我举一个示例,说明如果 C# 是动态范围的,世界会是什么样子。请考虑以下事项:
class P
{
static void M()
{
int x = 1;
Func<int, int> f = y => x + y;
x = 2;
N(f);
}
static void N(Func<int, int> g)
{
int x = 3;
Console.WriteLine(g(100));
}
}
如果 C# 是动态作用域的,那么这将打印"103",因为计算g
评估f
,而在动态作用域的语言中,计算f
将在当前环境中查找x
的值。在当前环境中,x
为 3。在创建 f
时存在的环境中,x
为 2。同样,x
在这种环境中的价值发生了变化;正如您的文档所指出的,环境是一个动态环境。但是哪个环境是相关的不会改变。
如今,大多数语言都不是动态范围的,但有一些。例如,PostScript(在打印机上运行的语言(是动态范围的。