As I already noted in a comment, type variance can only be talked of in relation to a type parameter. A type itself isn't covariant or contravariant or invariant. Since K
does not appear in the type parameter list of Cat
, Cat
has no variance in relation to K
. Consider:
trait Cat[T] {
def meow[K]
}
class SiameseCat[T] extends Cat[T] {
def meow[K] = println("loud meow")
}
class Foo
class Bar extends Foo
class Baz extends Bar
val barSiamese = new SiameseCat[Bar]
// COMPILATION ERROR: personality.analysis.demo.Bar <: personality.analysis.demo.Foo, but class SiameseCat is invariant in type T
val fooSiamese: SiameseCat[Foo] = barSiamese
// SAME
val bazSiamese: SiameseCat[Baz] = barSiamese
// NO ERROR
barSiamese.meow[Foo]
barSiamese.meow[Bar]
barSiamese.meow[Baz]
barSiamese.meow[Int]
barSiamese.meow[Unit]
Arguably, in more lax speech, you can say a type is *variant if it's obviously a container type and takes just one type parameter, such as List[T]
; i.e. one can say List
is covariant, but this actually expands to "List[T]
is covariant with respect to T
".
However, if K
did in fact appear in the type parameter list of Cat
, it would make it possible to declare Cat
as covariant with respect to K
by prepending a +
to K
: Cat[T, +K]
, which will be allowed by the compiler because K
only appears in variance-neutral positions in the body of Cat
:
def meow[K] // <-- meow doesn't take any parameters and returns `Unit`, so `K` is irrelevant with respect to variance
if, however, you were returning K
from meow
, you'd only be able to mark Cat
as invariant or covariant with respect to K
:
def meow: K // contravariance made impossible
conversely, this:
def meow(x: K) // covariance made impossible
would force you to either go with Cat[T, -K]
(contravariant) or just Cat[T, K]
(invariant).
For the reason why, either google, or see a recent answer of mine @ why the first type parameter is defined as contravariant in Function1[-A, +B]?