RBS から始める
静的型付け生活
Leaner Technologies Inc.
黒曜
(@kokuyouwind)
$ whoami
-
黒曜 / @kokuyouwind
-
名古屋在住
-
Leaner Technologies Inc. 所属
-
Railsエンジニア
-
Next.js とか AWS 周りも触ってる
-
We're Hiring!!!
静的型付けの話
2020年12月の Ruby 3.0 で
静的型検査の仕組みが導入された
2021年12月の Ruby 3.1 でも
各種のアップデートが入っている
Rubyの静的型検査の概要と、
どう活用するかの話をします
アジェンダ
-
Ruby の静的型検査機能の概要
-
静的型検査を利用した開発手順
-
RBSの機能と利用例
-
ライブラリ利用時の静的型検査
-
まとめ
アジェンダ
-
Ruby の静的型検査機能の概要
-
静的型検査を利用した開発手順
-
RBSの機能と利用例
-
ライブラリ利用時の静的型検査
-
まとめ
動的型付けと静的型付け
-
動的型付け
-
事前の型検査は行わず、実行時に型を検査する
-
型エラーは実行時にしかわからない
-
動的なメソッド追加などに対応しやすい
-
-
静的型付け
-
コンパイル時など、実行前に型を検査する
-
型エラーを早期に検知できる
-
動的型付け言語への静的型検査機能追加が増えている
-
Rubyは動的型付け言語
Rubyの静的型検査機能: スタンス
Rubyは生まれた時から動的型付け言語で,
型指定が無くてもこれまで動いているため,
型指定はむしろ積極的に外すべきだと
まつもとさんは主張します。
ー Ruby Kaigi 2016 基調講演レポートより
Rubyの静的型検査機能: 構成
-
RBS: Rubyのための型記述言語
-
Steep: 静的型検査器
-
TypeProf: 静的型解析器
class Stack
def push(e)
@stack << e
end
end
stack.rb
class Stack
def push: (Integer) -> untyped
end
stack.rbs
TypeProfで
型推論
Steepで
型検査
Rubyの静的型検査機能: RBS
-
RBS: Rubyのための型記述言語
-
.rb ごとに、対応する .rbs ファイルに記述
-
TypeScriptの.d.tsのようなイメージ
-
class Stack
def push(e)
@stack << e
end
end
stack.rb
class Stack
def push: (Integer) -> untyped
end
stack.rbs
TypeProfで
型推論
Steepで
型検査
Rubyの静的型検査機能: RBS
-
Steep: 静的型検査器
-
RBS を元に、Rubyコードの型エラーを検査
-
tsc --noEmit のようなイメージ
-
class Stack
def push(e)
@stack << e
end
end
stack.rb
class Stack
def push: (Integer) -> untyped
end
stack.rbs
TypeProfで
型推論
Steepで
型検査
Rubyの静的型検査機能: RBS
-
TypeProf: 静的型解析器
-
Rubyコードを元に、型を推論してRBSを生成
-
あまり他の言語では見ない仕組み
-
class Stack
def push(e)
@stack << e
end
end
stack.rb
class Stack
def push: (Integer) -> untyped
end
stack.rbs
TypeProfで
型推論
Steepで
型検査
(余談) 静的型検査ツール Sorbet
-
Stripe製の静的型検査ツール
-
RBSではなくRBIという独自形式を用いる
-
インラインで型注釈を書くことも可能
-
-
RBSを用いるツール群とは現状で互換性がない
# typed: true
extend T::Sig
sig {params(name: String).returns(Integer)}
def main(name)
puts "Hello, #{name}!"
name.length
end
ここまでのまとめ
-
Ruby は型宣言のための文法は導入しない方針
-
代わりに型宣言専用のRBSが導入された
-
-
RBSを用いる代表的なツールが Steep, TypeProf
-
Steep を用いて静的型検査が行える
-
TypeProf を用いて型推論と簡易的な検査が行える
-
-
Sorbet はRBIという独自記法を用いる
-
RBSとは現状で互換性がない
-
アジェンダ
-
Ruby の静的型検査機能の概要
-
静的型検査を利用した開発手順
-
RBSの機能と利用例
-
ライブラリ利用時の静的型検査
-
まとめ
静的型検査を利用した開発手順
大まかに2つの手法が存在する
-
RBS をすべて記述する
-
Steep による厳密な型検査が可能
-
他の利用者にも型情報を提供できる
-
(TypeProfでRBSの雛形を作ることも可能)
-
-
RBS を記述せず、Ruby コードから推論させる
-
TypeProf による簡易的な型検査が可能
-
作業量を増やさず型の恩恵が得られる
-
実際に試して違いを見てみよう!
例題: IntStack
stack = IntStack.new
stack.push(1)
stack.push(2)
stack.push(10)
p stack.pop # => 10
p stack.pop + stack.pop # => 3
デモ
(Steepを利用した開発手法)
Steepfile
target :app do
# 検査対象ディレクトリ
check "lib"
# RBS配置ディレクトリ
signature "sig"
# 検査レベルの指定
configure_code_diagnostics(
Steep::Diagnostic::Ruby.all_error
)
end
RBSにpushの定義を記述すると、Ruby側でエラー表示
マウスホバーで型エラー内容を表示
push, pop の型を記述し、型だけ合わせて実装
(まだスタックとしては動かない)
インスタンス変数に配列を持つように変更
(push, popそれぞれで型エラー)
pushはnilを返すよう変更
popは @stack が空のときにデフォルト値を返すよう変更
デモ
(TypeProfを利用した開発手法)
型を推論させるため、利用側コードから記述
(この時点では undefined method)
型をあわせてpush, popを定義
Stackの機能を実装
デモ
(TypeProfでRBSの雛形を作る)
Steepを利用した実装から、RBSをすべて消した状態
(型エラーが出ている)
TypeProf で 型解析して RBS に出力
ここまでのまとめ
-
RBSを書くとコストは掛かるが各種メリットがある
-
Steepで静的型検査を厳密に行える
-
型定義ファイルをライブラリ利用者にも提供できる
-
-
TypeProfのみでもある程度の型エラーが検知できる
-
型記述のコストなしで恩恵が得られる
-
-
IDE拡張でエラー表示などの開発サポートが得られる
-
Language Server Protocolで実現されているため、
VSCode以外でも利用可能
-
アジェンダ
-
Ruby の静的型検査機能の概要
-
静的型検査を利用した開発手順
-
RBSの機能と利用例
-
ライブラリ利用時の静的型検査
-
まとめ
RBSの機能と利用例
-
RBSには高度な型を表現するための各種機能がある
-
Union Type ( T | U )
-
Generics ( T[U] )
-
Covariant, Contravariant( out T, in T )
-
etc...
-
-
機能を知るための資料
-
rbsリポジトリのdocs/syntaxに文法がまとまっている
-
rbsリポジトリのcoreに標準ライブラリの型定義がある
-
ジェネリクスを使った型定義
例題: Stack
stack1 = Stack.new
stack.push(1)
stack.push(2)
p stack.pop + stack.pop # => 3
stack2 = Stack.new
stack.push('a')
stack.push('b')
p stack.pop + stack.pop # => 'ba'
stack1.push('a') # TypeError
デモ
(Stackの実装)
IntStack を Stack にリネーム
Stack#push に文字列を渡すと型エラー
Stack の型定義を Stack[T] にし、Integer を T に置き換え
(pop でデフォルト0を返すところが型エラー)
initialize で default を受け取るように変更
Integer でも String でも動くようになり、
Integer をデフォルトにしたstackに String を push すると型エラー
参考: Bounded Generics
class PrettyPrint[T < _Output]
interface _Output
def <<: (String) -> void
end
attr_reader output: T
end
昨年末リリースされた Ruby 3.1 (RBS 2.0) で追加
(Steepでの型検査はまだ動かない模様)
ここまでのまとめ
-
RBS には高度な型を表現するための各種機能がある
-
rbsのsyntaxドキュメントを見ると一通り把握できる
-
-
一例としてジェネリクスを紹介した
-
「任意の型に関する型」を表現できる
-
Ruby 3.0 ではBounded Genericsが入り強化された
-
アジェンダ
-
Ruby の静的型検査機能の概要
-
静的型検査を利用した開発手順
-
RBSの機能と利用例
-
ライブラリ利用時の静的型検査
-
まとめ
ライブラリ利用時の静的型検査
-
外部ライブラリの型情報をどうするか
-
gem_rbs_collection に型情報レジストリがある
-
型が提供されていないものは、自分で書くか
その部分の型検査を緩める必要がある
-
-
rbs collection コマンドで Gemfile から依存を解析し、
対応する型情報を gem_rbs_collection から取得できる-
Steep などのツールの依存性解決もやってくれる
-
デモ
(RBS Collectionを利用した開発手法)
外部Gemの ULID と KSUID を使うコード
(いずれも型情報がないため untyped でエラー)
rbs_collection.yaml に設定を記述し、
rbs collection install を実行
コミュニティで型情報が提供されている ULID は
型が解決できるようになった
KSUID は自分で RBS を記述することで型を解決
( vendor/rbs 以下も sig ソースとして Steepfile に設定)
Steepの検査設定をstrictなどにして
untyped の型検査をしないという選択肢もある
Rails利用時の静的型検査
アジェンダ
-
Ruby の静的型検査機能の概要
-
静的型検査を利用した開発手順
-
RBSの機能と利用例
-
ライブラリ利用時の静的型検査
-
まとめ
まとめ
-
静的型検査を活用したRubyの開発について紹介した
-
RBS を書いて Steep で型検査すると、
静的型付け言語のような開発体験・安全性が得られる -
TypeProf を使うと、これまでと同じ感覚で
Rubyを書きながら型の恩恵が(多少)得られる -
IDE拡張を利用すると良好な開発体験を得られる
-
-
RBS自体や周辺ツールもどんどん進化している
-
ruby-jp Slackの #types チャンネルに入ろう!
RBSから始める静的型付け生活
By 黒曜