I've read a few tutorials including the main Scala documentation regarding method signatures of covariant types. Suppose I have the following abstract class:
abstract class List[+A] {
def head: A
def tail: List[A]
def isEmpty: Boolean
def add[B >: A](element: B): List[B]
protected def printElements: String
override def toString: String = "[" + printElements + "]"
}
My question concerns the signature of the add()
method. Why is it necessary to declare it that way? We are passing in a parameter that is a supertype of A. What problem does this solve? I'm trying to understand this on an intuitive level.
Given
abstract class List[+A] {
def add(element: A): List[A]
}
"This program does not compile, because the parameter element in
add
is of typeA
, which we declared covariant. This doesn’t work because functions are contravariant in their parameter types and covariant in their result types. To fix this, we need to flip the variance of the type of the parameter element inadd
.
We do this by introducing a new type parameterB
that hasA
as a lower type bound".
-- reference.
In this example, if you add
something to a List:
It must be an A
- in this case the List is still a List[A]
.
Or it must be any subtype of A
- in this case the element gets upcasted to A
, and the List remains a List[A]
.
Or if it is another type B
, then it MUST be a supertype of A
- in this case the List gets upcasted to a List[B]
. (Note: Because Any
is just a supertype of everything, in the worst case the List will be upcasted to List[Any]
).