本記事作成に至った経緯(Background)
アイスとタピオカ、響きからして全人類が好だと思います!!!
前提
みなさん型を意識して開発してますか。型好きですよね。え、Rubyには型がないって??
文句言われるわけですよね。
Ruby is Dead, Rails is Dead. Because We don't have static typing. — 発表スライド
Rubyは死んだとかRailsはもう終わりだー。なんかガッカリすることもあるんですけど、そういう方は2010年代最近話題になっているホットになっている言語はみんな静的型付け言語持っているのにRubyにはそれがない。おじいちゃんだから— Matz
【引用: [JA][Keynote] Ruby3 Typing / Yukihiro "Matz" Matsumoto】
動画の通り2016年9月のカンファレンスでRubyと型には言及がありました。終盤に東京オリンピックまでに~と述べてましたが、しっかり2020年12月25日に3.0.0が正式リリースされましたね。
Ruby 3.0.0 リリース
2010年代は静的型言語の時代でした。Rubyは抽象解釈を武器に、型宣言なしで静的型チェックする未来を目指します。RBSとTypeProfはその第一歩です。Rubyがもたらす誰も見たことがない静的型の世界を見守ってください — Matz
Ruby3.0.0で静的解析にRBSとTypeProfが導入されました。ここから最新RubyKaigi2025まで型周りの話が永遠とRuby界隈ではHotトピックなわけですが正直なところどうなんでしょう。と思い調査しようと思いました。
本題
他社事例
Ruby、Railsを使っている企業は山ほどありますが、他社はどうしているのか調べてみました。
・Eight from sansan: EightではRubyへの型導入を進めています
・Findy: FindyのRailsプロジェクトでSorbetの型チェックを試してみた
・Timee: RBS に出会って変わった Ruby への向き合い方
・WEAR form zozo: rbs-traceを使ってWEARで型生成を試してみた
Sorbetについて
RubyKaigi2019でStripe社から発表があったRuby向け型チェッカーであるSorbet🍨の概要です。
[EN] State of Sorbet: A Type Checker for Ruby
スライド: https://sorbet.run/talks/RubyKaigi2019/#/
Tapiocaについて
Sorbetが公式に推奨するRBI生成方法であるShopify社のTapiocaの概要です。
RubyKaigi2023の講演情報はこちら。
[EN] Generating RBIs for dynamic mixins with Sorbet and Tapioca
スライド: Generating RBIs for dynamic mixins with Sorbet and Tapioca
リリース情報: Tapioca is the recommended way to generate RBIs for Sorbet
やってみよう
導入手順
1. sorbetとtapiocaのインストール
gem 'sorbet', :group => :development
gem 'sorbet-runtime'
gem 'tapioca', require: false, group: [:development, :test]
公式: https://sorbet.org/
公式Github: https://github.com/sorbet/sorbet
公式Github: https://github.com/Shopify/tapioca
2. インストール確認
$ docker compose -f compose.yaml run --rm web bundle exec srb
[+] Creating 1/0
✔ Container rails_research-db-1 Running 0.0s
No sorbet/ directory found. Maybe you want to run 'srb init'?
A type checker for Ruby
Usage:
srb Same as "srb t"
srb (init | initialize) Initializes the `sorbet` directory
srb rbi [options] Manage the `sorbet` directory
srb (t | tc | typecheck) [options] Typechecks the code
Options:
-h, --help View help for this subcommand.
--version Show version.
For full help:
https://sorbet.org
sorbeのsrbコマンドが使用可能になった。
$ docker compose -f compose.yaml run --rm web bundle exec srb typecheck -e 'puts "Hello, world!"'
[+] Creating 1/0
✔ Container rails_research-db-1 Running 0.0s
No errors! Great job.
sorbeのsrbコマンドオプションのtypecheckも使用可能。
$ docker compose -f compose.yaml run --rm web bundle exec ruby -e 'puts(require "sorbet-runtime")'
[+] Creating 1/0
✔ Container rails_research-db-1 Running 0.0s
true
sorbet-runtimeはDockerコンテナ内の環境に正しくインストールされており利用可能。
$ docker compose -f compose.yaml run --rm web bundle exec tapioca help
[+] Creating 1/0
✔ Container XXXXXX Running 0.0s
Commands:
tapioca --version, -v # Show version
tapioca annotations # Pull gem RBI annotations from remote sources
tapioca check-shims # Check duplicated definitions in shim RBIs
tapioca configure # Initialize folder structure and type checking configuration
tapioca dsl [constant...] # Generate RBIs for dynamic methods
tapioca gem [gem...] # Generate RBIs from gems
tapioca help [COMMAND] # Describe available commands or one specific command
tapioca init # Get project ready for type checking
tapioca require # Generate the list of files to be required by tapioca
tapioca todo # Generate the list of unresolved constants
Options:
-c, [--config=<config file path>] # Path to the Tapioca configuration file
# Default: sorbet/tapioca/config.yml
-V, [--verbose], [--no-verbose], [--skip-verbose] # Verbose output for debugging purposes
# Default: false
tapiocaコマンドが使えるようになりました
3. 初期化コマンド実施
tapioca initコマンドでいざ初期化していきましょう。
$ docker compose -f compose.yaml run --rm web bundle exec tapioca init
<省略>
.......................
.......................
.......................
Documentation
We recommend skimming these docs to get a feel for how to use Sorbet:
- Gradual Type Checking: https://sorbet.org/docs/gradual
- Enabling Static Checks: https://sorbet.org/docs/static
- RBI Files: https://sorbet.org/docs/rbi
自動生成の詳細の中身はこちら
$ docker compose -f compose.yaml run --rm web bundle exec tapioca init
[+] Creating 1/0
✔ Container rails_research-db-1 Running 0.0s
create sorbet/config
create sorbet/tapioca/config.yml
create sorbet/tapioca/require.rb
create bin/tapioca
Retrieving index from central repository... Done
Listing gems from Gemfile.lock... Done
Removing annotations for gems that have been removed... Nothing to do
Fetching gem annotations from central repository...
Fetched actionmailer
create sorbet/rbi/annotations/actionmailer.rbi
Fetched actionpack
create sorbet/rbi/annotations/actionpack.rbi
Fetched actionview
create sorbet/rbi/annotations/actionview.rbi
Fetched activejob
create sorbet/rbi/annotations/activejob.rbi
Fetched activemodel
create sorbet/rbi/annotations/activemodel.rbi
Fetched activerecord
create sorbet/rbi/annotations/activerecord.rbi
Fetched activesupport
create sorbet/rbi/annotations/activesupport.rbi
Fetched globalid
create sorbet/rbi/annotations/globalid.rbi
Fetched minitest
create sorbet/rbi/annotations/minitest.rbi
Fetched railties
create sorbet/rbi/annotations/railties.rbi
Fetched rainbow
create sorbet/rbi/annotations/rainbow.rbi
Done
Removing RBI files of gems that have been removed:
Nothing to do.
Generating RBI files of gems that are added or updated:
Requiring all gems to prepare for compiling... Done
Compiled actionmailer
create sorbet/rbi/gems/actionmailer@8.0.2.rbi
Compiled actiontext
create sorbet/rbi/gems/actiontext@8.0.2.rbi
Compiled activejob
Compiled actionmailbox
create sorbet/rbi/gems/actionmailbox@8.0.2.rbi
create sorbet/rbi/gems/activejob@8.0.2.rbi
Compiled actioncable
create sorbet/rbi/gems/actioncable@8.0.2.rbi
Compiled ast
create sorbet/rbi/gems/ast@2.4.3.rbi
Compiled base64
create sorbet/rbi/gems/base64@0.2.0.rbi
Compiled benchmark
create sorbet/rbi/gems/benchmark@0.4.0.rbi
Compiled bigdecimal
create sorbet/rbi/gems/bigdecimal@3.1.9.rbi
Compiled addressable
Compiled bindex (empty output)
create sorbet/rbi/gems/bindex@0.8.1.rbi
create sorbet/rbi/gems/addressable@2.8.7.rbi
Compiled activemodel
create sorbet/rbi/gems/activemodel@8.0.2.rbi
Compiled builder (empty output)
create sorbet/rbi/gems/builder@3.3.0.rbi
Compiled bootsnap
create sorbet/rbi/gems/bootsnap@1.18.4.rbi
Compiled activestorage
create sorbet/rbi/gems/activestorage@8.0.2.rbi
Compiled connection_pool (empty output)
create sorbet/rbi/gems/connection_pool@2.5.3.rbi
Compiled crass
create sorbet/rbi/gems/crass@1.0.6.rbi
Compiled date
create sorbet/rbi/gems/date@3.4.1.rbi
Compiled dotenv
create sorbet/rbi/gems/dotenv@3.1.8.rbi
Compiled actionview
create sorbet/rbi/gems/actionview@8.0.2.rbi
Compiled drb
create sorbet/rbi/gems/drb@2.2.1.rbi
Compiled erubi
create sorbet/rbi/gems/erubi@1.13.1.rbi
Compiled et-orbi
create sorbet/rbi/gems/et-orbi@1.2.11.rbi
Compiled globalid
create sorbet/rbi/gems/globalid@1.2.1.rbi
Compiled fugit
create sorbet/rbi/gems/fugit@1.11.1.rbi
Compiled importmap-rails
create sorbet/rbi/gems/importmap-rails@2.1.0.rbi
Compiled io-console (empty output)
create sorbet/rbi/gems/io-console@0.8.0.rbi
Compiled brakeman
create sorbet/rbi/gems/brakeman@7.0.2.rbi
Compiled jbuilder
create sorbet/rbi/gems/jbuilder@2.13.0.rbi
Compiled i18n
Compiled concurrent-ruby
create sorbet/rbi/gems/i18n@1.14.7.rbi
create sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi
Compiled lint_roller (empty output)
create sorbet/rbi/gems/lint_roller@1.1.0.rbi
Compiled json
create sorbet/rbi/gems/json@2.11.3.rbi
Compiled logger
create sorbet/rbi/gems/logger@1.7.0.rbi
Compiled loofah
create sorbet/rbi/gems/loofah@2.24.0.rbi
Compiled actionpack
create sorbet/rbi/gems/actionpack@8.0.2.rbi
Compiled mini_mime
create sorbet/rbi/gems/mini_mime@1.1.5.rbi
Compiled activesupport
Compiled marcel
create sorbet/rbi/gems/marcel@1.0.4.rbi
Compiled matrix
create sorbet/rbi/gems/activesupport@8.0.2.rbi
create sorbet/rbi/gems/matrix@0.4.2.rbi
Compiled msgpack
create sorbet/rbi/gems/msgpack@1.8.0.rbi
Compiled mysql2
create sorbet/rbi/gems/mysql2@0.5.6.rbi
Compiled capybara
create sorbet/rbi/gems/capybara@3.40.0.rbi
Compiled net-pop
create sorbet/rbi/gems/net-pop@0.1.2.rbi
Compiled net-protocol
create sorbet/rbi/gems/net-protocol@0.2.2.rbi
Compiled minitest
create sorbet/rbi/gems/minitest@5.25.5.rbi
Compiled netrc
create sorbet/rbi/gems/netrc@0.11.0.rbi
Compiled nio4r
create sorbet/rbi/gems/nio4r@2.7.4.rbi
Compiled parallel
create sorbet/rbi/gems/parallel@1.27.0.rbi
Compiled net-smtp
create sorbet/rbi/gems/net-smtp@0.5.1.rbi
Compiled prettyprint
create sorbet/rbi/gems/prettyprint@0.2.0.rbi
Compiled pp
create sorbet/rbi/gems/pp@0.6.2.rbi
Compiled propshaft
create sorbet/rbi/gems/propshaft@1.1.0.rbi
Compiled language_server-protocol (empty output)
create sorbet/rbi/gems/language_server-protocol@3.17.0.4.rbi
Compiled public_suffix
create sorbet/rbi/gems/public_suffix@6.0.2.rbi
Compiled psych
create sorbet/rbi/gems/psych@5.2.5.rbi
Compiled raabro
create sorbet/rbi/gems/raabro@1.4.0.rbi
Compiled nokogiri
create sorbet/rbi/gems/nokogiri@1.18.8.rbi
Compiled net-imap
Compiled racc
create sorbet/rbi/gems/racc@1.8.1.rbi
create sorbet/rbi/gems/net-imap@0.5.8.rbi
Compiled puma
create sorbet/rbi/gems/puma@6.6.0.rbi
Compiled rack-session
create sorbet/rbi/gems/rack-session@2.1.1.rbi
Compiled rack-test
Compiled rails (empty output)
create sorbet/rbi/gems/rails@8.0.2.rbi
create sorbet/rbi/gems/rack-test@2.2.0.rbi
Compiled rackup
create sorbet/rbi/gems/rackup@2.2.1.rbi
Compiled rails-dom-testing
create sorbet/rbi/gems/rails-dom-testing@2.2.0.rbi
Compiled rails-html-sanitizer
create sorbet/rbi/gems/rails-html-sanitizer@1.6.2.rbi
Compiled rainbow (empty output)
create sorbet/rbi/gems/rainbow@3.1.1.rbi
Compiled rack
create sorbet/rbi/gems/rack@3.1.14.rbi
Compiled rake
create sorbet/rbi/gems/rake@13.2.1.rbi
Compiled railties
create sorbet/rbi/gems/railties@8.0.2.rbi
Compiled rbi
create sorbet/rbi/gems/rbi@0.3.3.rbi
Compiled activerecord
Compiled reline (empty output)
create sorbet/rbi/gems/reline@0.6.1.rbi
create sorbet/rbi/gems/activerecord@8.0.2.rbi
Compiled rbs
create sorbet/rbi/gems/rbs@3.9.4.rbi
Compiled regexp_parser
create sorbet/rbi/gems/regexp_parser@2.10.0.rbi
Compiled rexml
create sorbet/rbi/gems/rexml@3.4.1.rbi
Compiled rubocop-performance (empty output)
create sorbet/rbi/gems/rubocop-performance@1.25.0.rbi
Compiled rubocop-rails-omakase (empty output)
create sorbet/rbi/gems/rubocop-rails-omakase@1.1.0.rbi
Compiled rubocop-ast (empty output)
create sorbet/rbi/gems/rubocop-ast@1.44.1.rbi
Compiled ruby-progressbar (empty output)
create sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi
Compiled securerandom
create sorbet/rbi/gems/securerandom@0.4.1.rbi
Compiled rubyzip
create sorbet/rbi/gems/rubyzip@2.4.1.rbi
Compiled solid_cable
create sorbet/rbi/gems/solid_cable@3.0.8.rbi
Compiled rubocop-rails (empty output)
create sorbet/rbi/gems/rubocop-rails@2.31.0.rbi
Compiled solid_cache
create sorbet/rbi/gems/solid_cache@1.0.7.rbi
Compiled solid_queue
create sorbet/rbi/gems/solid_queue@1.1.5.rbi
Compiled stimulus-rails
create sorbet/rbi/gems/stimulus-rails@1.3.4.rbi
Compiled stringio (empty output)
create sorbet/rbi/gems/stringio@3.1.7.rbi
Compiled selenium-webdriver
create sorbet/rbi/gems/selenium-webdriver@4.32.0.rbi
Compiled spoom
create sorbet/rbi/gems/spoom@1.6.3.rbi
Compiled timeout
create sorbet/rbi/gems/timeout@0.4.3.rbi
Compiled thor
create sorbet/rbi/gems/thor@1.3.2.rbi
Compiled turbo-rails
create sorbet/rbi/gems/turbo-rails@2.0.13.rbi
Compiled unicode-display_width (empty output)
create sorbet/rbi/gems/unicode-display_width@3.1.4.rbi
Compiled unicode-emoji (empty output)
create sorbet/rbi/gems/unicode-emoji@4.0.4.rbi
Compiled tzinfo
create sorbet/rbi/gems/tzinfo@2.0.6.rbi
Compiled uri
create sorbet/rbi/gems/uri@1.0.3.rbi
Compiled rdoc
Compiled useragent (empty output)
create sorbet/rbi/gems/useragent@0.16.11.rbi
create sorbet/rbi/gems/rdoc@6.13.1.rbi
Compiled tapioca
create sorbet/rbi/gems/tapioca@0.16.11.rbi
Compiled web-console
create sorbet/rbi/gems/web-console@4.2.1.rbi
Compiled mail
Compiled websocket-extensions
create sorbet/rbi/gems/websocket-extensions@0.1.5.rbi
create sorbet/rbi/gems/mail@2.8.1.rbi
Compiled xpath
create sorbet/rbi/gems/xpath@3.2.0.rbi
Compiled websocket-driver
create sorbet/rbi/gems/websocket-driver@0.7.7.rbi
Compiled websocket
create sorbet/rbi/gems/websocket@1.2.11.rbi
Compiled yard-sorbet
create sorbet/rbi/gems/yard-sorbet@0.9.0.rbi
Compiled zeitwerk
create sorbet/rbi/gems/zeitwerk@2.7.2.rbi
Compiled rubocop (empty output)
create sorbet/rbi/gems/rubocop@1.75.5.rbi
Compiled yard
create sorbet/rbi/gems/yard@0.9.37.rbi
Compiled prism
create sorbet/rbi/gems/prism@1.4.0.rbi
Compiled parser
create sorbet/rbi/gems/parser@3.3.8.0.rbi
Checking generated RBI files... Done
Changed strictness of sorbet/rbi/gems/drb@2.2.1.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/json@2.11.3.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/logger@1.7.0.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/msgpack@1.8.0.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/net-imap@0.5.8.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/net-protocol@0.2.2.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/net-smtp@0.5.1.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/pp@0.6.2.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/rdoc@6.13.1.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/rexml@3.4.1.rbi to `typed: false` (conflicting with DSL files)
Changed strictness of sorbet/rbi/gems/tapioca@0.16.11.rbi to `typed: false` (conflicting with DSL files)
All operations performed in working directory.
Please review changes and commit them.
Finding all unresolved constants, this may take a few seconds... Nothing to do
This project is now set up for use with Sorbet and Tapioca
The sorbet/ folder should exist and look something like this:
├── config # Default options to be passed to Sorbet on every run
└── rbi/
├── annotations/ # Type definitions pulled from the rbi-central repository
├── gems/ # Autogenerated type definitions for your gems
└── todo.rbi # Constants which were still missing after RBI generation
└── tapioca/
├── config.yml # Default options to be passed to Tapioca
└── require.rb # A file where you can make requires from gems that might be needed for gem RBI generation
Please check this folder into version control.
🤔 What's next
1. Many Ruby applications use metaprogramming DSLs to dynamically generate constants and methods.
To generate type definitions for any DSLs in your application, run:
bin/tapioca dsl
2. Check whether the constants in the sorbet/rbi/todo.rbi file actually exist in your project.
It is possible that some of these constants are typos, and leaving them in todo.rbi will
hide errors in your application. Ideally, you should be able to remove all definitions
from this file and delete it.
3. Typecheck your project:
bundle exec srb tc
There should not be any typechecking errors.
4. Upgrade a file marked "# typed: false" to "# typed: true".
Then, run: bundle exec srb tc and try to fix any errors.
You can use Spoom to bump files for you:
spoom bump --from false --to true
To learn more about Spoom, visit: https://github.com/Shopify/spoom
5. Add signatures to your methods with sig. To learn how, read: https://sorbet.org/docs/sigs
Documentation
We recommend skimming these docs to get a feel for how to use Sorbet:
- Gradual Type Checking: https://sorbet.org/docs/gradual
- Enabling Static Checks: https://sorbet.org/docs/static
- RBI Files: https://sorbet.org/docs/rbi
生成されたファイルは以下に格納されています
$ git status
(use "git add <file>..." to include in what will be committed)
bin/tapioca
sorbet/
ここまでがインストール編になります。
to be continued...