7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

Sorbetを使ってRubyでAbstract Classを使う

Last updated at Posted at 2023-07-14

RubyKaigiで以下のセッションでRBIとRBSの比較がいろいろ紹介されていました。
その中で、RBIの機能としてAbstract classの説明があり、面白そうだったので使ってみました。

Abstract Classについて

Abstract Class, Abstract Moduleは1つ以上のAbstract Methodを持つクラス、モジュールです。

以下のように定義します。

base_engineer.rb
# 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')

image.png

また、以下のようにAbstract Methodを定義したとき、 BaseEngineerを継承やinclude, extendしたclass等で greet methodを定義しなかった場合、これも気づくことができます。

base_engineer.rb
# typed: true

class BaseEngineer
  extend T::Helpers
  extend T::Sig

  abstract!

  def initialize(name:)
    @name = name
  end

+ sig { abstract.void }
  def greet; end
end
backend_engineer.rb
# typed: true

class BackendEngineer < BaseEngineer
end

image.png

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
image.png
Abstract Method
image.png

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

image.png

RBIには Interface もあるのですが、interface! を使った場合、すべてのメソッドがAbstract Methodだけという制約がつきます。

image.png

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としての機能が拡張されている感じがしておもしろいなと思いました。

参考

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?