QR code

The End of Type Annotations

  • Translated by to

Аннотации типов - это то, что делает статически типизированные объектно-ориентированные языки, такие как Java, быстрее и безопаснее. Без аннотаций каждая переменная фактически сводилась бы к Object, что приводило бы к необходимости приведения типов во время выполнения. Код бы работал, но медленнее и с большим количеством ошибок времени выполнения. Некоторые аннотации типов могут быть выведены, а не явно указаны программистом. Хотя не все. В Java, например, ряд трудно разрешимых проблем мешают нам выводить типы всех объектов. Это не является фундаментальным ограничением ООП само по себе. Это результат компромиссов в дизайне Java и аналогичных языков. В идеальном объектно-ориентированном языке все типы переменных были бы выводимы.

Представьте простой метод на Java:

Два основания обосновывают использование аннотаций типов Book, City, Price и Delivery: компиляторам и программистам нужна помощь.

Во-первых, мы помогаем компилятору устранить некоторые динамические вызовы в пользу статических вызовов. Если Book - это класс, а не интерфейс, вызов book.price() может быть скомпилирован в переход к абсолютному адресу. Без информации о классе book, .price() сначала идет в виртуальную таблицу, находит адрес, а затем делает переход. Второй сценарий дороже. Аннотация типа, присоединенная к book, помогает избежать этого.

Во-вторых, мы помогаем себе писать безопасный код, избегая “Метод не найден” ошибок времени выполнения. Если book не аннотирован как Book, мы можем ошибочно передать Integer, имея в виду идентификатор книги в базе данных. На этапе компиляции это не привело бы к ошибкам. Позже, во время выполнения, мы получаем ошибку, когда .price() не найден в виртуальной таблице класса Integer.

Однако и компиляторы, и программисты могут улучшиться.

Иногда компилятор может самостоятельно определить тип переменной без явной аннотации. Например, этот код компилируется на Java, начиная с версии 10:

Аннотация типа, используемая в более ранних версиях Java, заменяется ключевым словом var.

В небольшом фрагменте кода, подобном этому, компилятор может вывести типы. Однако он может не справиться с параметрами book и city. В общем случае вывод типов недекларируем для программ на Java. Из-за обобщений, перегрузки методов, отражения и … сложности.

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

Все остальные барьеры, такие как обобщения, компилятор не может преодолеть:

Неважно, насколько старается компилятор, в общем случае на этот вопрос нет ответа.

Мы, программисты, можем помочь компилятору выводить типы.

Например, мы можем перестать использовать обобщения. Вместо List<Book> у нас может быть Library, а вместо Map<User, Phone> - PhoneBook. Легче вывести тип объекта, взятого из Library, чем из обобщенного List.

Мы также можем перестать использовать перегрузку методов. Вместо print(String x) и print(Integer x) мы можем создать printString(x) и printInteger(x). Типы параметров легче выводить в более специализированных методах.

Мы также можем перестать использовать рефлексию.

Программисты на Java могут быть не готовы к такому радикальному шагу. Однако если бы они были готовы, это помогло бы не только компилятору, но и самим им. Удаление аннотаций типа делает код короче и, из-за этого, более чистым. Поэтому в Java 10 был введен синтаксис var.

В приведенном выше коде имена переменных - существительные. В хорошо написанном коде существительные в качестве имен достаточно для однозначного определения переменных. Нет необходимости называть это cityOfDelivery или bookToDeliver. Просто book и city достаточно.

Мы также назвали переменные по их типам: book имеет тип Book и так далее. Просматривая имя переменной, мы можем определить ее тип. Аннотация типа Book выглядит как синтаксическое избыток. Она лишь затрудняет чтение кода, удлиняя его. Разумно ожидать, что вывод типов избавит наши программы от этой избыточности.

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

Языки типа Haskell и семейство ML доказывают, что полное вывод типов возможно. Однако они все еще требуют аннотаций для крайних случаев. Rust занимает промежуточную позицию. Он выводит типы локальных переменных, но требует явных аннотаций для публичных интерфейсов. Go, до недавнего времени, избегал обобщений, перегрузки операторов и глубокого отражения, что делало вывод типов простым. Однако он заставляет программистов аннотировать все публичные границы - сигнатуры функций, поля структур и интерфейсы.

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

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

Вот как бы выглядел фрагмент кода для цены книги в таком языке:

Мне кажется, что этот код намного более читаем, чем оригинальный код на Java.

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

Мы экспериментируем с этим подходом в EOLANG, языке, разработанном для максимизации вывода путем устранения функций, делающих его неразрешимым.

Translated by ChatGPT gpt-3.5-turbo/42 on 2025-08-17 at 15:51

sixnines availability badge  GitHub stars