Можно ли переопределить поле типа?

scala> class C
defined class C

scala> class subC extends C
defined class subC

scala> class A { type T = C}
defined class A

scala> class subA extends A { override type T = subC}
<console>:10: error: overriding type T in class A, which equals C;
 type T has incompatible type
      class subA extends A { override type T = subC}
                                           ^

В приведенном выше примере появляется сообщение об ошибке, что я не могу переопределить поле типа в классе A (даже если выбранный тип subC расширяет класс C).

Является ли переопределение поля типа вообще возможным? И если да, что не так с приведенным выше примером?

Ответы

Ответ 1

Вы бы не говорили о "переопределении" по отношению к типам, а скорее о сужении своих границ.

  • type T... без границ
  • type T <: C... T есть C или подтип C (который называется верхней границей)
  • type T >: C... T есть C или супертип C (который называется нижней границей)
  • type T = C... T - это точно C (тип псевдонима)

Следовательно, если T является типом элемента trait A, а SubA является подтипом A, в случае (2) SubA может сужаться T до более конкретного подтипа C, тогда как в случае (3) он может сузить его до высшего супертипа C. Случай (1) не налагает никаких ограничений на SubA, а случай (4) означает, что T является "окончательным", так сказать.


Это имеет последствия для использования T в A - может ли он отображаться как тип аргумента метода или возвращаемый тип метода.

Пример:

trait C { def foo = () }
trait SubC extends C { def bar = () }

trait MayNarrow1 {
  type T <: C  // allows contravariant positions in MayNarrow1
  def m(t: T): Unit = t.foo  // ...like this
}

object Narrowed1 extends MayNarrow1 {
   type T = SubC
}

object Narrowed2 extends MayNarrow1 {
  type T = SubC
  override def m(t: T): Unit = t.bar
}

В MayNarrow1 можно определить метод m, потому что тип T встречается в контравариантной позиции (как тип аргумента метода), поэтому он по-прежнему действителен, даже если T сужается в подтипе MayNarrow1 (тело метода может обрабатывать T, как если бы это был тип C).

Напротив, type T = C неизбежно фиксирует T, что было бы похоже на создание метода final. Исправив T, его можно использовать в ковариантной позиции (в качестве возвращаемого типа метода):

trait Fixed extends MayNarrow1 {
  type T = C   // make that T <: C to see that it won't compile
  final def test: T = new C {}
}

Теперь вы можете легко видеть, что это должно быть запрещено для дальнейшего "переопределения" T:

trait Impossible extends Fixed {
  override type T = SubC

  test.bar  // oops...
}

Чтобы быть полным, здесь представлен менее общий случай нижней границы:

trait MayNarrow2 {
  type T >: SubC  // allows covariant positions in MayNarrow2
  def test: T = new SubC {}
}

object Narrowed3 extends MayNarrow2 {
  type T = C
  test.foo
}

object Narrowed4 extends MayNarrow2 {
  type T = C
  override def test: T = new C {}
}