pexels-photo-5535554.jpeg

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

 
0
このエントリーをはてなブックマークに追加
Kazuki Moriyama
Kazuki Moriyama (森山 和樹)

はじめに

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

https://sorbet.org/

Overview

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

srb

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

sorbet-runtime

  • Pure Rubyにアノテーションを加えるgem
  • 名前空間Tの中にsigメソッドを定義
  • 実行中も動的にコードの型チェック
  • 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

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/  
└── ···

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にとって革命じゃないのか。
追加・修正あればコメントお願いします。

info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。