Как мы можем решить проблему алмазного наследования абстрактных типов в scala 2.13 / 3.x?

#scala #path-dependent-type #abstract-type

#scala #path-dependent-type #абстрактный тип

Вопрос:

Вот простой пример:

 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
  

Очевидно, что это вызовет ошибку компиляции, поскольку оба override type A являются взаимоисключающими. Это противоречит интуиции, поскольку Product и Serializable обычно используются вместе. В качестве альтернативы я могу определить:

 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
}
  

Это делает определение a и b недействительным, поскольку тип A не был конкретизирован, кроме того, строка override type A = Product with Serializable явно является шаблонной и может быть выведена вместо этого.

Каков правильный способ определить абстрактный тип, который допускает смешивание алмазов, избегая при этом шаблонного явного определения его во всех реализациях?

Комментарии:

1. override type A = Product with Serializable не является шаблонным (его нельзя вывести). Можно было бы определить object SS extends Sub1 with Sub2 { case class MyClass(i: Int); override type A = MyClass; override def a = MyClass(1) } . MyClass отличается от Product with Serializable типа. Вы должны указать, какой тип вы имеете в виду в object SS .

2. Вероятно, можно было бы сделать вывод, что это object SS extends Sub1 with Sub2 { override type A <: Product with Serializable } (без def a ). Что ж, Scala решила быть здесь более явной.

3. В вашем случае MyClass <: Product с Serializable , поэтому это сужение необязательно. Но вы правы, шаблонность не является важной проблемой, поэтому я меняю свой пример на что-то более серьезное (оправданное, но не может быть скомпилировано)

Ответ №1:

Я думаю, вы потеряли нижние границы.

"s" -> "s" имеет тип (String, String) , который является подтипом Product Serializable ) , но не подтипом A <: Product (или A <: Serializable ).

Попробуйте

 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)]
  

Если вы укажете возвращаемый тип Sub1#a , Sub2#b чтобы быть A (выше они были выведены как (String, String) , т. е. возвращаемый тип был сужен при переопределении метода), то

 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)]
  

Вы можете сделать даже

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