こんにちは。
ShareWisのソフトウェアエンジニアのルーカスです。
今回は私たちのサービスで利用しているRubyについてお話をします。
ここ数年、動的型付けされたプログラミング言語の中で静的型付けを採用する傾向が強まっています。
それは Rubyのエコシステムでも同じです。
この記事では両者を比較したり、静的型付けや動的型付けの利点と欠点を挙げたりはしません。Rubyでの静的型付けをどうやって使うかを紹介したいと思います。
Rubyの型について検索していると、おそらく以下のような名前を目にするでしょう。RBS
, Sorbet
, Steep
, Typeprof
, などなど。
これらは何でしょうか?また、どのような場合に使用するのでしょうか?
それでは解説していきます。
RBS (Ruby Signature)
Ruby 3のリリースとともにRubyのコードに型定義情報を提供するRBSという仕組みが導入されました。RBSはRubyの型情報を文書化するためのもので、通常は「シグネチャ」と呼ばれる構造体を.rbs
ファイル(Rubyコードとは別物)に記述します。これらのファイルは、C/C++ の .h ファイルに似ていると考えると分かりやすいでしょう。
既存のプロジェクトでは、すべてのシグネチャを手動で作成する代わりに、RBS はプロトタイプとなる.rbs
ファイルを自動生成してくれます。自動生成されたファイルの一部は手動で修正しないといけないかもしれませんが、行数の多いファイルがある場合には自動生成機能はとても便利です。
RBSを使用するには、まずGemfileにgemを追加します。
# Gemfile
gem "rbs"
次の例では、lib/user.rb
を使用しています。
# lib/user.rb
class User
attr_reader :login, :email
def initialize(login:, email:)
@login = login
@email = email
end
end
プロトタイプ機能は、次のように実行することができます。
$ bundle exec rbs prototype rb lib/user.rb > user.rbs
.rbsファイルの例です。
# sig/user.rbs
class User
attr_reader login: String
attr_reader email: String
def initialize: (login: String, email: String) -> void
end
静的型付け分析ツールたち
RBSはRubyが提供している公式の型情報提供機能です。
公式の機能があるのに、他のgemを使う理由は何でしょうか?簡単に言えば、他のgemは型の分析ツールであり、それぞれに特徴があります。
TypeProf
TypeProfは型推論ツールです。型情報を持たない、注釈のないRubyコードを解析し、コード内のメソッドの型を推測します。いくつかの例外はありますが、ほとんどすべての 値を型レベルにまで抽象化します。
RBSのプロトタイプを使う代わりに、TypeProfを使ってプロジェクトのプロトタイプを生成することができます。そのためには以下のコマンドを実行します。
使用する前に、Gemfileにgemを追加する必要があります。
# Gemfile
gem "typeprof"
次の例は、 lib/user.rbd
です。
# lib/user.rb
class User
attr_reader :login, :email
def initialize(login:, email:)
@login = login
@email = email
end
end
$ bundle exec typeprof lib/user.rb > user.rbs
TypeProfの生成したプロトタイプです。
# sig/user.rbs
# TypeProf 0.15.3
# Classes
class User
attr_reader login: String
attr_reader email: String
def initialize: (login: String, email: String) -> void
end
Steep
SteepはRBSをベースにした静的型チェックツールで、チェックをより正確にするためにコードに沿ったアノテーションもサポートしています。
Steepを使い始めるには、まずGemfileに追加する必要があります。
# Gemfile
gem "steep"
そして、次の例のように、 $ steep init
で起動し、Steepfileを設定します。
# Steepfile
target :lib do
signature "sig"
check "lib" # Directory name
end
.rbsファイルが/sigフォルダ内にあることを確認した後、ファイルをタイプチェックするために、以下のコマンドを実行します
$ steep check
最後に、前回の RubyKaigiでTypeProfの作者である遠藤祐介さんがSteepとTypeProfの主な違いについてコメントしていたので、以下に紹介します。
Sorbet
RBSはRubyの公式な型定義言語で、アノテーションは使いません。一方、Sorbetはコードベースに静的な型チェックを導入するために段階的に採用できるシステムで、アノテーションベースであり、RBIと呼ばれる独自の型定義言語を持っています。
RBSとSorbetの両方を比較する人もいますが、実際には目的が違うので、両方を使い分けるのがいいと思います。
Sorbetを使うために、まずGemfileでSorbetを設定します。
# Gemfile
gem 'sorbet', :group => :development
gem 'sorbet-runtime'
また、アノテーションを使用する例として、Sorbetを使用したクラスは次のようになります。
# lib/user.rb
# typed: strict
class User
extend T::Sig
sig {params(login: String, email: String).void}
def initialize(login:, email:)
@login = login
@email = email
end
end
SorbetのRubyインターフェイス (RBI) を生成するために、Tapiocaというgemを使うことができますが、このgemはまだ開発中(この記事が書かれた時点)なので、少し不安定かもしれません。
まとめ
この記事では、Rubyの静的型付けに関して RBS
, TypeProf
, Steep
そして Sorbet
を紹介しました。個々のgemのについて、それぞれの違いを雰囲気だけでも感じてもらえれば幸いです。
静的型付けの一歩を踏み出す前に、基本的なことを理解することで、現在のプロジェクトの目標に応じて、何ができるか、どのツールを使うのがベストかについて、より良いアイデアが得られると思います。
この記事を書くために使用した参考ページを以下にまとめましたので、より詳しく学びたい人はぜひチェックしてみてください。
参考ページ
RBS
TypeProf
Steep
Sorbet
(ルーカス)
English version: Static Typing in Ruby
In the last few years the trend of adopting static typing among the dynamic-typed programming languages is increasing and it’s not different in ruby’s ecosystem. The objective is not to compare the solutions or to list the advantages and disadvantages of static or dynamic typed, but it is to list some of different solutions we can use while working in ruby, so in this post I’ll do a brief introduction some them and share couple references in case you are interested on learning more about.
While searching about type checking in ruby you’ll probably see some names such as: RBS
, Sorbet
, Steep
,Typeprof
, and others… And what are those? When should I use one of them?
RBS (Ruby Signature)
Along with the Ruby 3 we also had the RBS being released, which is the official type definition language for Ruby, having as the main goal to describe the structures of the programs.
Is important to note that the RBS alone doesn’t perform any type checking, it is used to document the Ruby code, with structures that usually are called as “signatures” and are written in .rbs
files (which is different from Ruby code). For easier understanding you could consider those files something similar to the .h
files in C/C++.
For existing projects, instead of creating all the signatures manually, RBS provide a way to generate prototypes, you’ll probably need to check the files later to fix some of the signatures, although is really helpful when you have many files with a lot of lines.
For using the RBS first, add the gem into the Gemfile:
# Gemfile
gem "rbs"
Having the following example: lib/user.rb
# lib/user.rb
class User
attr_reader :login, :email
def initialize(login:, email:)
@login = login
@email = email
end
end
The basic usage of the prototype feature can be executed by running:
$ bundle exec rbs prototype rb lib/user.rb > user.rbs
This is an example of .rbs
file
# sig/user.rbs
class User
attr_reader login: String
attr_reader email: String
def initialize: (login: String, email: String) -> void
end
Static type analyzers
We have RBS for defining the structures of our Ruby’s programs, so what are the reasons for the other gems? In short, the others gems are static type analyzers and each of them has their own characteristics.
TypeProf
TypeProf is a type inference tool, analyzing non-annotated Ruby code which has no type information and try to guess the type of the methods in the code. It abstracts almost all Ruby values to a type level, although there still are some exceptions.
You can use TypeProf in order to generate the prototypes of your project instead of using RBS’ prototypes, and in order to do it you can run the following command:
Before using, we need add the gem in the Gemfile:
# Gemfile
gem "typeprof"
Having the following example: lib/user.rb
# lib/user.rb
class User
attr_reader :login, :email
def initialize(login:, email:)
@login = login
@email = email
end
end
$ bundle exec typeprof lib/user.rb > user.rbs
TypeProf’s generated prototype:
# sig/user.rbs
# TypeProf 0.15.3
# Classes
class User
attr_reader login: String
attr_reader email: String
def initialize: (login: String, email: String) -> void
end
Steep
Steep is a static type checker based on RBS, and it also support some annotations along the code to make the check more accurate.
To start using the steep you first will need to add it into the Gemfile:
# Gemfile
gem "steep"
Then start it with the $ steep init
and setting up the Steepfile, like the following example:
# Steepfile
target :lib do
signature "sig"
check "lib" # Directory name
end
After ensuring you have the .rbs
files inside the folder /sig
, in order to type check the files you can run the following command:
$ steep check
Lastly, during the last RubyKaigi, Yusuke Endoh (TypeProf’s author) commented some the main differences between Steep and TypeProf I thought really helpful, for that reason I’m writing it below.
Sorbet
While RBS is the official type definition language for Ruby and is a non-annotation type solution, Sorbet is a gradual type system that can be adopted incrementally to introduce the static type checking to the code base, being annotation based and having it’s own type definition language, called RBI.
Some people compare both RBS and Sorbet, although in reality they have different purposes, as much that they’re working together in order to improve both.
Setting the Sorbet in the Gemfile:
# Gemfile
gem 'sorbet', :group => :development
gem 'sorbet-runtime'
And as for an example what is to use annotations this is what a class using Sorbet would look like:
# lib/user.rb
# typed: strict
class User
extend T::Sig
sig {params(login: String, email: String).void}
def initialize(login:, email:)
@login = login
@email = email
end
end
For helping to generate the Ruby Interfaces (RBI) for Sorbet, it’s possible to use a gem called Tapioca, although the gem is still under development(by the time of this post was written) so it might be a little unstable.
Conclusion
This post is a brief introduction on the solutions related to Ruby’s static typing, explaining a little about RBS
, TypeProf
, Steep
and Sorbet
, trying to help to explain the different purpose of every gem commented here.
By understanding the basic before take any step further, I believe you’ll have a better idea on what you could do or which tool would be best to use depending on the goal you want to achieve in your current project.
In case you were interested by any gem/solution I’m listing below below the references I used for writing this post.
References
RBS
TypeProf
Steep
Sorbet
(Lucas)