RubyKaigiで以下のセッションでRBIとRBSの比較がいろいろ紹介されていました。
その中で、RBIの機能としてAbstract classの説明があり、面白そうだったので使ってみました。
Abstract Classについて
Abstract Class, Abstract Moduleは1つ以上のAbstract Methodを持つクラス、モジュールです。
以下のように定義します。
# typed: true
class BaseEngineer
extend T::Helpers
extend T::Sig
+ abstract!
def initialize(name:)
@name = name
end
end
すると、このAbstract Classのインスタンスを作成しようとすると、型エラーを発生させます。
# typed: true
engineer = BaseEngineer.new(name: 'kyntk')
また、以下のようにAbstract Methodを定義したとき、 BaseEngineer
を継承やinclude, extendしたclass等で greet
methodを定義しなかった場合、これも気づくことができます。
# typed: true
class BaseEngineer
extend T::Helpers
extend T::Sig
abstract!
def initialize(name:)
@name = name
end
+ sig { abstract.void }
def greet; end
end
# typed: true
class BackendEngineer < BaseEngineer
end
Rubyには他の言語のようにAbstract ClassやAbstract Methodは存在しません。
Rubyで実装しようとした場合、以下のようにエラーをraiseする方法があります。
しかしこれらはランタイムエラーであり、実行するまで気づくことができません。
class AbstractClassError < StandardError; end
class BaseEngineer
def initialize
raise AbstractClassError
end
def greet
raise NotImplementedError
end
end
そこで、SteepやSorbetを使うことで、プログラムの実行前に気づくことができます。それに加えて、これらのエラーはエディタの拡張を用いれば実装中に即座に気づくことができます。
Abstract Class
Abstract Method
Mixinで使う
継承以外にもinclude等で使うこともできます。
# typed: true
module Programmer
extend T::Helpers
extend T::Sig
abstract!
sig { abstract.returns(String) }
def language; end
end
# typed: true
class BackendEngineer
include Programmer
end
RBIには Interface もあるのですが、interface!
を使った場合、すべてのメソッドがAbstract Methodだけという制約がつきます。
Abstract Methodをオーバーライドする
override
を使ってメソッドの定義を行います。
# typed: true
class BackendEngineer
extend T::Sig
include ProgrammerInterface
sig { params(language: String).void }
def initialize(language: )
@language = T.let(language, String)
end
sig { override.returns(String) }
def language
@language
end
end
その他
ドキュメントを見ると、色々と柔軟にできそうです。
Rubyとしての機能が拡張されている感じがしておもしろいなと思いました。
参考