The Scala Language Specification (Section 4.5 on Variance Annotations, p. 44) says
Using the first point above, it is easy to see (at least formally) that
trait Covariant[+A] {
def problematic[B <: A](x : B)
}
produces the error message
error: covariant type A occurs in contravariant position in type >: Nothing <: A of type B
def problematic[B <: A](x : B)
and using the first and the second point it is easy to see that
trait Contravariant[-A] {
def problematic[B >: A](x : B)
}
produces the error message
error: contravariant type A occurs in covariant position in type >: A <: Any of type B
def problematic[B >: A](x : B)
As I mention, it's easy to see formally (i.e., following the rules for variance annotations) why these errors occur. However, I can not come up with an example illustrating the need for these restrictions. In contrast, it is very easy to come up with examples that illustrate why method parameters should change variance positions, see e.g. Checking Variance Annotations.
So, my question is the following: Assuming, the two pieces of codes above were allowed, what are the examples of problems that arise? This means, I'm looking for examples similar to this one that illustrate what could go wrong in case the two rules cited above were not used. I'm particularly interested in the example involving lower type bounds.
Note that the answer to Scala type bounds & variance leaves this particular question open, whereas the answer given in The "lower bound" will reverse the variance of a type, but why? seems wrong to me.
Edit: I think the first case can be handled as follows (adapting the example cited above). Assume, the following was allowed
trait Queue[+T] {
def head : T
def tail : Queue[T]
def enqueue[U <: T](x : U) : Queue[T]
}
Then we could implement
class QueueImplementation[+T] extends Queue[T] {
/* ... implement Queue here ... */
}
class StrangeIntQueue extends QueueImplementation[Int] {
override def enqueue[U <: Int](x : U) : Queue[Int] = {
println(math.sqrt(x))
super.enqueue(x)
}
}
and use it as
val x : Queue[Any] = new StrangeIntQueue
x.enqueue("abc")
which is clearly troublesome. However, I can not see how to adapt this in order to show that the combination "contravariant type parameter + lower type bound" is also problematic?
Let's suppose we allow for a class to have a type parameter [-T]
and a method on that class to have [U >: T]
...
for come class hierarchy
Dog <: Mammal <: Animal
class Contra[-X](x: X){
def problem[Y >: X](y: Y): Y = x // X<:Y so this would be valid
}
val cMammal:Contra[Mammal] = new Contra(new Mammal)
val a:Animal = cMammal problem new Animal // Animal >: Mammal, this is fine
val m:Mammal = cMammal problem new Mammal // Mammal >: Mammal, this is fine
val d:Mammal = cMammal problem new Dog // (Dog upcasts to Mammal) >: Mammal, this is fine
val cDog:Contra[Dog] = cMammal // Valid assignment
val a:Animal = cDog problem new Animal // Animal >: Mammal, this is fine
val m:Mammal = cDog problem new Mammal // Mammal >: Mammal, this is fine
val d:Dog = cDog problem new Dog // AAAHHHHHHH!!!!!!
This last line would type check, cDog problem new Dog
would actually return a Mammal
. This is clearly not a good thing. Thankfully the type system doesn't actually let us do this.
Q.E.D. contravariant type parameter + lower type bound not a good idea to mix.
I hope this example helps.