tribbloid
tribbloid

Reputation: 3838

In scala 2.13+ / 3.x, how can we address the diamond inheritance problem of abstract types?

Here is a simple example:

trait Sup {

  type A

  def a: A

  def b: A
}

trait Sub1 extends Sup {

  override type A = Product

  override def a = "s" -> "s"
}

trait Sub2 extends Sup {

  override type A = Serializable

  override def b = "s" -> "s"
}

object SS extends Sub1 with Sub2

Obviously it will cause a compilation error, as both override type A are mutually exclusive. This is counter-intuitive as Product & Serializable are commonly used together. Alternatively, I can define:

trait Sup {

  type A

  def a: A
}

trait Sub1 extends Sup {

  override type A <: Product

  override def a = "s" -> "s"
}

trait Sub2 extends Sup {

  override type A <: Serializable

  override def b = "s" -> "s"
}

object SS extends Sub1 with Sub2 {

  override type A = Product with Serializable
}

This makes definition of a and b invalid, as type A hasn't been reified, in addition, the line override type A = Product with Serializable is clearly a boilerplate and can be inferred instead.

What is the correct way to define an abstract type that allows diamond mixin, while avoid the boilerplate to explicitly define it in every implementations?

Upvotes: 1

Views: 131

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

I guess you lost lower bounds.

"s" -> "s" has type (String, String), which is a subtype of Product (and Serializable) but not a subtype of A <: Product (or A <: Serializable).

Try

trait Sup {
  type A
  def a: A
  def b: A
}

trait Sub1 extends Sup {
  override type A >: (String, String) <: Product
  override def a = "s" -> "s"
}

trait Sub2 extends Sup {
  override type A >: (String, String) <: Serializable
  override def b = "s" -> "s"
}

object SS extends Sub1 with Sub2 {
  override type A = Product with Serializable
}

SS.a: (String, String)
SS.b: (String, String)
implicitly[SS.A =:= (Product with Serializable)]

If you specify return type of Sub1#a, Sub2#b to be A (above they were inferred to be (String, String) i.e. return type was narrowed upon method overriding) then

trait Sup {
  type A
  def a: A
  def b: A
}

trait Sub1 extends Sup {
  override type A >: (String, String) <: Product
  override def a: A = "s" -> "s"
}

trait Sub2 extends Sup {
  override type A >: (String, String) <: Serializable
  override def b: A = "s" -> "s"
}

object SS extends Sub1 with Sub2 {
  override type A = Product with Serializable
}

SS.a: Product with Serializable
SS.b: Product with Serializable
implicitly[SS.A =:= (Product with Serializable)]

You can do even

object SS extends Sub1 with Sub2 {
  override type A >: (String, String) <: Product with Serializable
}

Upvotes: 5

Related Questions