Rubyの静的型付けについて (RBS, TypeProf, Steep, Sorbet)

English follows Japanese. 英語の文章は後半にあります。

こんにちは。
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の主な違いについてコメントしていたので、以下に紹介します。

TypeProf と Steep の違いをまとめた表

TypeProf と Steep の違い

Sorbet

RBSはRubyの公式な型定義言語で、アノテーションは使いません。一方、Sorbetはコードベースに静的な型チェックを導入するために段階的に採用できるシステムで、アノテーションベースであり、RBIと呼ばれる独自の型定義言語を持っています。

RBSとSorbetの両方を比較する人もいますが、実際には目的が違うので、両方を使い分けるのがいいと思います。

Types in Ruby 3, RBS, and Sorbet · Sorbet
Yesterday Square to their blog introducing

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はまだ開発中(この記事が書かれた時点)なので、少し不安定かもしれません。

GitHub - Shopify/tapioca: The swiss army knife of RBI generation
The swiss army knife of RBI generation. Contribute to Shopify/tapioca development by creating an account on GitHub.

まとめ

この記事では、Rubyの静的型付けに関して RBS, TypeProf, Steep そして Sorbet を紹介しました。個々のgemのについて、それぞれの違いを雰囲気だけでも感じてもらえれば幸いです。

静的型付けの一歩を踏み出す前に、基本的なことを理解することで、現在のプロジェクトの目標に応じて、何ができるか、どのツールを使うのがベストかについて、より良いアイデアが得られると思います。

この記事を書くために使用した参考ページを以下にまとめましたので、より詳しく学びたい人はぜひチェックしてみてください。

参考ページ

RBS

GitHub - ruby/rbs: Type Signature for Ruby
Type Signature for Ruby. Contribute to ruby/rbs development by creating an account on GitHub.
RubyKaigi Takeout 2020
RubyKaigi Takeout 2020, #rubykaigi

TypeProf

GitHub - ruby/typeprof: An experimental type-level Ruby interpreter for testing and understanding Ruby code
An experimental type-level Ruby interpreter for testing and understanding Ruby code - ruby/typeprof
TypeProf for IDE: Enrich Dev-Experience without Annotations by Yusuke Endoh
RubyKaigi Takeout 2021, #rubykaigi

Steep

GitHub - soutaro/steep: Static type checker for Ruby
Static type checker for Ruby. Contribute to soutaro/steep development by creating an account on GitHub.
RubyKaigi 2019
RubyKaigi 2019, 4/18...4/20, Fukuoka, Japan #rubykaigi
Ruby Programming with Type Checking
RubyKaigi 2018

Sorbet

GitHub - sorbet/sorbet: A fast, powerful type checker designed for Ruby
A fast, powerful type checker designed for Ruby. Contribute to sorbet/sorbet development by creating an account on GitHub.
Sorbet · A static type checker for Ruby
A static type checker for Ruby
Types in Ruby 3, RBS, and Sorbet · Sorbet
Yesterday Square to their blog introducing
GitHub - Shopify/tapioca: The swiss army knife of RBI generation
The swiss army knife of RBI generation. Contribute to Shopify/tapioca development by creating an account on GitHub.

 

(ルーカス)


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.

Static Analyzer Differences

TypeProf & Steep Differences

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

GitHub - ruby/rbs: Type Signature for Ruby
Type Signature for Ruby. Contribute to ruby/rbs development by creating an account on GitHub.
RubyKaigi Takeout 2020
RubyKaigi Takeout 2020, #rubykaigi

TypeProf

GitHub - ruby/typeprof: An experimental type-level Ruby interpreter for testing and understanding Ruby code
An experimental type-level Ruby interpreter for testing and understanding Ruby code - ruby/typeprof
TypeProf for IDE: Enrich Dev-Experience without Annotations by Yusuke Endoh
RubyKaigi Takeout 2021, #rubykaigi

Steep

GitHub - soutaro/steep: Static type checker for Ruby
Static type checker for Ruby. Contribute to soutaro/steep development by creating an account on GitHub.
RubyKaigi 2019
RubyKaigi 2019, 4/18...4/20, Fukuoka, Japan #rubykaigi
Ruby Programming with Type Checking
RubyKaigi 2018

Sorbet

GitHub - sorbet/sorbet: A fast, powerful type checker designed for Ruby
A fast, powerful type checker designed for Ruby. Contribute to sorbet/sorbet development by creating an account on GitHub.
Sorbet · A static type checker for Ruby
A static type checker for Ruby
Types in Ruby 3, RBS, and Sorbet · Sorbet
Yesterday Square to their blog introducing
GitHub - Shopify/tapioca: The swiss army knife of RBI generation
The swiss army knife of RBI generation. Contribute to Shopify/tapioca development by creating an account on GitHub.

 

(Lucas)

タイトルとURLをコピーしました