Take a Covariant type constructor:
class ContainerCovariant[+A](element: A) {
class ContainerCovariant[+A](element: A) {
def set(what: A): Unit = ??? // covariant type A occurs in contravariant position in type A of value what
Eek, it doesn't compile. (Note: all argument types are covariant on a JVM. Haskell doesn't have sub- and super-types so you encounter Co- and Contra-variance less often.)
Why it doesn't compile can be understood by Proof by Contradiction. Say it did compile. Then we could write:
val covariantWithChild: ContainerCovariant[Child] = new ContainerCovariant[Child](aChild)
val covariantWithParent: ContainerCovariant[Parent] = covariantWithChild
covariantWithParent.set(aParent)
val aChildRight: Child = covariantWithChild.get // what the hey? this is a parent not a child!
[Note that when a type constructor is co- or contra-variant, it is relative to the reference pointing at it. For example, in the above code, the reference on the right hand side (a ContainerCovariant[Child]) is covariant to the reference on the left (a ContainerCovariant[Parent])
This leads to an important mnemonic, the five Cs:
"Co- and Contra-variance of Classes are Compared to the Call Site".]
Anyway, we can get around this compilation error (see here and the other question linked off it) by making the type of the parameter contra-variant in A as the compiler is asking us to do:
def set[B >: A](what: B): Unit = ???
class ContainerCovariant[+A](element: A) {
var varA: A = null.asInstanceOf[A] // <-- "covariant type A occurs in contravariant position in type A of value varA_
def set[B >: A](what: B): Unit = { varA = what }
Only this time, the compiler complains at the var declaration. Making it a val helps:
val valA: A = null.asInstanceOf[A] // this compiles
Remembering the Get/Set Principle, a mutable class should have an invariant type, so let's add:
class ContainerCovariant[+A](element: A) {
class InvariantMutableRef[B](var b: B)
val invariantMutableRef = new InvariantMutableRef(valA) // "covariant type A occurs in invariant position in type => ..."
class ContainerCovariant[+A](element: A) {
class ContravariantRef[-B] { ... }
val contravariantRef = new ContravariantRef[A] // "covariant type A occurs in contravariant position in type => ContainerCovariant.this.ContravariantRef[A] of value contravariantRef..."
class ContravariantRef[-B] {
val b: B = null.asInstanceOf[B] // "contravariant type B occurs in covariant position in type => B of value b"
var b: B = null.asInstanceOf[B] // "contravariant type B occurs in covariant position in type => B of method b"
because we could refer to ContravariantRef with a more specific type of B when B wasn't more specific at all. Double eek.
So, mutability is related to contr-, co- and invariance down at the compiler level which tries to stop us from doing something evil.
No comments:
Post a Comment