Search code examples
inheritanceabstractcrystal-lang

Crystal: how to implement multiple abstract methods with one method in child?


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

Solution

  • 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