Sorbet — это специализированный статический типизатор для языка программирования Ruby, созданный в компании Stripe с целью улучшения надежности и удобства работы с крупными кодовыми базами. На протяжении последних нескольких лет Sorbet прошёл путь от набора экспериментальных решений до зрелого инструмента, который активно применяется в индустрии разработки программного обеспечения. Рассмотрим историческое развитие, особенности синтаксиса типов и перспективы его эволюции. Исторические корни Sorbet связаны с внутренними потребностями компании Stripe. В середине 2010-х годов в компании работало около 300 инженеров, и масштаб и сложность кодовой базы серьезно выросли, что вызвало необходимость внедрения более строгого контроля типов в Ruby-коде.
При этом Ruby, будучи динамически типизированным языком, традиционно не имел встроенных средств статического анализа типов, что создало вызовы при работе с быстрорастущими проектами. До появления Sorbet в кодовой базе Stripe уже использовались различные DSL (Domain-Specific Language) для выполнения проверки типов в рантайме. Это были Ruby-библиотеки, которые позволяли объявлять интерфейсы и проверять корректность данных во время выполнения программы. Например, подобные подходы обеспечивали строгость при работе с моделями базы данных и интерфейсами, через динамическое подтверждение реализованных методов и предотвращение вызова неподдерживаемых функций. Основная проблема существующих решений заключалась в том, что они либо не обеспечивали эффективности статического анализа, либо приводили к чрезмерной нагрузке на процесс загрузки кода, что было неприемлемо для масштабной инфраструктуры Stripe.
Кроме того, не было универсального стандарта для описания типов, а поддержка таких языков, как RBS и альтернативных систем типизации, находилась в зачаточном состоянии. Команда Sorbet рассмотрела несколько подходов к внедрению системы типизации в Ruby. Одним из вариантов был подход TypeScript — создание отдельного синтаксиса, который компилируется и удаляется на этапе сборки. Однако этот метод несовместим с динамической природой Ruby и его инструментарием, где быстрое выполнение и интерактивная среда важнее. Внедрение обязательного этапа компиляции могло привести к серьезному снижению скорости разработки и потребовало бы масштабной перестройки инфраструктуры.
Другим вариантом была концепция заголовочных файлов с отдельным описанием типов вне тела кода, что схоже с RBS. Такой подход дает гибкость в синтаксисе и позволяет отделить логику типов от бизнес-логики приложения. Тем не менее, он не решал проблему необходимости явных утверждений типов непосредственно в теле функций и усложнял работу с динамическими аспектами языка. Некоторые разработчики выступали за использование комментариев в коде как место для аннотаций типов — подобно подходу JSDoc в JavaScript. Этот метод облегчает внедрение типизации без изменения синтаксиса языка, однако в Ruby важно не только статическое, но и динамическое (runtime) выполнение проверок типа, что делает комментарии недостаточными для полноценной реализации.
В конечном итоге Sorbet выбрал стратегию использования DSL, продолжая традицию, начатую внутри компании Stripe. Главным символом этой концепции стал метод sig, который служит для декларации типов параметров и возвращаемых значений методов. Такой синтаксис оказался достаточно удобен, чтобы реализовывать проверки как во время выполнения, так и статически, и при этом вписался в стандарты Ruby без необходимости модифицировать сам язык. Несмотря на успех DSL подхода, внешне синтаксис Sorbet иногда критикуют за излишнюю громоздкость и непривычность. Однако разработчики отмечают, что в языковом дизайне семантика всегда важнее синтаксиса: корректное понимание и интерпретация типов критична для обеспечения надежности и предсказуемого поведения программного кода.
Архитектура Sorbet предусматривает, что типы представлены в виде выражений Ruby, что создает определенные ограничения. Например, использование «|» для объединения типов или «&» для пересечения уже занято методами Ruby, а оператор «[]» нельзя свободно переопределять, особенно учитывая особенности стандартной библиотеки языка. Это требует обходных решений и усложняет синтаксис. Ранее Sorbet испытывал проблемы с порядком загрузки кода, так называемыми «forward references», когда используемые в типах классы еще не были определены. Для решения этой проблемы был введен механизм ленивой оценки блоков с типовыми объявлениями, который откладывает проверку типа до момента вызова метода.
Аналогичные решения были реализованы и в других динамических языках, таких как Python, где появилась возможность отложенной оценки аннотаций. Среди перспектив развития Sorbet рассматриваются варианты опционального расширения синтаксиса с помощью monkey patching для более удобного и выразительного описания типов. Также обсуждаются изменения в синтаксисе кортежей и возможность сокращения избыточной вербозности при объявлении сложных типов. Интересной идеей будущего являются RBS-комментарии — особые строки документации, способные описывать сигнатуры методов в более чистом и компактном формате. В 2024 году на RubyKaigi был представлен опыт их использования, а Sorbet уже начал внедрять поддержку этих аннотаций.
Идея заключается в том, чтобы встроить поддержку таких комментариев непосредственно в Ruby VM, что позволит использовать преимущества как inline-аннотаций, так и открытого синтаксиса RBS без ущерба для производительности и динамических свойств Ruby. Если Ruby VM научится понимать и обрабатывать RBS-комментарии, это позволит создавать новые инструменты для автодополнения в IRB, генерации JSON-схем на основе типов, более точного анализа кода и улучшения статической проверки. Такой подход открывает перспективы появлению более гибкой и мощной системы типизации, не требующей изменения ядра языка, но обеспечивающей его расширяемость. В целом Sorbet сумел найти баланс между строгой типизацией и динамичностью Ruby, внедряя решения, которые соответствуют ожиданиям разработчиков и требованиям индустрии. Хотя синтаксис пока далек от идеала и порой критикуется за избыточную сложность, его эволюция не остановлена.