Wednesday, March 24, 2010

In- and Co- variance of type parameters

In Java most parameterized types are considered to be "invariant". What does that mean? Here is an example to explain:
  1. /*
  2. This is an example of a parameterized class that with an invariant parameter B
  3. In both Scala and Java parameters are invariant by default.
  4. */
  5. scala> class Invariant[B]
  6. defined class Invariant
  7. scala> var x : Invariant[Object] = new Invariant[Object]
  8. x: Invariant[java.lang.Object] = Invariant@2e0c5575
  9. /*
  10. Note: Invariant[String] cannot be assigned to Invariant[Object]
  11.       even though logically it seems like it should be.
  12.       This is the effect of invariance.  Covariant parameters do not have
  13.       this restriction.
  14. */
  15. scala> var x : Invariant[Object] = new Invariant[String]
  16. < console>:6: error: type mismatch;
  17.  found   : Invariant[String]
  18.  required: Invariant[java.lang.Object]
  19.  var x : Invariant[Object] = new Invariant[String]
  20.                              ^
  21. scala> class Sub[A] extends Invariant[A]   
  22. defined class Sub
  23. /*
  24. Since Sub is a subclass of Invariant it can be assigned
  25. (but not Sub[String])
  26. */
  27. scala> val x : Invariant[Object] = new Sub[Object]
  28. x: Invariant[java.lang.Object] = Sub@26ced1a8

Assignment compatibility has multiple dimensions: the object type and the types of the parameters. Unlike object type the compatibility of the type-parameters can be covariant, contravariant and invariant. Java has invariant parameters and that is demonstrated by the previous example. Covariant parameters allow subclassing. Contravariant parameters need their own topic.
  1. // The '+' indicates the parameter is covariant
  2. scala> class Covariant[+B]
  3. defined class Covariant
  4. scala> var x : Covariant[Object] = new Covariant[Object]
  5. x: Covariant[java.lang.Object] = Covariant@315cb235
  6. // Now this is legal
  7. scala> var x : Covariant[Object] = new Covariant[String]
  8. x: Covariant[java.lang.Object] = Covariant@26e2e276
  9. /*
  10. Warning: The following is not legal because 
  11.          you cannot supply an invariant parameter 
  12.          with a covariant value.
  13. */
  14. scala> class Sub[+A] extends Invariant[A]
  15. < console>:7: error: covariant type A occurs in invariant position in type [+A]Invariant[A] with ScalaObject{def this(): Sub[A]} of class Sub
  16.        class Sub[+A] extends Invariant[A]
  17.              ^
  18. scala> class Sub[+A] extends Covariant[A]
  19. defined class Sub
  20. scala> class Sub[A] extends Covariant[A] 
  21. defined class Sub

3 comments:

  1. I must admit I’ve never heard of “intravariance”.

    ReplyDelete
  2. While A in List is defined to be covariant, A in Set is invariant: List[+A], Set[A]. Why is following still legal:

    case class C(i:Int)
    case class D(j:Int) extends C(j)
    val s = collection.mutable.Set(C(1))
    s += D(2) // covariant?

    ReplyDelete