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”.
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 Covariance Syntax:-
To represent Covariance relationship between two Parameterized Types, Scala uses the following syntax:
Prefixing Type Parameter with “+” symbol defines Covariance in Scala.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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:
1 2 3 4 |
class Animal[T](val animial:T) // Remaining code is same as above. |
It wont compile. We will get the following compilation error message:
1 2 3 |
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 Contravariant Syntax:
To represent Contravariant relationship between two Parameterized Types, Scala uses the following syntax:
Prefixing Type Parameter with “-” symbol defines Contravariant in Scala.
Example:-
Write a Scala program to demo Scala Contravariant SubTyping technique.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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.
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.