I\'m trying to use a covariant type parameter inside a trait to construct a case-class like so:
trait MyTrait[+T] {
private case class MyClass(c: T)
}
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.
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. :-)