Variance is the interconnection of Sub-Typing relationships which are either of complicated types or of their constituent types. Scala provides three types of variance:
- Covariant
- Contravariant
- Invariant
Covariance states that if there are two parameterized types such that S is a subtype of T, then List[S] is a subtype of List[T]. This is an inheritance relationship. So, this basically defines the relationship whether we can replace a type by its base type. Simply putting in words, if we take an example where Car is a subtype of Vehicle, then List[Car] is a subtype of List[Vehicle]. Therefore, we can replace List[Car] by List[Vehicle]. When we declare a type to be covariant, then its safe use at various positions becomes limited. In the case of immutable types, covariance is widely used.
Syntax:
List[+T]
Here, T is a type parameter and the “+” symbol represents Scala covariance.
Let us discuss this concept with the help of examples:
Example 1:
// Scala program to illustrate the concept of covariance // Creating an abstract class // for Flower abstract class Flower
{ def name : String
} // Creating a sub-class Lily // of Flower case class Lily(name : String) extends Flower
// Creating a sub-class Carnation // of Flower case class Carnation(name : String) extends Flower
object Covariance extends App
{ // Creating a method
def FlowerNames(flowers : List[Flower]) : Unit =
{
flowers.foreach
{
flower => println(flower.name)
}
}
// Assigning names
val lily : List[Lily] = List(Lily( "White Lily" ),
Lily( "Jersey Lily" ))
val carnations : List[Carnation] = List(Carnation( "White carnations" ),
Carnation( "Pink carnations" ))
// Print: names of lily
FlowerNames(lily)
// Print: names of carnation
FlowerNames(carnations)
} |
Output:
White Lily Jersey Lily White carnations Pink carnations
Explanation: In the above example, Lily and Carnation are subtypes of Flower. So, it is evident that a List[Lily] is a List[Flower] and a List[Carnation] is also a List[Flower], and we can substitute any of them for a List[Flower]. In the later part of the code, there is a method FlowerNames that prints names of flowers and the acceptable argument is a list of flowers. If the two lists are covariant, only then the method calls will compile and the flower names will get printed respectively. So, as Lily and Carnation are a subtype of Flowers and the last two lines will execute due to covariance.
Note:
- Abstract class is utilized here to apply covariance as it has List[+T] with it where the type parameter T is covariant.
- A trait App is used here to speedily change objects into workable programs.
Example 2:
// Scala program to illustrate the concept of covariance // Creating an abstract class // for Animal abstract class Animal
{ def name : String
} // Creating a sub-class Mammal // of Animal case class Mammal(name : String) extends Animal
// Creating a sub-class Reptile // of Animal case class Reptile(name : String) extends Animal
object CovarianceExample extends App
{ // Creating a method
def SpecieNames(animals : List[Animal]) : Unit =
{
animals.foreach
{
animal => println(animal.name)
}
}
// Assigning names
val mammals : List[Mammal] = List(Mammal( "Zebra" ),
Mammal( "Horse" ))
val reptiles : List[Reptile] = List(Reptile( "Snake" ),
Reptile( "Lizard" ))
// Print: names of mammals
SpecieNames(mammals)
// Print : names of reptiles
SpecieNames(reptiles)
} |
Output:
Zebra Horse Snake Lizard