Rubyに静的型付けSorbetを触ってみる
ついにRubyでも静的型付けができるようになりましたね。
Typescriptなど少し前から、静的型付けが熱いですがやっとRubyでも!
僕自身、業務上ではRubyを使っていて、動的型付けに悩まされることもあるので嬉しいです!
とはいえ、触ってみないとわからないため、今回はそのメモです。
今回はSorbetのPlaygroundで簡単に触ってみます。
Sorbetとは?
Gradual Type Checkingは段階的な型チェック
つまり、ファイル単位やメソッド単位など細かい粒度で採用していけるということ。
「Sorbetを採用したいけどシステムが大規模...」
「どれだけの時間がかかるか...」
といったことがなく、1つ1つのファイル対応や重要な処理を優先的にできる!
もちろん全て対応できた方がいいと思います...
メソッドシグネチャ
コード内で型チェックを可能にするための主な方法
# typed: true require 'sorbet-runtime' class Sample # シグネチャを使用するため、クラス・モジュールのトップに必要 extend T::Sig sig {params(x: Integer, y: Integer).returns(Integer)} def self.sum(x, y) x + y end end Sample.sum(1, 2) # => 3 Sample.sum('aa', 'bb') # => error
sig {params(x: Integer, y: Integer).returns(Integer)}
これは、引数xとyがInteger型、戻り値がIntegerという意味です。
以下のように複数行でも可能なため、長くなりそうな時でも可読性は問題なさそうです。
sig do params( x: Integer, y: Integer ) .returns(Integer) end
戻り値がない場合は
sig {void} def self.print_bar puts 'bar' end
とかけます
型アサーション
種類としては4つあります。
T.let(expo, Type)
T.cast(expo, Type)
T.must(expo)
T.assert_type!(express, Type)
T.let
変数代入で型指定ができる
x = T.let(10, Integer) y = T.let(10, String) => error
T.cast
# typed: true require 'sorbet-runtime' extend T::Sig class A; def foo; end; end class B; def bar; end; end sig {params(label: String, a_or_b: T.any(A, B)).void} def foo(label, a_or_b) case label when 'a' a_or_b.foo # Not enough arguments provided for method Object#foo on B component of T.any(A, B). when 'b' a_or_b.bar # Method bar does not exist on A component of T.any(A, B) end end
これは、引数にそんなメソッドはないと言われています。 そのため以下のようにcastしてclassを明示してあげる必要があります。
case label when 'a' T.cast(a_or_b, A).foo when 'b' T.cast(a_or_b, B).bar end
T.must
変数の中身がnilだとエラーを出す。
使われていない変数の検知やnilが入って欲しくない変数に使えそう?
class A extend T::Sig sig {void} def foo x = T.let(nil, T.nilable(String)) y = T.must(nil) puts y # error: This code is unreachable end sig {void} def bar vals = T.let([], T::Array[Integer]) x = vals.find {|a| a > 0} T.reveal_type(x) # Revealed type: T.nilable(Integer) y = T.must(x) puts y # no static error end end
T.assert_type!
以下では、assert_type
でxはString型と断言しているが、xは型がないためエラーになっています。
class A extend T::Sig sig {params(x: T.untyped).void} def foo(x) T.assert_type!(x, String) # error here end end
まとめ
今回はPlaygroundを使って、どんな感じなのかを触ってみました。
まだ触り始めたばかりですが、型があることで変数に何が入ってくるのかわかりいいですね。
あと段階的に型に対応していけるのは嬉しいですね!