tengo lo siguiente:
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
Lo que quiero es que mi ThreeDCoordinate
herede x
e y
de Coordinate
para no tener que volver a escribirlos en ThreeDCoordinate
. ¿Cómo puedo lograr esto?
Hay una manera de hacer esto, usando T::InexactStruct
, pero tendrá que dejar de escribir fuertemente el inicializador de sus estructuras:
# 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
El problema con T::Struct
y las subclases es que Sorbet crea un inicializador para su estructura que tiene en cuenta todos sus campos declarados. Entonces, para Coordinate
el inicializador tiene los params(x: Integer, y: Integer).void
pero para ThreeDCoordinate
tiene los params(x: Integer, y: Integer, z: Integer).void
. Ahora bien, estas firmas no son compatibles entre sí, por lo que Sorbet no le permite subclasificar una de la otra.
T::InexactStruct
le permite renunciar a la tipificación fuerte en el constructor y cambiarla por poder hacer herencia en estructuras tipeadas.
Hasta donde yo sé, Sorbet no es compatible exactamente con la función que solicitaste.
Sin embargo, estoy usando interfaces para lograr objetivos de escritura similares:
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
Pruébelo en Sorbet Playground para ver cómo las comprobaciones estáticas de Sorbet ayudan a garantizar la escritura.
Aunque puede haber algunas repeticiones en los códigos, hemos logrado el objetivo de garantizar que las clases que implementan ThreeCoordinate
tengan la interfaz de Coordinate
.
Además, utilizando este enfoque, la implementación es más flexible. También podemos implementar algo como ThreeDCenter
arriba sin estar atados a usar solo las propiedades de Struct.