В ближайшем будущем в языке Java появятся новые фичи, над которыми сейчас идет работа в рамках проектов Valhalla, Panama и Loom. Расширение языка — дело непростое, тем более — языка, в котором акцент делается на обратную совместимость; поэтому для того, чтобы их интеграция в Java прошла органично, архитекторам языка приходится решать накопившиеся фундаментальные вопросы.

Вчера (8 января) Брайан Гетц, работающий в Oracle на должности Java Language Architect, опубликовал в рассылке Project Amber письмо «Нам нужно больше ключевых слов, капитан!», в котором предложил способ решения проблемы добавления в язык новых ключевых слов. Вследствие чего в языке могут появиться такие ключевые слова, как non-null, non-final, eventually-final и this-return (полный список ждет вас под катом в конце поста).

Поскольку в прошлом эта проблема возникала в языке нечасто, обычно над ней особо не задумывались и «старались как можно быстрее свалить с шеи»; из-за недостатков уже существующих подходов в будущем их применение будет проблематичным, и в связи с этим было решено сработать на упреждение. Предлагаемое решение: попытаться расширить набор лексических форм, которые можно использовать в качестве ключевых слов: разрешить ключевые слова, разделенные дефисом, в составе которых будет использоваться одно (или более) уже существующее ключевое слово или зарезервированный идентификатор.

Важное замечание: Брайн отмечает, что новые ключевые слова приведены исключительно в качестве иллюстративного примера, и что заострять на них свое внимание не стоит. Однако, очевидно, что перед нами продуманная демонстрация того, как синтаксис языка может измениться в будущем — в письме автор упоминает, что эта идея поможет добавить в Java многие желаемые недостающие языковые конструкции.

«Старые» методы


Как известно, на сегодняшний день в языке Java существует 50 ключевых слов (keywords), которые запрещено использовать в качестве идентификаторов переменных. Их полный список приведен в спецификации языка JLS в пункте 3.9. Список этот не сильно изменился с первой версии языка — добавились разве что assert в 4 версии, enum в 5 и _ в 9. Помимо них, есть еще «зарезервированные идентификаторы» — это true, false и null — которые ведут себя схожим с ключевыми словами образом.

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

  • Принудительное отчуждение собственности: берем слова, которые ранее были идентификаторами, и превращаем их в ключевые слова (пример — assert).
  • Утилизация: существующее ключевое слова начинает использоваться таким образом, которым его никогда не планировали использовать (пример — использование default для значений аннотации или методов по умолчанию).
  • Обойтись без него: найти способ использовать синтаксис, который не требует нового ключевого слова — например, использовать interface для аннотаций вместо annotation — или вовсе отказаться от фичи.
  • Создание видимости: создать иллюзию ключевых слов, зависящих от контекста, с помощью героических лингвистических достижений (restricted keywords, reserved type names).

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

Добавление новых ключевых слов


Против того, чтобы «просто» взять и добавить новые слова, есть следующие аргументы.

  • Чем удобнее и популярнее будет выбранное ключевое слово, тем чаще оно будет попадаться в исходном коде программ, что добавит в язык несовместимость (например, когда в Java SE 1.4 появилось слово assert, перестали работать все тестовые фреймворки).
  • Стоимость устранения подобной несовместимости кода разработчиком будет сильно варьироваться от небольшой (переименование локальной переменной) до фатальной (когда произойдет инвалидация метода интерфейса или публичного типа).
  • Слова, которые скорее всего захочется использовать разработчикам языка, являются популярными идентификаторами (например, value, var, или method);
  • Если выбирать слова, которые редко используются в исходном коде и с которыми будет меньше коллизий, то придется использовать конструкции вроде usually_but_not_always_final, которых в языке естественно хотелось бы избежать.
  • Если все-таки прибегнуть к выбору редко используемых слов, то пользоваться этим методом слишком часто не выйдет — ломать совместимость нехорошо, а более-менее удачных сочетаний не так уж и много.

Повторное использование «старых» ключевых слов


Насчет того, чтобы «просто» продолжать жить с теми словами, есть свои соображения.

  • Прецеденты повторного использования ключевых слов в разных контекстах встречаются во многих языках программирования (пример из Java — это использование ((ab)use) final для обозначений «не изменяемый», «не переопределяемый» и «не расширяемый»).
  • Иногда такой подход имеет смысл и приходит сам по себе, но обычно он не в приоритете.
  • Со временем набор требований к набору ключевых слов расширяется, и дело может дойти до смешного — никто не захочет использовать в своем коде null final.
  • Если последнее показалось вам преувеличением — то учтите, что во время работы над JEP 325 на полном серьезе предлагали использовать конструкцию new switch для того, чтобы описать switch с отличной от принятой семантикой — если продолжать в таком же духе, лет через десять можем дойти и до new new switch.

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

