I have the following:
class Coordinate < T::Struct
const :x, Integer
const :y, Integer
end
class ThreeDCoordinate < T::Struct
const :x, Integer
const :y, Integer
const :z, Integer
end
What I want is to have my ThreeDCoordinate
inherit x
and y
of Coordinate
so I don't have to rewrite them in ThreeDCoordinate
. How can I accomplish this?
CodePudding user response:
As far as I know, Sorbet does not support exactly the feature that you asked.
However, I am using interfaces to achieve similar typing goals:
module Coordinate
extend T::Helpers
extend T::Sig
interface!
sig { abstract.returns(Integer) }
def x; end
sig { abstract.returns(Integer) }
def y; end
end
module ThreeCoordinate
extend T::Helpers
extend T::Sig
include Coordinate
interface!
sig { abstract.returns(Integer) }
def z; end
end
class ThreeDLocation < T::Struct
extend T::Sig
include ThreeCoordinate
# Will be complained by Sorbet because of missing interface
const :x, Integer
end
class ThreeDCenter < T::Struct
extend T::Sig
include ThreeCoordinate
sig { override.returns(Integer) }
def x
0
end
sig { override.returns(Integer) }
def y
0
end
sig { override.returns(Integer) }
def z
0
end
end
Try out on Sorbet Playground to see how Sorbet static checks help ensure the typing.
Although there might be some repetitions in the codes, we have achieved the goal that classes implementing ThreeCoordinate
is guaranteed to have the interface of Coordinate
as well.
Also, using this approach, the implementation is more flexible. We can also implement something like ThreeDCenter
above without being tied down to using Struct properties only.
CodePudding user response:
There is a way to do this, using T::InexactStruct
, but you will have to give up being able to strongly type the initializer of your structs:
# typed: true
class Coordinate < T::InexactStruct
const :x, Integer
const :y, Integer
end
class ThreeDCoordinate < Coordinate
const :z, Integer
end
coord = Coordinate.new(x: 2, y: 3)
T.reveal_type(coord.x)
T.reveal_type(coord.y)
threeD = ThreeDCoordinate.new(x: 2, y: 3, z: 4)
T.reveal_type(threeD.x)
T.reveal_type(threeD.y)
T.reveal_type(threeD.z)
# Note that the constructors are not typed anymore:
Coordinate.new(x: "foo", y: :bar) # This should fail but doesn't
The problem with T::Struct
and subclassing is that Sorbet creates an initializer for your struct that takes into account all of its declared fields. So for Coordinate
the initializer has the signature params(x: Integer, y: Integer).void
but for ThreeDCoordinate
it has the signature params(x: Integer, y: Integer, z: Integer).void
. Now these signatures are not compatible with each other, so Sorbet does not allow you to subclass one from the other.
T::InexactStruct
allows you to give up the strong typing in the constructor and trade it in for being able to do inheritance on typed structs.