Covariance and Contravariance in Scala

Scala Extractors
Reading Time: 3 minutes

Variance is the quality of being different. It is the correlation of subtyping relationships of complex types and subtyping relationships of their component types.
Covariance allows assigning an instance to a variable whose type is one of the instance’s generic type; i.e. supertype.
Contravariance allows assigning an instance to a variable whose type is one of the instance’s derived type; i.e. subtype.

Before learning about variances, prerequisite is to first understand : Type System and Type Parameterization.

Liskov substitution principle

Liskov substitution principle (the L. of SOLID principles) specifies that, in a relation of inheritance, a type defined as supertype allows to substitute it by any of its derived classes.

Let’s see how this happens

scala> abstract class Vehicle
defined class Vehicle

scala> case class Car() extends Vehicle
defined class Car

scala> case class Bike() extends Vehicle
defined class Bike

Now let’s define a function which takes a Vehicle as a parameter and returns the same vehicle value.

scala> val vehicleIdentity = (vehicle:Vehicle) => vehicle
vehicleIdentity: Vehicle => Vehicle = <function1>

Now, we can invoke the vehicle Identity function as

scala> vehicleIdentity(Car())
res0: Vehicle = Car()

scala> vehicleIdentity(Bike())
res1: Vehicle = Bike()

So here, we can substitute Car and Bike in place of Vehicle, since Car and Bike are subclass of Vehicle.

Variance annotation creates a type hierarchy between parameterized types. In other words, given a class List [A],
if A is a subclass of B then, List [A] can be a subclass of List [B].
The variance models this correlation and allows us to create more reusable generic classes.

Let’s see how variance works.

scala> case class Parking[A](value: A)
defined class Parking

scala> val carParking: Parking[Vehicle] = Parking[Car](new Car)
<console>:12: error: type mismatch;
 found   : Parking[Car]
 required: Parking[Vehicle]
Note: Car <: Vehicle, but class Parking is invariant in type A.

Here, Car is a subtype of Vehicle but Parking[Car] isn’t subtype of Parking[Vehicle]. Hence we aren’t able to assign the Parking[Car] in place of Parking[Vehicle].

Before moving to details of covariance and contravariance, below image is a good example to start with.

Covariance

A generic class covariant over its abstract type can receive a parameter type of that type or subtypes of that type.

scala> abstract class Vehicle
defined class Vehicle

scala> case class Car() extends Vehicle
defined class Car

scala> case class Parking[+A](vehicle: A)
defined class Parking

scala> val carParking : Parking[Vehicle] = Parking[Car](new Car)
carParking: Parking[Vehicle] = Parking(Car())

Legal positions of covariant type parameter

The covariant type parameter can be used as:
immutable field type,
method return type,
method argument type( if the method argument type has a lower bound
)

Because of these restrictions, covariance is most commonly used in producers (types that return something) and immutable types.

Contravariance

To understand contravariance, stop thinking of types in terms of “is a more specialized type” and switch the focus to the idea of acceptance. Contravariant is the way to express that a Container can be either the basic type or only specialized for a given type?

scala> case class Parking[-A]()
defined class Parking

scala> val parking: Parking[Car] = Parking[Vehicle]
parking: Parking[Car] = Parking()

Use cases for contravariant type parameter

Contravariant type parameter is usually used as a method argument type. Contravariance is most commonly associated with consumers (types that accept something).

Use restrictions of covariant type parameter

Contravariant type parameter would be illegal in a position such as a method return type then Scala compiler would have reported an error.

Conclusion

In general, parameterized types that are covariant in the type parameter are producers of that type parameter and those that are contravariant in the type parameter are consumers of the type parameter.

References

Variance in Scala
Learn more about variances
Contravariance in details

Knoldus Pune Careers - Hiring Freshers

Get a head start on your career at Knoldus. Join us!