Контекстные ключевые слова


Контекстные ключевые слова, которые используются для предоставления конкретного значения в коде, но при этом не являются зарезервированными словами (используются в C#) на первый взгляд кажутся той самой «волшебной палочкой», но здесь Брайан излагает собственный взгляд на их использование, основанный на практике (например, реализации var в Java 10, которое является не ключевым словом, а reserved type name). В обмен на иллюзию добавления новых ключевых слов без необходимости «ломать» существующие программы, мы получаем возросшую сложность и искажения в языке.

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

От этого можно было бы отмахнуться — дескать, это проблема не пользователей языка, а его создателей и тех, кто пишет компиляторы и IDE. Но на самом деле в итоге страдают все. Для IDE это может представлять существенную проблему: потребуется больше информации на входе для того, чтобы угадать, что вводит разработчик — контекстное ключевое слово или идентификатор. В итоге пользователи получают ухудшение работы подсветки синтаксиса кода, автодополнения и возможностей проведение рефакторинга.

Использовать это средство можно, но делать это следует с осмотрительностью.

Искажение языка


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

Для разработчиков на Java написание interface вместо annotation сегодня является привычным делом, но все согласятся, что использование понятного термина annotation вместо комбинации @ и «старого» ключевого слова было бы куда логичнее.

Другой пример: набор доступных модификаторов (public, private, static, final, и т.д.) нельзя назвать полным — мы никак не можем сказать not final или not static. В свою очередь, это означает, что нельзя создать фичи, в которых переменные или классы являются по умолчанию final, или члены являются по умолчанию static, поскольку не существует способа указать на то, что мы хотели бы отказаться от этого модификатора.

Данная проблема не так очевидна для тех, кто пользуется языком — но авторы самого языка из-за своего стремления развивать его дальше постоянно с ней сталкиваются, и расплачиваться за подобные решения (когда явно, когда неявно) приходится в итоге уже нам всем.

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

Предложенное решение


В экспериментальных возможностях языка для предварительного обозначения новых ключевых слов используется синтаксис, при котором сами новые ключевые слова предваряются двумя символами подчеркивания (например, в прототипе Project Valhalla это __ByValue). Причина такого решения понятна — нужно указать на то, что это временная замена, для которой в дальнейшем нужно будет принять решение насчет финального синтаксиса, и при этом можно с легкостью избежать коллизий с существующим кодом. Можно было бы предложить использовать для новых ключевых слов подобный формат — начинать их с одного или двух подчеркиваний — но это решение нельзя назвать красивым, ведь в таком случае на выходе мы получим путаницу из обычных и новых ключевых слов.

Поэтому предлагается использовать ключевые слова, построенные с использованием дефиса, в составе которых будет использоваться одно или более «старых» ключевых слов или зарезервированных идентификаторов.

В отличие от restricted keywords, такой подход создаст гораздо меньше проблем для парсинга, поскольку (далее — пример) not-null нельзя спутать с выражением вычитания, а лексер всегда сможет определить, представляет ли собой a-b три токена или один. Благодаря этому нам открываются новые широкие возможности для создания ключевых слов, которые с гораздо меньшей вероятностью будут конфликтовать с уже существующим исходным кодом или между собой. Вдобавок ко всему, у них с куда большей вероятностью будут осмысленные имена, поскольку многое из того, что создателям языка хочется добавить в Java, базируется на уже существующих в ней языковых конструкциях — например, non-null.

В качестве примеров новых ключевых слов приводятся вероятные кандидаты на место новых ключевых слов (напоминаю, что по утверждению автора, на данный момент этот список носит чисто иллюстративный характер):

non-null;
non-final;
package-private (модификатор уровня доступа к членам класса по умолчанию, который в данный момент никак не обозначается);
public-read (публично читаемый, приватно записываемый);
null-checked;
type-static (концепт, необходимый для Valhalla; обозначает статичность по отношению к конкретной специализации класса, а не самого класса);
default-value;
eventually-final (то, что сейчас предполагается делать при помощи аннотации Stable),
semi-final (в качестве альтернативы sealed);
exhaustive-switch;
enum-class, annotation-class, record-class (авторы языка могли бы использовать данные ключевые слова как альтернативу enum и interface, если бы у них была такая возможность);
this-class (для описания литерала класса для текущего класса);
this-return (часто просят добавить способ пометить сеттер/метод-билдер в качестве возвращающего свой получатель).

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

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

Комментарии (23)


  1. DrPass
    10.01.2019 00:16
    +1

    Честно говоря, не понимаю этого подхода. Почему бы не ввести у компилятора параметр, который бы просто определял версию языка для конкретного проекта? Чтобы старые проекты можно было продолжать собирать со старым диалектом, а новые (равно как и старые после рефакторинга) можно было переводить на новый диалект?
    Ведь у разработчиков же нет такой самоцели, чтобы после появления нового диалекта языка сразу весь старый код на нем начал собираться с поддержкой новый фич.


    1. firk
      10.01.2019 00:27

      Чтобы старые проекты можно было продолжать собирать со старым диалектом

      После такого всем лень будет поддерживать эту опцию и в итоге она сломается и сквозная совместимость будет утеряна. Это если диалект в общем случае.


      А вот если речь идёт про только поддержку ключевых слов то наверно можно — ведь там всего лишь надо отключить распознавание токена как ключевого слова.


    1. rezdm
      10.01.2019 00:30

    1. DouTro
      10.01.2019 01:48

      А как будет решаться проблема если вы подключаете библиотеку которая написана на старой Java, а сами пишете на новой?


      1. DrPass
        10.01.2019 03:57

        А как будет решаться проблема если вы подключаете библиотеку которая написана на старой Java, а сами пишете на новой?

        Точно так же, как все остальные программисты решают проблему использования легаси-компонент в новом коде. Поискать новую версию, поискать аналог, попросить автора исправить, или сделать это самому, если доступны исходники. С этим разработчики сталкиваются в своей практике регулярно, ничего особо страшного нет, кроме каких-то редких единичных случаев, когда поломался интерфейс какой-то старой, но Очень Необходимой Библиотеки, у которой нет ни исходников, ни аналогов, и о необходимости использования которой стало понятно вот сейчас, когда уже написали полпроекта на новом диалекте, а не на этапе его проектирования.


      1. andreymal
        10.01.2019 04:39

        Для сравнения — в Rust для каждой библиотеки указывается своя редакция языка (2015 или 2018). А если вдруг старая библиотека использует в идентификаторах новые ключевые слова, то даже синтаксис-костыль вида r#async выдумали, чтобы можно было к ним обратиться


      1. mayorovp
        10.01.2019 09:33

        Так библиотеки-то подключаются в уже скомпилированном виде (.jar-архив или набор .class-файлов)


        1. AnarchyMob
          10.01.2019 17:40

          Так имя метода или публичное поле или имя класса в подключенной библиотеке может быть ключевым словом в новой версии языка…


          1. Mingun
            11.01.2019 17:49

            Ну, такая проблема на самом деле уже есть — имя метода/поля, это просто строка. Можно даже из пробелов, чем пользуются всякие обфускаторы.


            1. AnarchyMob
              12.01.2019 02:02

              Я вообще .net'чик. В "спеке" java я не нашел ничего про пробелы. Возможно на уровне JVM это так, но на уровне source code нет. В CLR, к примеру, используются идентификаторы с угловыми скобками <>, но в source code это не допускается.


    1. Gorthauer87
      10.01.2019 07:28

      В rust так сделали недавно. Посмотрим к чему это приведет.


  1. ExplosiveZ
    10.01.2019 01:32

    Как-то оно слишком сложно выглядит. Сложно просто взять и ответить, ведь с этим жить потом.


  1. NeoCode
    10.01.2019 07:39

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


    1. maxzh83
      10.01.2019 10:30

      В Java нельзя просто взять и сломать обратную совместимость.


      1. Mingun
        10.01.2019 19:45

        Уже сломали один раз, и еще смогут


        1. zirix
          10.01.2019 20:06

          Вы про Jigsaw? Если да, то ничего не сломано. Jigsaw включается только при наличии module-info.java в корне проекта (наличие module-info.java в библиотеках игнорируется, если его нет в корневом проекте).


          1. Mingun
            10.01.2019 21:54

            В статье упоминается, как минимум, assert и enum.


  1. Revertis
    10.01.2019 12:15

    пометить сеттер/метод-билдер в качестве возвращающего свой получатель
    Замечательный перевод, не помеченный как перевод :)


  1. werklop
    10.01.2019 14:35

    За символ подчеркивания в качестве ключевого слова нужно отстрел вести таким придумщикам, а тем, кто их использует в именах переменных(намеренно злоупотребляя, см. коды программ на C/C++ с двумя и более подряд символами с обоих сторон слова), руки отрубать, нещадно!


    1. striver
      10.01.2019 14:44

      Ваши варианты названий переменных аzs00000, aaab005350? — и ниже в комментах полная расшифровка?


  1. werklop
    11.01.2019 14:44

    striver
    Роберт Мартин: Чистый код. Создание, анализ и рефакторинг. Глава 2. Содержательные имена.
    Перечитайте на досуге


    1. striver
      11.01.2019 14:51

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


  1. AnarchyMob
    11.01.2019 17:09

    Можно сделать так что ключевое слово в обратных кавычках, к примеру, `private` будет считаться идентификатором…