Я думал, что С# имеет лексическое определение, но почему этот пример показывает поведение динамического охвата?
var x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
Выход 3. Я бы предположил, что это 2, в соответствии с https://web.archive.org/web/20170426121932/http://www.cs.cornell.edu/~clarkson/courses/csci4223/2013sp/lec/lec12.pdf
Ответы
Ответ 1
Есть тонкость в отношении лексического охвата, что PDF не объясняет полностью. Его пример фактически имеет две разные переменные с именем x
, он не переназначает значение первого x
(и действительно функциональные языки могут не допускать мутации).
С# имеет лексическую область - он смотрит вверх 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()));
Лямбда привязывается к переменной x
, которая существует внутри DefineIt
. Значение (x = 1
) в точке определения не имеет значения. Переменная позже будет установлена на x = 3
.
Но это явно не динамический масштаб, потому что x = 2
внутри InvokeIt
не используется.
Ответ 2
Этот вопрос был тема моего блога 20-го мая 2013 года. Спасибо за отличный вопрос!
Вы не понимаете, что означает "лексически охваченный". Укажите цитату из документа, с которым вы связались:
тело функции оценивается в старой динамической среде, которая существовала во время определения функции, а не в текущей среде при вызове функции.
Вот ваш код:
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
просматривается в среде e, которая существовала при создании f. Содержимое этой среды изменилось, но среда, к которой привязана f
, является той же средой. (Обратите внимание, что это фактически код, который генерирует компилятор С#! Когда вы используете локальную переменную в лямбда, компилятор генерирует специальный класс "среда" и превращает любое использование локального в использование поля.)
Позвольте мне привести пример того, как будет выглядеть мир, если бы С# был динамически охвачен. Рассмотрим следующее:
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));
}
}
Если С# был динамически охвачен, тогда это будет печатать "103", потому что оценка g
оценивает f
, а на языке с динамическим охватом оценка f
будет искать значение x
в текущей среде. В текущей среде x
равно 3. В среде, которая существовала при создании f
, x
равно 2. Снова значение x
в этой среде изменилось; как указывает ваш документ, среда является динамической средой. Но какая среда релевантна, не меняется.
Большинство языков в эти дни не динамически ограничены, но есть несколько. PostScript, например, язык, который работает на принтерах, динамически ограничен.