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}
^
In the example above, I get an error message, that I can not override the type field in class A
( even if the chosen type subC
extends the class C
).
Is overriding a type field possible at all ? And if yes, what is wrong with the example above ?
You wouldn't speak of 'overriding' with respect to types, but rather narrowing their bounds.
type T
... no bounds
type T <: C
... T
is C
or a subtype of C
(which is called upper bound)
type T >: C
... T
is C
or a supertype of C
(which is called lower bound)
type T = C
... T
is exactly C
(type alias)
Therefore, if T
is a type member of trait A
, and SubA
is a subtype of A
, in case (2) SubA
may narrow T
to a more particular subtype C
, whereas in case (3) it could narrow it to a higher supertype of C
. Case (1) doesn't impose any restrictions for SubA
, while case (4) means that T
is 'final' so to speak.
This has consequences for the useability of T
in A
—whether it may appear as a method argument's type or a method's return type.
Example:
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
}
It is possible to define method m
in MayNarrow1
because type T
occurs in contravariant position (as a method argument's type), therefore it is still valid even if T
is narrowed down in a subtype of MayNarrow1
(the method body can treat t
as if it were type C
).
In contrast, type T = C
inevitably fixes T
, which would kind of correspond to making a method final
. By fixing T
, it can be used in a covariant position (as a method's return type):
trait Fixed extends MayNarrow1 {
type T = C // make that T <: C to see that it won't compile
final def test: T = new C {}
}
You can now easily see that it must be forbidden to further 'override' T
:
trait Impossible extends Fixed {
override type T = SubC
test.bar // oops...
}
To be complete, here is the less common case of a lower bound:
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 {}
}