开发者

What is the other usage of type variance except input/output params?

My understanding is that the type variance is used in the following cases:

  • If a generic type G has type parameter T1, which appears as a type of an argument of a G method, then G开发者_运维知识库 can be contravariant in T1.

  • If G has type parameter T2, which appears as a type of any return value of a method (or ctor) of G, then G can be covariant in T2.

What if I replace can be with should be in the sentences above? Are there any other case of co- and contra-variant usage? When and why do you make your types co- and contra-variant?


Quoting from the spec, section 4.5 Variance Annotations:

Variance annotations indicate how instances of parameterized types vary with respect to subtyping (§3.5.2). A '+' variance indicates a covariant dependency, a '-' variance indicates a contravariant dependency, and a missing variance indication indicates an invariant dependency. A variance annotation constrains the way the annotated type variable may appear in the type or class which binds the type parameter. In a type definition type T [tps] = S, or a type declaration type T [tps] >: L <: U type parameters labeled +' must only appear in covariant position whereas type parameters labeled '-' must only appear in contravariant position.

A type parameter is therefore by default considered to be invariant. You have to explicitly annotate the type parameter to be either co- or contravariant if you want to use this. Also, it is perfectly legal to use variance annotations on a type parameter that is not used at all (although his may not be so useful). For example:


scala> class A[+T, -S] {def myMethod(s: String) = println(s)}
defined class A

scala> class A2[T] {def myMethod(t: T) = println(t)}
defined class A2

scala> class A3[-T] {def myMethod(t: T) = println(t)}
defined class A3

scala> val a1 = new A2[Any]
a1: A2[Any] = A2@1cd1cea

scala> val a2: A2[Int] = a1
:6: error: type mismatch;
 found   : A2[Any]
 required: A2[Int]
       val a2: A2[Int] = new A2[Any]

scala> val a3  = new A3[Any]
a3: A3[Any] = A3@875dee

scala> val a4: A3[Int] = a3
a5: A3[Int] = A3@875dee

The variance annotation on class A3, which is contravariant in this example, makes that A3[Any] is considered to be a subtype of A3[Int], making the assignment from instance a4 to a3 legal. This fails if you do not use the variance annotation.


Things are just not that simple. Sometimes variance doesn't make any sense at all, so you just keep the class invariant.

Also, note that variance toggles along a usage chain. For example:

class A[+T]
class B[-T] {
  def f(x: A[T]) {}
}

class C[+T] {
  def g(x: B[T]) {}
}

Or, put in another way, it is not a simple thing that can be described in a few lines. Which is the main reason why the strong enforcement of variance by Scala is a very useful thing -- nowadays, I'm half convinced most code using variance in Java must have subtle bugs.


Let me give a try at this old question. One of the usage of the covariance and contravariance is to have some restraint on the Generic by means of lower bound >: (covariance) and upper bound <: (contravariance). The usage can be seen in the following code snippet. It is from my own blog on the subject.

    abstract class Animal (animalType:String)

    class HasFourLegs(animalType:String) extends Animal(animalType){
      def move=println(this+" walking on four legs")
    }

    class HasTwoLegs(animalType:String) extends Animal(animalType){
      def move=println(this+" walking on Two legs")
    }

    case class Dog(animalType:String) extends HasFourLegs(animalType)
    case class Ostrich(animalType:String) extends HasTwoLegs(animalType)

      def moveOn4legs[T<:HasFourLegs](animal:T)=  animal.move
      val dog = Dog("dog")
      val ostrich=Ostrich("ostrich")
      moveOn4legs(dog)
      /*
      moveOn4legs(ostrich)
      error: inferred type arguments [this.Ostrich] do not conform to method moveOn4legs's type parameter bounds [T <: this.HasFourLegs]
      moveOn4legs(ostrich)
      ^
      */
      println

    class AnimalMovement [+T]{
      def movement[U>:T](animal:U)=println(animal+" walking on Two legs!!!")
    }

      val moveLikeTwoLegs=new AnimalMovement[HasTwoLegs]()
      moveLikeTwoLegs.movement(ostrich)
      moveLikeTwoLegs.movement(dog)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