Об авторе: Соутаро является ведущим разработчиком Ruby в Square, работающим над Steep и статической типизацией; вместе с Матцем и другими разработчиками ядра он работает над спецификацией RBS, которая будет поставляться с Ruby 3.
Мы с радостью анонсируем новый язык сигнатуры типов для Ruby 3 — RBS. Одной из давно заявленных целей Ruby 3 было добавление инструментов проверки типов. После продолжительных обсуждений с Матцем и командой разработчиков Ruby мы решили предпринять инкрементный шаг, добавив в Ruby 3 язык сигнатуры типов RBS, который будет поставляться вместе с сигнатурами для stdlib. Инструменты командной строки RBS также будут поставляться вместе с Ruby 3, так что вы сможете генерировать сигнатуры для собственного кода Ruby.
Справочная информация
Статическая или динамическая типизация — эта дихотомия является давней проблемой языков программирования. Языки со статической типизацией лучше подходят для больших проектов, при этом зачастую они менее гибкие. Языки с динамической типизацией обеспечивают быструю разработку, но масштабировать команды и базы кода с ними может быть затруднительно.
Разработчики языков программирования, разумеется, знают об этой проблеме и пытаются её компенсировать, используя возможности других языков. В C# есть функция dynamic
, переносящая проверку типов со стадии компиляции на этап выполнения: присвоение и чтение любого типа разрешено в процессе компиляции, но может вызвать ошибку при выполнении программы для обеспечения безопасности. Это почти эквивалентно языкам с динамической типизацией! Что насчёт обратной ситуации? Мы видим, что такие языки с динамической типизацией, как PHP и Python, реализуют опции проверки типов. У нас также есть типизированные диалекты языков с динамической типизацией, используемых в производственной среде, например TypeScript.
Ещё четыре года назад Матц объявил, что Ruby 3 будет поддерживать статическую проверку типов. После просмотра нескольких средств проверки типов, разработанных сообществом, команда разработчиков Ruby решила заложить основу для последующего развития сообществом. Ruby 3 будет поставляться со способностью писать сигнатуры типов для программ на Ruby, а также со встроенными сигнатурами типов для стандартных библиотек Ruby. Язык сигнатуры стандартного типа сделает определение типов в коде Ruby переносимым между средствами проверки типов и вдохновит сообщество на написание типов для собственных гемов и приложений.
Мы назвали язык и библиотеку RBS.
Как выглядит RBS?
Сигнатуры пишутся в файлах с расширением .rbs
, которые отличаются от кода Ruby. В некотором роде файлы .rbs
похожи на файлы .d.ts
в TypeScript или файлы .h
в C/C++/ObjC. Преимущество отдельных файлов в том, что для запуска проверки типа не нужно изменять код Ruby. Вы можете безопасно подписаться на проверку типа, не внося ни единого изменения в рабочий процесс.
Сигнатуры типов для классов Ruby в RBS будут выглядеть так.
# sig/merchant.rbs
class Merchant
attr_reader token: String
attr_reader name: String
attr_reader employees: Array[Employee]
def initialize: (token: String, name: String) -> void
def each_employee: () { (Employee) -> void } -> void
| () -> Enumerator[Employee, void]
end
Файл merchant.rbs
определяет класс Merchant
, что помогает читателю понять обзор класса.
У класса есть три атрибута: token
, name
и employees
. Типом token
и name
является String
. RBS также поддерживает обобщённые классы, такие как Array
. Например, тип атрибута employees
— Array
класса Employee
.
RBS также описывает определённые в классе методы и их типы. Класс определяет методы initialize
и each_employee
. В качестве именованных аргументов для метода initialize
требуются token
и name
. Метод each_employee
принимает блок или возвращает экземпляр Enumerator
.
RBS — это язык для описания структуры программ Ruby. Он даёт разработчикам обзор кода и представление о том, какие классы и объекты в нём определены. Самым большим преимуществом является то, что определение типа может быть проверено на соответствие как на стадии разработки, так и в режиме выполнения!
Ключевые фичи RBS
Разработка системы типирования для такого языка с динамической типизацией, как Ruby, отличается от ординальных языков со статической типизацией. В мире уже существует огромное количество кода на Ruby, и система типов должна поддерживать как можно больший объём этого кода.
Это заставляет разработчиков системы типирования идти на компромиссы в отношении сложности и корректности для совместимости с существующим кодом. Возможно, нам придётся вводить функцию проверки типов для поддержки шаблона в существующем коде Ruby, который в противном случае может быть ошибочным. Однако добавление функции делает систему типов громоздкой и сложной для понимания. Поэтому мы сосредоточились на наиболее важных шаблонах кода, чтобы минимизировать её сложность.
Продемонстрируем две важные характеристики кода Ruby и возможности присвоения типа для них.
Утиная типизация
Утиная типизация — популярный в среде рубистов стиль программирования, который предполагает, что объект будет реагировать на определённый набор методов. Преимущество утиной типизации в её гибкости. Она не требует наследования, миксинов или объявлений реализации. Если у объекта есть конкретный метод, он работает. Проблема в том, что это предположение скрыто в коде, что делает его визуально сложнее.
Чтобы учесть утиную типизацию, мы ввели типы интерфейса, которые представляют собой наборы методов, независимых от конкретных классов и модулей.
Если мы хотим определить метод, требующий конкретный набор методов, мы можем написать его с помощью типов интерфейса.
interface _Appendable
# Требует оператор `<<`, который принимает объект `String`.
def <<: (String) -> void
end
# Передача `Array[String]` или `IO` работает.
# Передача `TrueClass` или `Integer` не работает.
def append: (_Appendable) -> String
Этот код лучше традиционной утиной типизации, поскольку определяет ожидаемую реализацию явного интерфейса класса или модуля и предоставляет подсказки для документации и расширений для редактора кода. Таким образом, ранее неявный интерфейс представлен в качестве надёжной практичной документации.
Неоднородность
Неоднородность — ещё один шаблон, позволяющий выражению иметь различные типы значений. Он популярен в Ruby и применяется:
- когда вы определяете локальную переменную, хранящую экземпляры двух разных классов;
- когда вы пишете гетерогенную коллекцию;
- когда вы возвращаете из метода два разных типа значений.
Чтобы приспособиться к этому, RBS допускает типы объединения и перегрузку методов.
class Comment
# Комментарий могут создавать User или Bot
def author: () -> (User | Bot)
# Две перегрузки с блоками/без них
def each_reply: () -> Enumerator[Comment, void]
| { (Comment) -> void } -> void
...
end
Типы объединения и перегрузка методов часто встречаются в коде Ruby и стандартных библиотеках.
Программирование на Ruby с использованием типов
Мы предоставляем язык для написания типов. Так что же мы можем делать с файлами RBS?
Ниже список основных преимуществ наличия типов. Мы можем писать типы в файлах RBS, чьи инструменты помогают с:
- поиском дополнительных ошибок: мы сможем обнаруживать неопределённый вызов метода, неопределённую константную ссылку и многое другое, что может пропустить язык с динамической типизацией;
- проверкой на нулевое значение (Nil safety): у средств проверки типов, основанных на RBS, есть концепция опциональных типов, допускающих значение
nil
. Средство проверки типов может проверить возможность выражения принимать значениеnil
и безопасно обнаруживать неопределённыйmethod
для nil:NilClass. - улучшением интеграции IDE: парсинг файлов RBS даёт IDE лучшее понимание кода Ruby; автозавершение имён методов выполняется быстрее; выдача отчёта об ошибках по ходу работы позволяет выявить больше проблем. Рефакторинг становится более надёжным!
- управлением утиной типизацией: типы интерфейсов можно использовать для утиной типизации, что помогает пользователям API более чётко понимать, что они могут делать; это более безопасная версия утиной типизации.
Разумеется ничто не бывает просто так. Как мы создаём инструменты для RBS, чтобы облегчить разработчикам работу с ним?
Мы разработали средства статической проверки типов поверх RBS. Steep — это средство статической проверки типов, реализованное в Ruby и основанное на RBS. Sorbet — это средство статической проверки типов, имеющее собственный язык определения типов, RBI, но в будущем планируется поддержка RBS.
Кроме того, мы разрабатываем и работаем над дополнительными инструментами для расширения набора RBS. Средство проверки типов выполнения RBS — это один из проектов Ruby Google Summer of Code, использующий сигнатуры типов RBS для реализации проверки типов выполнения. type-profiler — это исследовательский проект для создания файлов RBS из исходного кода Ruby на основе метода анализа программ, называемого Abstract Interpretation. Также существует проект поддержки для Rails.
Sorbet и RBS
Sorbet — это наиболее широко используемое на сегодняшний день средство статической проверки типов для Ruby. RBS не пытается вытеснить Sorbet и его формат сигнатуры типа RBI. Матц и команда разработчиков Ruby тесно сотрудничают с командой Sorbet и высоко ценят их усилия и те улучшения, которые команда Sorbet вносит в разработку.
Цель RBS в том, чтобы предоставить основу для описания информации о типах программ Ruby. Такие средства статической проверки типов, как Sorbet или Steep, могут использовать определение типов, написанное в RBS. Для упрощения взаимодействия гем RBS поставляется с переводчиком с RBI на RBS, а переводчик с RBS на RBI уже разрабатывается.
Заключение
В этом посте я представил вам RBS — новый проект Ruby 3 для работы с типами. Я объяснил, что можно написать, используя RBS, ключевые идеи его дизайна, а также его преимущества и инструменты, поставляющиеся с ним. Вы пишете определения типов для кода Ruby, а наши инструменты анализируют ваш код. Мы знаем, что не все рубисты перейдут на типизированный Ruby, но мы уверены, что его стоит попробовать!
Читайте также:
- Создаем функции поиска и фильтрации в Ruby on Rails
- Как и почему я перешёл с Ruby на Python
- Ruby on Rails меняет всё
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Soutaro Matsumoto: The State of Ruby 3 Typing