I am currently learning scala and i am confused about the variance annotations especially the covariance and contravariance.
So i did some research an came upon following example
class Box[+T] {
def put[U >: T](x: U): List[U] = {
List(x)
}
}
class Animal {
}
class Cat extends Animal {
}
class Dog extends Animal {
}
var sb: Box[Animal] = new Box[Cat];
So this says class Box is covariant in T whiche means Box[Cat] is a subclass of Box[Animal] since Cat is a subclass of Animal. Sofar i understand this. But when it comes to method parameters my understanding ends. The spec says methods parameters can not be covariant so we have to use this lower bound annotation.
Lets look at the method definiton
def put[U >: T](x: U): List[U] = {
List(x)
}
So [U >: T] says that U must be a superclass of T
Trying following code
var sb: Box[Animal] = new Box[Cat];
sb.put(new Cat);
Works as expected but this drives me nuts
var sb: Box[Animal] = new Box[Cat];
sb.put(1);
It logically makes no sense to me to put an INT into a Box of Animals alltough its correct since INT will be resolved to Any which is a superclass of Animal.
So my question is
How do i have to adapt the code that the put method
accepts only subtypes of
animal? I can not use the upper bound annotation
class Box[+T] {
def put[U <: T](x: U): List[U] = {
List(x)
}
}
since i get this well known error
covariant type T occurs in contravariant position in type
You can add both a lower and an upper bound:
class Box[+T] { def put[U >: T <: Animal](x: U): List[U] = List(x) }
But this is not what you want, since you wire the definition of Box
to Animal
and logically there is no reason to add such an upper bound.
You say:
It logically makes no sense to me to put an INT into a Box of Animals alltough its correct since INT will be resolved to Any which is a superclass of Animal.
You don't put an Int
into a Box[Animal]
, the existing box is immutable (and it is not possible to make it mutable, since the definition of covariance doesn't allow it). Instead you get a box (or in case of your put
method) of a new type. If your goal is to get only a List[Anmial]
, then you just have to specify that:
scala> class Box[+T] { def put[U >: T](x: U): List[U] = List(x) }
defined class Box
scala> var b: Box[Animal] = new Box[Cat]
b: Box[Animal] = Box@23041911
scala> val xs: List[Animal] = b put new Dog
xs: List[Animal] = List(Dog@f8d6ec4)
scala> val xs: List[Animal] = b put 1
<console>:14: error: type mismatch;
found : Int(1)
required: Animal
val xs: List[Animal] = b put 1
^
scala> val xs = b put 1 // this will result in a List[Any]
xs: List[Any] = List(1)
There is no need to complicate the definition of the put
method.
For a more in depth explanation about why co- and contravariance is needed see this question.