Альфа-преобразование в лямбду - PullRequest
1 голос
/ 25 января 2012

Почему C # не поддерживает альфа-преобразование?

int n = 3;
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.Out.WriteLine("N value = " + n);

Выход:

В этой области не может быть объявлена ​​локальная переменная с именем 'n', посколькудругое значение для «n», которое уже используется в «родительском или текущем» объеме для обозначения чего-то другого

Есть ли какая-то частичная причина, о которой я не знаю, потому что это звучит очень глупо?

Ответы [ 4 ]

11 голосов
/ 25 января 2012

Прежде всего, ваш тест на странность неверен.

Ответ на ваш вопрос состоит в том, чтобы опровергнуть предпосылку вашего вопроса. Это не имеет ничего общего с альфа-преобразованием.

И не имеет ничего общего с "лексическим ограничением", под которым, по-видимому, подразумевается нечто иное, чем мое понимание лексического определения объема. C # - это лексически ограниченный язык.

Теперь я хочу подчеркнуть, что недопустимо объявлять два локальных языка в C #, где один скрывает другой .Совершенно законно прятаться в других сферах;параметр типа может скрывать параметр внешнего типа (хотя делать это действительно, очень глупо; не делайте этого.) Поле может скрывать поле базового класса (хотя вы должны пометить скрытое поле как «новое», чтобы подчеркнуть этот факт.) Местный житель может скрыть метод.И так далее.

Но локальный может не скрывать другого локального, потому что (1) это создает фермы ошибок и (2) нарушает более общее правило об использовании простых имен.

Это правило об именах - вот интересное правило. Вы бы получили похожую ошибку, если бы сделали это:

class C
{
    int n;
    void M()
    {
        Console.WriteLine(n); // n means this.n
        Func<double, double> f = n=>n; // n means the formal parameter
    }
}

Ошибка, которую вы получаете, состоит в том, что вы нарушаете правило C #, которое простое имя должно иметь непротиворечивое значение во всей локальной области, в которой оно впервые используется.

Программы, где 'n' означает одно в одной строке, а что-то совершенно другое в следующей, сбивают с толку иподвержен ошибкам и поэтому недопустим.

Если вы хотите сделать это, тогда два значения 'n' должны быть в непересекающихся областях:

class C
{
    int n;
    void M()
    {
        {
          Console.WriteLine(n); // n means this.n
        }
        Func<double, double> f = n=>n; // n means the formal parameter
    }
}

Это было бы законно, потому что теперь два использования n находятся в непересекающихся областях.

Проблема не имеет ничего общего с альфа-преобразованием.C # выполняет альфа-преобразование очень хорошо, когда это необходимо.

И это , потому что C # имеет лексическую область видимости, что компилятор может определить, что вы нарушаете это правило. Этоне является доказательством того, что в C # отсутствует лексическая область видимости;это свидетельство того, что имеет лексическую область видимости.

Подробнее об этом правиле см. в моей статье на эту тему:

http://blogs.msdn.com/b/ericlippert/archive/2009/11/02/simple-names-are-not-so-simple.aspx

1 голос
/ 25 января 2012

Это не совсем альфа-преобразование.

Проблема в том, что в C # нет правильной лексической области видимости, что подразумевает, что переменные могут быть затенены во внутренних областях.

0 голосов
/ 26 января 2012

Подходящим термином является «дублирование», так как в «C # не разрешает одной локальной переменной затенять другую».

Нет технических причин для ограничения. Затенение не вызывает проблем при проверке типов или генерации кода.

Я считаю, что мотивация этого ограничения является чисто эргономичной. Я ожидаю, что когда люди в Sun разрабатывали Java (обратите внимание: у Java тоже было это ограничение, и MS, вероятно, сказала, что это звучит разумно, и поместили его также в C #), они подумали, что затенение сбивает с толку программистов, которых они хотят украсть у Сообщества C и C ++. И они, вероятно, полагали, что Lispers, Smalltalkers и MLers, и тому подобное, либо будут жить с этим, либо в любом случае откажутся использовать такой низкоуровневый пешеходный язык.

И я вроде согласен с их оценкой эргономических вопросов. Когда вы модифицируете большой блок кода, вы можете случайно захватить ссылки на переменные, если добавите новую локальную привязку. Другими словами, текстовые правки программы не имеют должной лексической области действия.

0 голосов
/ 25 января 2012

Причина, по которой это происходит, заключается в том, что функции C # lamda не объявлены в области видимости.Они могут получить доступ к переменным внутри родительской области.Например, следующий код позволяет вашей лямбде получить доступ к переменной n:

int outerN = 3; 
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; 
int oddNumbers = numbers.Count(localN => localN % 2 == outerN); 
Console.Out.WriteLine("N value = " + outerN); 

Переменные внутри лямбда-функции следует рассматривать как находящиеся в той же области видимости, что и метод, в котором они объявлены.

С MSDN :

Лямбды могут ссылаться на внешние переменные, которые находятся в области видимости во вложенном методе или типе, в котором определяется лямбда.Переменные, которые собираются таким образом, сохраняются для использования в лямбда-выражении, даже если переменные иначе вышли бы из области видимости и были бы собраны мусором.Внешняя переменная должна быть определенно назначена, прежде чем она будет использована в лямбда-выражении.

Следующие правила применяются к области действия переменной в лямбда-выражениях:

  • Переменная, котораяЗахваченный не будет собираться мусором до тех пор, пока делегат, ссылающийся на него, не выйдет из области видимости.

  • Переменные, введенные в лямбда-выражении, не видны во внешнем методе.

  • Лямбда-выражение не может напрямую захватить параметр ref или out из метода вложения.

  • Оператор return в лямбда-выражении не вызывает возврата метода вложения.

  • Лямбда-выражение не может содержать оператор goto, оператор break или оператор continue, цель которого находится вне тела или в теле содержащейся анонимной функции.

...