👩‍🏫

初めてのソルベ(Sorbet) Part 1

2024/03/25に公開

私は主にJavaScript、TypeScript、およびJavaで仕事をしてきましたが、今回は初めてRakushifuプロジェクトに参加し、Ruby on RailsとSorbetを使用しています。

What is Sorbet?

Sorbetは、コードが実行される前にミスをチェックすることで、開発者がより良いRubyコードを書くのを支援するツールです。コードのスペルチェッカーのようなものであり、エラーを早期にキャッチして後でのバグを防ぎます。コードに型注釈を追加することで、Sorbetはすべてが正しく組み合わさることを確認し、コードを理解しやすく、保守しやすくします。
簡単に言うと、
Sorbet は、あなたのコードのための熟練した折り紙の先生のようなものです。それは注意深くコードの各行を検査し、正しく折りたたまれてスムーズに組み合わされることを確認します。開発者は、コードに注釈を追加することで、Sorbet にコードの構造と配置方法のヒントを提供します。入念な折り紙作家が段階的に作品を構築するのと同様に、Sorbet は開発者がコードベースに段階的に型情報を追加できるようにし、方法論的でエラーに強いプロセスを確保します。

How Sorbet works?

まず最初に、RubyコードでSorbetを使用するためには、以下を追加してそれを有効にする必要があります。

# typed: strict

Method Signature

メソッドシグネチャは、Rubyコード内のメソッドに関する追加情報を提供する注釈です。メソッドが期待するパラメータの型や、返す値の型を指定します。メソッドシグネチャは、Sorbetがメソッドの使用方法を理解し、より正確な型チェックを行うのを支援します。
例)

# typed: strict
extend T::Sig
sig {params(x: Integer, y: Integer).returns(Integer)}
def foo(x, y)
  sum = x + y
  sum
end
foo(4, 5)

この例では、# typed: strict は型チェックを有効にします。
sig {params(x: Integer, y: Integer).returns(Integer)} はメソッドシグネチャで、sig {params(x: Integer, y: Integer) は期待される入力パラメータの型を表し、returns(Integer) は期待される戻り値の型を表します。

Type Assertion

Sorbetでは、型アサーションはRubyコード内の変数や式の期待される型を明示的に定義する明言です。これにより、型チェッカーが正確な型の検証を行うのを支援します。
例)

x = T.let(10, Integer)
y = T.let(10, String) # error: Argument does not have asserted type String

ここでは、10は整数なので、T.let()を使用しました。これは2つのパラメータ、値と型を取ります(値 -> 10、型 -> Integer)。

T.let(expr, Type)

上記の例で述べたように、T.let(10, Integer)は変数xが整数型であることをSorbetに明示的に伝えます。これにより、コード全体で整数として扱われることが保証されます。

T.must(value/expr)

T.mustは、値がnilでないことをアサートするために使用されるユーティリティメソッドです。値がnilであってはならないと確信している場合に一般的に使用され、その仮定をSorbetに伝えたいときに使用されます。

# typed: strict
name = T.must(params[:name])

T.must()を使うことで、Sorbetはnamenilであることはないと理解し、その型を推論します。

T.cast(value, Type)

T.cast(value, Type)は、プログラムがそれ以外では明確でない場合でも、特定の値の型を指定するのを支援するSorbetのメソッドです。
例えば、Integer | String型の変数xがある場合(つまり、整数または文字列のどちらかであることを意味します)。 xを整数として扱いたい場合、T.castを使用して明示的に整数にキャストすることができます。

x = 10
y = T.cast(x, Integer)  # Cast x to Integer

これにより、yが整数として扱われることが保証されます。たとえ x が潜在的に文字列である場合でも、y は整数として扱われます。xが実際に文字列である場合、Sorbetは実行時に型エラーを発生させます。

T.bind()

T.bindは、特定のコンテキストや型環境にコードブロックをバインドするために使用されるメソッドです。
例えば、文字列引数を取るメソッドoperate_stringがあるとします。

def operate_string(str)
    # We want to make sure that any code inside this block
    # treats `str` as a string, even if Sorbet isn't sure
    # what type it is.
  T.bind(String, str) do
    # Now, any code inside this block will treat `str` as a string
    puts str.upcase
  end
end
# Usage
operate_string("hello world")

簡単に言うと、T.bind()は特定のコードブロックにメガネをかけるようなものです。
T.bind(self, Type)を使用すると、Sorbetに、現在のオブジェクト(self)を特定の型(Type)として見るようにメガネをかけるように伝えています。これは、「このコードのこの部分では、selfを特定の型として扱ってね」と言っているようなものです。
T.bind(Type, variable)を使用すると、Sorbetに、特定の変数を特定の型として見るようにメガネをかけるように伝えています。これは、「このブロック内では、この変数をこの型として想像してみよう」と言っているようなものです。

T.assert_type!(value/expr, Type)

T.assert_type!()は、変数や式が指定された型と一致することをアサートまたは検証するために使用されるメソッドです。通常、実行時に型の正確性を強制するために使用されます。変数や式の型が指定された型と一致しない場合、Sorbetは実行時にエラーを発生させます。

# typed: strict
# Let's say we expect a variable to be an Integer
x = T.let("hello", Integer)
# Now, we want to make sure it's really an Integer
T.assert_type!(x, Integer)  # This will raise an error at runtime

T.unsafe

T.unsafeは、Sorbetに対して、「この特定の部分のコードの型をチェックする必要はないよ」と伝えるようなものです。特定の式やコードブロックに対して一時的にSorbetの型チェックを無効にする方法です。

# Concatenating strings with different encodings
T.unsafe do
  result = "Hello, ".concat("world".force_encoding(Encoding::UTF_16LE))
end

ここでは、T.unsafeが使用されています。これは、潜在的に異なるエンコーディング(UTF-8とUTF-16LE)を持つ2つの文字列を連結するために、一時的にSorbetの型チェックを無効にするためのものです。Sorbetが文字列の正確なエンコーディングをコンパイル時に推論できない場合、このシナリオでT.unsafeを使用することで、型チェックエラーをトリガーせずに操作を実行できます。

Resources

Sorbet Online Playground
https://sorbet.run/


https://x-bit.co.jp/recruit/
https://herp.careers/v1/xbit
https://note.com/xbit_recruit

クロスビットテックブログ

Discussion