id | title | sidebar_label |
---|---|---|
abstract |
Abstract Classes and Interfaces |
Abstract Classes & Interfaces |
Sorbet supports abstract classes, abstract methods, and interfaces. Abstract methods ensure that a particular method gets implemented anywhere the class or module is inherited, included, or extended. An abstract class or module is one that contains one or more abstract methods. An interface is a class or module that must have only abstract methods.
Keep in mind:
abstract!
can be used to prevent a class from being instantiated.- Both
abstract!
andinterface!
allow the class or module to haveabstract
methods. - Mix in a module (via
include
orextend
) to declare that a class implements an interface.
Note: Most of the abstract and override checks are implemented statically, but some are still only implemented at runtime, most notably variance checks.
To create an abstract method:
- Add
extend T::Helpers
to the class or module (in addition toextend T::Sig
). - Add
abstract!
orinterface!
to the top of the class or module. (All methods must be abstract to useinterface!
.) - Add a
sig
withabstract
to any methods that should be abstract, and thus implemented by a child. - Declare the method on a single line with an empty body.
module Runnable
extend T::Sig
extend T::Helpers # (1)
interface! # (2)
sig {abstract.params(args: T::Array[String]).void} # (3)
def main(args); end # (4)
end
To implement an abstract method, define the method in the implementing class or
module with an identical signature as the parent, except replacing abstract
with override
.
class HelloWorld
extend T::Sig
include Runnable
# This implements the abstract `main` method from our Runnable module:
sig {override.params(args: T::Array[String]).void}
def main(args)
puts 'Hello, world!'
end
end
There are some additional stipulations on the use of abstract!
and
interface!
:
- All methods in a module marked as
interface!
must have signatures, and must be markedabstract
.- Note: this applies to all methods defined within the module, as well as any that are included from another module
- A module marked
interface!
can't haveprivate
orprotected
methods. - Any method marked
abstract
must have no body.sorbet-runtime
will take care to raise an exception if an abstract method is called at runtime. - Classes without
abstract!
orinterface!
must implement allabstract
methods from their parents. extend MyAbstractModule
works just likeinclude MyAbstractModule
, but for singleton methods.abstract!
classes cannot be instantiated (will raise at runtime).
abstract
singleton methods on a module are not allowed, as there's no way to
implement these methods.
module M
extend T::Sig
extend T::Helpers
abstract!
sig {abstract.void}
def self.foo; end
end
M.foo # error: `M.foo` can never be implemented
A somewhat common pattern in Ruby is to use an included
hook to mix class
methods from a module onto the including class:
module M
module ClassMethods
def foo
self.bar
end
end
def self.included(other)
other.extend(ClassMethods)
end
end
class A
include M
end
# runtime error as `bar` is not defined on A
A.foo
This is hard to statically analyze, as it involves looking into the body of the
self.included
method, which might have arbitrary computation. As a compromise,
Sorbet provides a new construct: mixes_in_class_methods
. At runtime, it
behaves as if we'd defined self.included
like above, but will declare to srb
statically what module is being extended.
We can update our previous example to use mixes_in_class_methods
, which lets
Sorbet catch the runtime error about bar
not being defined on A
:
# typed: true
module M
extend T::Helpers
interface!
module ClassMethods
extend T::Sig
extend T::Helpers
abstract!
sig {void}
def foo
bar
end
sig {abstract.void}
def bar; end
end
mixes_in_class_methods(ClassMethods)
end
class A # error: Missing definition for abstract method
include M
extend T::Sig
sig {override.void}
def self.bar; end
end
# Sorbet knows that `foo` is a class method on `A`
A.foo
-
Sorbet has more ways to check overriding than just whether an abstract method is implemented in a child. See this doc to learn about the ways to declare what kinds of overriding should be allowed.
-
Abstract classes and interfaces are frequently used with sealed classes to recreate a sort of "algebraic data type" in Sorbet.