The association between subtyping of complex types and subtyping of their component kinds is referred as variance.
Variance can also represent the inheritance correlation of Types with parameters or arguments.
If a user does not use
Let’s understand these three basics with the help of a generic type Cars
in List[Cars].
Suppose there’s a generic class in Scala with a type parameter [Cars].
We can make this class covariant by using the annotation [+Cars].
Abstract classes also offer implementation of Covariant types.
The models Mercedes,
BMW,
and Tesla
are subtypes of Cars.
This allows the List[Mercedes]
to be List[Cars]
, List[BMW]
to be List[Cars]
and similarly List[Tesla]
also to be List[Cars].
It would be correct to say that a list of Mercedes, a list of BMW, and a list of Tesla are each list of Cars, and we should be able to use them in place of List[Cars].
Note: Scala provides a built-in standard library with a generic (immutable sealed) abstract class List[+A] class, where the type parameter A is covariant.
Continuing with the same example, we have an abstract class ShowCarName
with a type parameter [-A]
that prints the Car’s name. ShowCarName[-A]
has a subtype DisplayCar.
that includes a method print()
for displaying car model.
If we observe closely, we can see that DisplayCar
knows how to print any car and DisplayBMW
knows how to print BMW. Therefore, it technically makes sense that DisplayCar
would know how to print any car. The same is not true for DisplayBMW.
We can use DisplayCar
instead of DisplayBMW,
thus making the abstract class ShowCarName[-A]
contravariant. Hence, we get the result The car's name is BMW i8
for both objects printcar
and printbmw.
If Cars
is the Tesla
subtype, and List[Tesla]
and List[Cars]
are neither inheritance nor subtype related, we characterize their relationship as an invariant
or not variant
between two parameterized types.
Note: Generic Classes in Scala are invariant by default.
Let’s implement what we learned above about covariance, contravariance, and invariance with the following code:
abstract class Cars {def model: String}def CarModels(cars: List[Cars]): Unit = {cars.foreach { car => println(car.model) }}val mercedes: List[Mercedes] = List(Mercedes("Mercedes S - Class"))val bmw: List[BMW] = List(BMW("BMW i8"))val tesla: List[Tesla] = List(Tesla("Model S"))CarModels(mercedes)CarModels(bmw)CarModels(tesla)case class Mercedes(model: String) extends Carscase class BMW(model: String) extends Carscase class Tesla(model: String) extends Cars// Contravariantabstract class ShowCarName[-A] {def print(name: A): Unit}class DisplayCar extends ShowCarName[Cars] {def print(car: Cars): Unit =println("The car's name is: " + car.model)}class DisplayBMW extends ShowCarName[BMW] {def print(bmw: BMW): Unit =println("The car's name is: " + bmw.model)}def printMyCar(showcarname: ShowCarName[BMW], bmw: BMW): Unit =showcarname.print(bmw)val printcar: ShowCarName[Cars] = new DisplayCarval printbmw: ShowCarName[BMW] = new DisplayBMWprintMyCar(printcar, BMW("BMW i8"))printMyCar(printbmw, BMW("BMW i8"))
RELATED TAGS
CONTRIBUTOR