Trusted answers to developer questions

What are variances in Scala?

Free System Design Interview Course

Many candidates are rejected or down-leveled due to poor performance in their System Design Interview. Stand out in System Design Interviews and get hired in 2024 with this popular free course.

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 annotationsdenoted by (-) or (+), Scala provides variance annotations of type parameters of generic classes, allowing them to be:

  • covariant (preserved)
  • contravariant (reversed)
  • invariant (ignored)

Let’s understand these three basics with the help of a generic type Cars in List[Cars].

Covariant

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.

Contraviant

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.

Invariant

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.

Variants in Scala

Code

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 Cars
case class BMW(model: String) extends Cars
case class Tesla(model: String) extends Cars
// Contravariant
abstract 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 DisplayCar
val printbmw: ShowCarName[BMW] = new DisplayBMW
printMyCar(printcar, BMW("BMW i8"))
printMyCar(printbmw, BMW("BMW i8"))

RELATED TAGS

scala

CONTRIBUTOR

Dian Us Suqlain
Copyright ©2024 Educative, Inc. All rights reserved
Did you find this helpful?