I thought C# has lexical scoping, but why this example shows dynamic scoping behavior?
There's a subtlety concerning lexical scoping that PDF doesn't fully explain. Its example actually has two different variables named x
, it does not reassign the value of the first x
(and indeed functional languages may not allow mutation).
C# is lexically scoped -- it looks up x
at the point of definition of the lambda, not when the delegate is invoked. But: x
resolves to a variable, not a value, and it reads the variable's value at the time of invocation.
Here is a more complete example:
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()));
The lambda binds to the x
variable that exists inside DefineIt
. The value (x = 1
) at the point of definition is irrelevant. The variable is later set to x = 3
.
But it is clearly not dynamic scope either, because the x = 2
inside InvokeIt
is not used.
This question was the subject of my blog on the 20th of May 2013. Thanks for the great question!
You're misunderstanding what "lexically scoped" means. Let's quote from the document you linked to:
the body of a function is evaluated in the old dynamic environment that existed at the time the function was defined, not the current environment when the function is called.
Here's your code:
int x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
Now, what is "the dynamic environment that exists at the time the function was defined"? Think about an "environment" as a class. That class contains a mutable field for every variable. So this is the same as:
Environment e = new Environment();
e.x = 1;
Func<int,int> f = y => e.x + y;
e.x = 2;
Console.WriteLine(f(1));
When f
is evaluated, x
is looked up in the environment e that existed when f was created. The contents of that environment have changed, but the environment that f
is bound to is the same environment. (Note that this is actually the code that the C# compiler generates! When you use a local variable in a lambda, the compiler generates a special "environment" class and turns every usage of the local into a usage of a field.)
Let me give you an example of what the world would look like if C# was dynamically scoped. Consider the following:
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));
}
}
If C# was dynamically scoped then this would print "103" because evaluating g
evaluates f
, and in a dynamically scoped language, evaluating f
would look up the value of x
in the current environment. In the current environment, x
is 3. In the environment that existed when f
was created, x
is 2. Again, the value of x
in that environment has changed; as your document points out, the environment is a dynamic environment. But which environment is relevant doesn't change.
Most languages these days are not dynamically scoped, but there are a few. PostScript, for example -- the language that runs on printers -- is dynamically scoped.