Я думал, что С# имеет лексическое определение, но почему этот пример показывает поведение динамического охвата? - программирование
Подтвердить что ты не робот

Я думал, что С# имеет лексическое определение, но почему этот пример показывает поведение динамического охвата?

    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

4b9b3361

Ответ 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, например, язык, который работает на принтерах, динамически ограничен.