Rubyの型解析ライブラリSorbet事始め

はじめに

ついに前々から今年の夏リリースされるとアナウンスされていたstripe製のRuby型解析ライブラリがリリースされました。
個人的にRubyは型が加われば最強の言語なので、これは本当に嬉しい。
導入方法として、公式のGetting Startedをまとめていく。

https://sorbet.org/

Overview

Sorbetは2つのコンポーネントが中心

srb

  • SorbetのCLI
  • 静的解析(コード実行前解析)を行う
  • Sorbetを始めるときにも活躍

sorbet-runtime

  • Pure Rubyにアノテーションを加えるgem
  • 名前空間Tの中にsigメソッドを定義
    • Signaturesに関わる
    • https://sorbet.org/docs/sigs
  • 実行中も動的にコードの型チェック
  • 2つはお互いを補う
    • 実行中の型予測を行う
    • 同時に実行結果が型予測を補強
  • Sorbetの一例
# typed: true  
require 'sorbet-runtime'  

class A  
  extend T::Sig  

  sig {params(x: Integer).returns(String)}  
  def bar(x)  
    x.to_s  
  end  
end  

def main  
  A.new.barr(91)   # error: Typo!  
  A.new.bar("91")  # error: Type mismatch!  
end

play groud%7D%0A%20%20def%20bar(x)%0A%20%20%20%20x.to_s%0A%20%20end%0Aend%0A%0Adef%20main%0A%20%20A.new.barr(91)%20%20%20%23%20error%3A%20Typo!%0A%20%20A.new.bar(%2291%22)%20%20%23%20error%3A%20Type%20mismatch!%0Aend)

Adopting Sorbet in an Existing Codebase

Step 1: 必要gemのインストール

  • 必要なgemは2つ
  • cli
  • ランタイムシステム

手順

Gemfileに追加

# -- Gemfile --

gem "sorbet", :group => :development  
gem "sorbet-runtime"

インストール

bundle install

うまく動いているかの検証

❯ srb

[help output]

❯ srb typecheck -e ‘puts “Hello, world!”‘ No errors! Great job. ❯ bundle exec ruby -e ‘puts(require “sorbet-runtime”)’ true

Step 2: Sorbetのイニシャライズ

イニシャライズコマンド


❯ srb init

なんかいろいろ出てくる
終了後sorbetディレクトリが作成される
Gitなどでバージョン管理が必要

イニシャライズの検証

sorbet/ディレクトリの構成

sorbet/  
├── config  
└── rbi/  
└── ···
  • sorbet/configはSorbetの設定ファイル
  • https://sorbet.org/docs/cli
  • sorbet/rbi/はRBIファイルが定義されている
  • Ruby Interfaceファイルのこと
  • https://sorbet.org/docs/rbi

Step 3: まずはsrb tc

超簡単


❯ srb tc

デフォルトではカレントディレクトリのRuby fileすべての型解析を行う

Step 4: constant resolutionエラーの修正

この時点でのエラー

  • 基本的にはSorbetはそいつらを無視
  • 無視しないで修正することが大事

1. Parse errors

  • Rubyの文法が適正であることの要請

2. Dynamic constant references

  • 定数アクセスパターンは禁止
  • 例えばfoo.bar::A
  • ほとんどの場合メソッド呼び出しパターンで解決
  • foo.bar.get_A

3. Dynamic includes

動的なincludeは静的解析不可能

module A; end  
module B; end  

def x  
  rand.round == 0 ? A : B  
end  

class Main  
  include x  
end

メソッドxの内容によらず上のincludeそのものが良くないみたい

4. Missing constants

  • Gemを含めたすべてのクラス、モジュール、定数について知ることがSorbetの効率性に重要
  • 定数は継承階層、型の互換性を知る上で中心的な役割を果たす

3と4の解決方法

  • RBIファイルを使用
    • 型アノテーションだけが書かれたファイル
    • pythonでいうstubファイルみたいなもの
  • srb initが自動でRBIファイルを作成してくれる
  • 完璧じゃないので3と4のエラーを解決するには手書きが必要

Step 5: 型検査の適用

  • Step 4まででSorbetが保証したもの
  • すべてのRubyファイルの解析の完了
  • すべてのクラス、モジュール、定数の解析完了
    • コードの自動補完の完全正確性
    • 型アノテーションで使用可能
  • すべてのgemが明示的ななインターフェイスを持つ
  • 型検査適用の超まとめ
  • # typed: trueをファイルに記述
  • メソッドのシグネチャを書いていく

Quick Reference

型検査の実行

# typed: trueをRubyファイルに追加
あとは自作の関数にアノテーションをつけていくだけ

# typed: true

class Main
  # (2) sig アノテーションを利用するためT::Sigをextend:
  extend T::Sig

  # (3) メソッドにsigアノテーションを追加:
  sig {params(x: String).returns(Integer)}
  def self.main(x)
    x.length
  end

  # 引数がないメソッドはこうも書ける:
  sig {returns(Integer)}
  def no_params
    42
  end
end 
  • srbsorbet-runtimeMain.mainメソッドを検査し、引数がStringであることとIntegerをreturnすることを確認する
  • srbはこれを静的解析する
  • sorbet-runtimeは同時にメソッドが呼ばれるたび動的に検査する

よく使う型

  • Integer, String, T::Boolean – Class Types
  • T.nilable – Nilable Types
  • T.any – Union Types
  • T.let, T.cast, T.must, T.assert_type! – Type Assertions
  • [Type1, Type2] – Tuple Types
  • {key1: Type1, key2: Type2} – Shape Types
  • T.untyped
  • T.noreturn
  • T.type_alias – Type Aliases
  • T::Array, T::Hash, T::Set, T::Enumerable – Generics in the Standard Library
  • T.proc – Proc Types
  • T.class_of
  • T.self_type
  • T.all – Intersection Types

https://sorbet.org/docs/quickref#type-system

よくある質問

何かうまくいかない

トラブルシューティングの項目を参照
https://sorbet.org/docs/troubleshooting

Sorbetを実行できる環境はある?

playgroundがある
https://sorbet.run/

一つのファイルだけ実行できる?

できる


# -- foo.rb --  

# typed: true  
require 'sorbet-runtime'

class Main
  extend T::Sig

  sig {void}
  def self.main
    puts 'Hello, world!'
  end
end

Main.main

❯ srb tc foo.rb


実行時解析のテスト


❯ bundle exec ruby foo.rb

プロジェクト全体への実行は?

簡単


❯ srb

まとめ

導入簡単。
いろいろできそうなこと有るし、結構これRubyにとって革命じゃないのか。
追加・修正あればコメントお願いします。

View Comments
There are currently no comments.