I am studying variance in scala right now, and I think I have a good understanding of contravariance. For example given trait List[-A]
, I know that List[Int]
is a supertype of List[AnyVal]
.
But say that I have the following trait:
trait List[+A] {
def cons(hd: A): List[A]
}
Why is cons
parameter type wrong?
Why it is necessary to have def cons[B >: A](v: B): List[B]
?
For example:
val animal_list: List[Animal] = List(tiger, dog)
if we call:
animal_list.cons(tiger)
since Tiger <: Animal
, doesn't cons
ran into problem? Since B
is Tiger
and A
is Animal
and B >: A
is not true.
cons
's parameter type wrong?trait List[+A] {
def cons(hd: A): List[A]
}
Compiler give you error:covariant type A occurs in contravariant position in type A of value hd
because method parameters count as contravariant positions, but A
is covariant.
Let's imagine that this method declaration would compile. Then we could do:
class ListImpl[A] extends List[A] {
override def cons(hd: A): List[A] = ???
}
val strings: List[String] = new ListImpl[String]
val values: List[Any] = strings // OK, since List[String] <: List[Any] (in List[A], A is covariant)
values.cons(13) // OK(??), since values's static type is List[Any], so argument of cons should be Any, and 13 conforms to type Any
Is the last line above really OK? We are calling cons
on values
. values
is the same as strings
, and strings
is object of type ListImpl[String]
. So cons
invocation in the last line is expecting String
argument, but we are passing Int
, because values
's static type is List[Any]
and Int
conforms to Any
. Something is definitely wrong here - which line is to blame? The answer is: cons
method declaration. To fix this issue, we have to remove covariant type parameter A
from contravariant position (in cons
declaration). Alternatively we can make A
non-covariant.
See also these questions: #1, #2.
cons
ran into problem?trait List[+A] {
def cons[B >: A](v: B): List[B]
}
val animal_list: List[Animal] = List(tiger, dog) // We are assuming that List.apply and concrete implementation of List is somewhere defined.
No, animal_list.cons(tiger)
invocation is type-correct.
I assume that Animal
is common supertype of Dog
and Tiger
, and that dog
and tiger
are instances of Dog
and Tiger
respectively.
In animal_list.cons(tiger)
invocation, both A
and B
type parameters are instantiated to Animal
, so cons
method takes form of:
def cons[Animal >: Animal](v: Animal): List[Animal]
Animal >: Animal
constraint is satisfied because:
Supertype and subtype relationships are reflexive, which means a type is both a supertype and a subtype of itself. [source]
The argument to cons
is Tiger
which conforms to type Animal
, so the method invocation is type-correct.
Notice that if you force B
to be instantiated to Tiger
, like animal_list.cons[Tiger](tiger)
, then this invocation won't be type-correct, and you'll get compiler error.
See similar example here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With