Scala Variances: Covariant, Invariant and Contravariant

Filed Under: Scala

In this post, we are going to discuss about Scala Variance and it’s use cases.

What is Variance?

Variance defines Inheritance relationships of Parameterized Types. Variance is all about Sub-Typing.

Please go through the following image to understand “What is Parameterized Type”.

parameterized_type

Here T is known as “Type Parameter” and List[T] is known as Generic.

For List[T], if we use List[Int], List[AnyVal], etc. then these List[Int] and List[AnyVal] are known as “Parameterized Types”

Variance defines Inheritance relationship between these Parameterized Types.

Advantage of Variance in Scala

The main advantage of Scala Variance is:

  • Variance makes Scala collections more Type-Safe.
  • Variance gives more flexible development.
  • Scala Variance gives us a technique to develop Reliable Applications.

Types of Variance in Scala

Scala supports the following three kinds of Variance.

  • Covariant
  • Invariant
  • Contravariant

We will discuss these three variances in detail in coming sections.

Covariant in Scala

If “S” is subtype of “T” then List[S] is is a subtype of List[T].

This kind of Inheritance Relationship between two Parameterized Types is known as “Covariant

scala-covariant

Scala Covariance Syntax:-
To represent Covariance relationship between two Parameterized Types, Scala uses the following syntax:
Prefixing Type Parameter with “+” symbol defines Covariance in Scala.

scala-covariance-syntax

Here T is a Type Parameter and “+” symbol defines Scala Covariance.

NOTE:- For simplicity reason, I’m using “List” here. However it may be any valid Scala Type like Set[+T], Ordered[+T] etc.

Example:-
Write a Scala program to demo Scala Covariant SubTyping technique.


class Animal[+T](val animial:T)

class Dog
class Puppy extends Dog

class AnimalCarer(val dog:Animal[Dog])

object ScalaCovarianceTest{
  def main(args: Array[String]) {
    val puppy = new Puppy
    val dog = new Dog

    val puppyAnimal:Animal[Puppy] = new Animal[Puppy](puppy)
    val dogAnimal:Animal[Dog] = new Animal[Dog](dog)

    val dogCarer = new AnimalCarer(dogAnimal)
    val puppyCarer = new AnimalCarer(puppyAnimal)

    println("Done.")
  }
}

NOTE:- As Animal class is defined by using Variance Annotation i.e. “+T”, we can pass either dogAnimal or its subtype puppyAnimal to create a AnimalCarer object.

If we remove Variance Annotation in Animal class definition, like as shown below:


class Animal[T](val animial:T)
// Remaining code is same as above.

It wont compile. We will get the following compilation error message:


Type mismatch, expected: Animal[Dog], found: Animal[Puppy]

To solve these kind of problems, we should use Scala Covariance.

As per this example, we can say the following Scala Covariance:

“As Puppy is subtype of Dog, Animal[Puppy] is a subtype of Animal[Dog]. We can use Animal[Puppy] where we require Animal[Dog].” This is know as Scala Covariance.

Contravariant in Scala

If “S” is subtype of “T” then List[T] is is a subtype of List[S].

This kind of Inheritance Relationship between two Parameterized Types is known as “Contravariant

scala-contravariance

Scala Contravariant Syntax:
To represent Contravariant relationship between two Parameterized Types, Scala uses the following syntax:
Prefixing Type Parameter with “-” symbol defines Contravariant in Scala.

scala-contravariance-syntax

Example:-
Write a Scala program to demo Scala Contravariant SubTyping technique.


abstract class Type [-T]{
  def typeName : Unit
}

class SuperType extends Type[AnyVal]{
  override def typeName: Unit = {
    println("SuperType")
  }
}
class SubType extends Type[Int]{
  override def typeName: Unit = {
    println("SubType")
  }
}

class TypeCarer{
  def display(t: Type[Int]){
    t.typeName
  }
}

object ScalaContravarianceTest {

  def main(args: Array[String]) {
    val superType = new SuperType
    val subType = new SubType

    val typeCarer = new TypeCarer

    typeCarer.display(subType)
    typeCarer.display(superType)
  }

}

NOTE:-
As we define Contravariance in Type[-T], it works well. TypeCarer.display() is defined with Type[Int] i.e. SubType, but still it accepts Type[AnyVal] because Scala Contravariance subtyping.

If we remove “-” definition in Type like “Type[T]”, then we will get compilation error.

Invariant in Scala

If “S” is subtype of “T” then List[S] and List[T] don’t have Inheritance Relationship or Sub-Typing. That means both are unrelated.

This kind of Relationship between two Parameterized Types is known as “Invariant or Non-Variant

In Scala, by default Generic Types have Non-Variant relationship. If we define Parameterized Types without using “+’ or “-” symbols, then they are known as Invariants.

What is Variance Annotation in Scala?

Variance Annotation means defining “+” or “-” before Type Parameters.

Example:-
+T and – T are know as Variance Annotations in Scala.

Scala Variance Summary

In this section, we are going to summarize all 3 concepts we have discussed about Scala Variance Types in above sections.

Scala Variance Type Syntax Description
Covariant [+T] If S is subtype of T, then List[S] is also subtype of List[T]
Contravariant [-T] If S is subtype of T, then List[T] is also subtype of List[S]
Invariant [T] If S is subtype of T, then List[S] and List[T] are unrelated.

That’s it all about Scala Variance. We will discuss some more Scala concepts in my coming posts.

Please drop me a comment if you like my post or have any issues/suggestions.

Comments

  1. Arnold Higyed says:

    Thank you! Great post!

  2. Krishna Chowdary Garapati says:

    Good explanation. I am a Java developer. Better if you compare the topics with java, we can easily understand.

  3. Vinit S says:

    Nice post. Explained it very clearly.

  4. gayatri gupta says:

    Excellent post

  5. Aamir says:

    Best Post on Scala Generics I have read.

  6. Aniruddha Sinha says:

    You may find this example interesting on Covariance and Contravariance
    //COVARIANCE
    type AnimalCovariance[+T]
    type Dog
    type Puppy <: Dog
    type dogs=AnimalCovariance[Dog]
    type puppies=AnimalCovariance[Puppy]
    //test of covariance
    implicitly[Puppy<:<Dog]
    implicitly[puppies<:<dogs]

    //CONTRAVARIANCE
    type AnimalContravariance[-T]
    type Dog2
    type Puppy2<:Dog2
    type dogs2=AnimalContravariance[Dog2]
    type puppies2=AnimalContravariance[Puppy2]
    //test for contravariance
    implicitly[Puppy2<:<Dog2]
    implicitly[dogs2<:<puppies2]

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages