covariant type A occurs in contravariant position in type A of value a
Solution 1:
The error message is actually very clear once you understand it. Let's get there together.
You are declaring class Box
as covariant in its type parameter A
. This means that for any type X
extending A
(i.e. X <: A
), Box[X]
can be seen as a Box[A]
.
To give a clear example, let's consider the Animal
type:
sealed abstract class Animal
case class Cat extends Animal
case class Dog extends Animal
If you define Dog <: Animal
and Cat <: Animal
, then both Box[Dog]
and Box[Cat]
can be seen as Box[Animal]
and you can e.g. create a single collection containing both types and preserve the Box[Animal]
type.
Although this property can be very handy in some cases, it also imposes constraints on the operations you can make available on Box
. This is why the compiler doesn't allow you to define def set
.
If you allow defining
def set(a:A): Unit
then the following code is valid:
val catBox = new Box[Cat]
val animalBox: Box[Animal] = catBox // valid because `Cat <: Animal`
val dog = new Dog
animalBox.set(dog) // This is non-sensical!
The last line is obviously a problem because catBox
will now contain a Dog
! The arguments of a method appear in what is called "contravariant position", which is the opposite of covariance. Indeed, if you define Box[-A]
, then Cat <: Animal
implies Box[Cat] >: Box[Animal]
(Box[Cat]
is a supertype of Box[Animal]
). For our example, this is of course non-sensical.
One solution to your problem is to make the Box
class immutable (i.e. to not provide any way to change the content of a Box
), and instead use the apply method defined in your case class
companion to create new boxes. If you need to, you can also define set
locally and not expose it anywhere outside Box
by declaring it as private[this]
. The compiler will allow this because the private[this]
guarantees that the last line of our faulty example will not compile since the set
method is completely invisible outside of a specific instance of Box
.
If for some reason you do not want to create new instances using the apply method, you can also define set
as follows.
def set[B >: A](b: B): Box[B] = Box(b)
Solution 2:
Others have already given an answer why the code doesn't compile, but they haven't given a solution on how to make the code compile:
> case class Box[+A](v: A) { def set[B >: A](a: B) = Box(a) }
defined class Box
> trait Animal; case class Cat() extends Animal
defined trait Animal
defined class Cat
> Box(Cat()).set(new Animal{})
res4: Box[Animal] = Box($anon$1@6588b715)
> Box[Cat](Cat()).set[Animal](new Animal{})
res5: Box[Animal] = Box($anon$1@1c30cb85)
The type argument B >: A
is a lower bound that tells the compiler to infer a supertype if necessary. As one can see in the example, Animal
is inferred when Cat
is given.
Solution 3:
Try to understand what it means for your Box[+A]
to be covariant in A
:
It means that a Box[Dog]
should also be a Box[Animal]
, so any instance of Box[Dog]
should have all the methods a Box[Animal]
has.
In particular, a Box[Dog]
should have a method
set(a: Animal): Box[Animal]
However, it only has a method
set(a: Dog): Box[Dog]
Now, you'd think you can infer the first one from the second, but that's not the case: I do you want to box a Cat
using only the second signature? That's not doable, and that's what the compiler tells you: a parameter in a method is a contravariant position (you can only put contravariant (or invariant) type parameters).
Solution 4:
in addition to the other answers i'd like to provide another approach:
def set[B >: A](x: B): Box[B] = Box(x)