Suppose I have an abstract struct that needs to operate on two kinds of inputs like so (for more context, see previous SO question).
abstract struct Numberlike
alias Num = (Int32 | Float64)
abstract def -
abstract def -(other : self)
abstract def -(other : Num)
end
If my implementation can use self
and Num
interchangeably, it seems reasonable to just put them together:
struct Term < Numberlike
alias Num = (Int32 | Float64)
getter coeff : Num
getter sym : Symbol
def initialize(@coeff, @sym); end
def -(other : self | Num)
self.class.new(coeff - other, sym)
end
def -
self.class.new(-coeff, sym)
end
end
The truth is though that since the type is self | Num
it matches neither the self
nor the Num
requirement of the abstract class.
You can see for yourself in this playground.
Is there a way to combine these like I want? I'd rather not duplicate the code unnecessarily (i.e. the following compiles but I don't like it):
struct Term < Numberlike
alias Num = (Int32 | Float64)
getter coeff : Num
getter sym : Symbol
def initialize(@coeff, @sym); end
def -(other : self)
self.class.new(coeff - other, sym)
end
def -(other : Num)
self.class.new(coeff - other, sym)
end
def -
self.class.new(-coeff, sym)
end
end
The best I've been able to come up with, is to define the joined method in the abstract class, somewhat similar to what Samual is talking about. If done this way, the implementing structs are free to define each separately or all combined.
abstract struct Addable
abstract def +(other : self)
abstract def +(other : Int32)
def +(other : self | Int32)
if other.is_a?(Int32) ? self + other : self + other
end
end
The way this works is that if they are defined separately by you, then the combined method comes along for type safety but isn't used. If you define them together, you override the third method, but the other two won't give you trouble because a program which satisfies the third condition satisfies the first two.
Here's a demonstration: https://play.crystal-lang.org/#/r/6y3j