Share via


Неоднозначность необязательных скобок. Часть 3

В прошлый раз мы обсуждали, почему конкретный синтаксический сахар был отвергнут командой проектировщиков языка: потому что он приводил к неприятной неоднозначности в процессе семантического анализа без каких-либо преимуществ. Давайте продолжим наш диалог…

Как вы определили именно эту неоднозначность?

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

Разумно. Но в общем случае, обсуждая некоторую новую возможность как вы, в общем случае, определяете, приведет она к неоднозначности или нет? Руками? Путем формальных проверок? Путем автоматического анализа? Как?

Всеми тремя способами. В основном мы смотрим спецификацию и размышляем, как я делал в прошлый раз с предложенной возможностью.

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

x = frob 123 + 456

frob здесь выступает как оператор new или ++ – он идет перед некоторым выражением.

Прежде всего, мы выясняем необходимый приоритет выполнения, ассоциативность и т.п., чтобы мы знали, что этот код означает frob(123+456) или (frob 123)+456.

Потом мы начинаем задавать вопросы типа «а что, если в программе уже есть тип, поле, свойство, событие, метод, константа или локальная переменная с именем frob?» Это сразу же приведет нас к случаю вида:

frob x = 123;

Значит ли это «выполнить операцию frob на результате x = 10, или создать переменную типа frob с именем x и присвоить ей 10?» Или давайте рассмотрим следующий случай:

G(frob + x);

Означает ли это «выполнить операцию frob на результате унарного оператора плюс над переменной x» или «добавить выражение frob к x»?

И т.д. Для разрешения этих неоднозначностей нам нужны эвристики. В конце концов, мы уже видели подобные проблемы ранее. Когда вы пишите var x = 10; это неоднозначно; это может означать «выведи тип выражения x» или это может означать «объявить переменную x типа var». Поэтому у нас есть эвристика: вначале мы пытаемся найти тип с именем “var” и только если такого типа нет, мы выводим тип выражения x.

Или мы можем изменить синтаксис таким образом, чтобы устранить неоднозначность. При проектировании языка C# 2.0 у нас была такая проблема:

yield(x);

Означает ли это «вернуть x из блока итератора» или «вызвать метод yield с аргументом x?». Изменив это на:

yield return(x);

неоднозначность исчезла; теперь это не может означать «вызывать метод yield».

В случае необязательных скобок в списке аргументов инициализатора объекта достаточно просто проанализировать появится неоднозначность или нет. Почему? Потому что аргументы инициализатора содержат фигурные скобки после имени конструктора. Количество ситуаций, когда допустимо использовать что-либо, что начинается с левой фигурной скобки очень мало. В основном это контекст оператора (statement context), лямбда-выражение, инициализатор массивов и все. Достаточно просто обдумать все эти варианты и показать, что здесь не будет неоднозначности. Удостовериться, что работа IDE будет оставаться эффективной несколько сложнее (поскольку вам нужно работать с частично завершенными программами, которые могут содержать фигурные скобки в непонятных местах), но тоже возможно без особых проблем.

Такая возня со спецификацией в большинстве случаев является достаточной. Для особенно хитрых возможностей мы используем инструменты потяжелее. Например, при проектировании LINQ, один из разработчиков компилятора и один из разработчиков IDE, оба с хорошими знаниями в теории языков программирования, вместе создали анализатор, который мог анализировать грамматики в поисках неоднозначностей и затем скормили ему предложенную грамматику C# для выражений запросов (query comprehensions); это выявило множество случаев, когда запросы были неоднозначными.

Если особенность действительно достаточно коварная и нам нужно получить профессиональное мнение по этому поводу, мы обращаемся к супергениям. Например, когда мы реализовывали новаторский вывод типов в лямбда-выражениях в C# 3.0 мы написали наше предложение и отправили его в Microsoft Research в Cambridge, где команда языков программирования разработала формальное доказательство того, что наше предложение о выводе типов было теоретически возможным.

Оригинал статьи