Why is parameter in contravariant position?
Solution 1:
This is a fundamental feature of object-oriented programming that doesn't get as much attention as it deserves.
Suppose you have a collection C[+T]
. What the +T
means is that if U <: T
, then C[U] <: C[T]
. Fair enough. But what does it mean to be a subclass? It means that every method should work that worked on the original class. So, suppose you have a method m(t: T)
. This says you can take any t
and do something with it. But C[U]
can only do things with U
, which might not be all of T
! So you have immediately contradicted your claim that C[U]
is a subclass of C[T]
. It's not. There are things you can do with a C[T]
that you can't do with a C[U]
.
Now, how do you get around this?
One option is to make the class invariant (drop the +
). Another option is that if you take a method parameter, to allow any superclass as well: m[S >: T](s: S)
. Now if T
changes to U
, it's no big deal: a superclass of T
is also a superclass of U
, and the method will work. (However, you then have to change your method to be able to handle such things.)
With a case class, it's even harder to get it right unless you make it invariant. I recommend doing that, and pushing the generics and variance elsewhere. But I'd need to see more details to be sure that this would work for your use case.
Solution 2:
Almost there. Here:
scala> trait MyTrait[+T] {
| private case class MyClass[U >: T](c: U)
| }
defined trait MyTrait
Which means MyClass[Any]
is valid for all T
. That is at the root of why one cannot use T
in that position, but demonstrating it requires more code than I'm in the mood for at the moment. :-)