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) }
compiler says:
error: covariant type T occurs in contravariant position in type T of value c
I then tried the following but it also didn't work:
trait MyTrait[+T] { private case class MyClass[U <: T](c: U) }
the error this time is:
error: covariant type T occurs in contravariant position in type >: Nothing <: T of type U
Could somebody explain why the T is in a covariant position here and suggest a solution for this problem? Thx!
Covariance means that a method can return a type that is derived from the delegate's return type. Contra-variance means that a method can take a parameter that is a base of the delegate's parameter type.
In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.
Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.
Covariance is a concept that is very straightforward to understand. We say that a type constructor F[_] is covariant if B is a subtype of type A and F[B] is a subtype of type F[A]. In Scala, we declare a covariant type constructor using the notation F[+T], adding a plus sign on the left of the type variable.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With