🎓 A1Learner
🔍
0 of 48 lessons completed

Scala Variance

Variance is the rule that decides whether a container of one type counts as a container of another type. It follows the subtype rule of the values inside the container.

A shelf of dogs counts as a shelf of animals

The dog shelf is assigned to an animal shelf value. Reading the item still gives the dog name.

class Animal(val name: String)
class Dog(name: String) extends Animal(name)
class Cat(name: String) extends Animal(name)
class Shelf[+A](val item: A)
@main def main(): Unit =
  val dogShelf: Shelf[Dog] = Shelf(Dog("Rex"))
  val animalShelf: Shelf[Animal] = dogShelf
  println(animalShelf.item.name)

A feeder of animals counts as a feeder of dogs

The animal feeder is assigned to a dog feeder value. Feeding a dog still works.

class Animal(val name: String)
class Dog(name: String) extends Animal(name)
class Cat(name: String) extends Animal(name)
class Feeder[-A <: Animal](val label: String):
  def feed(a: A): String = label + " " + a.name
@main def main(): Unit =
  val animalFeeder: Feeder[Animal] = Feeder("Fed")
  val dogFeeder: Feeder[Dog] = animalFeeder
  println(dogFeeder.feed(Dog("Rex")))

A plain box of dogs is not a box of animals

Each box holds its own type. No assignment between the boxes is shown, because invariance forbids it.

class Animal(val name: String)
class Dog(name: String) extends Animal(name)
class Cat(name: String) extends Animal(name)
class Box[A](val value: A)
@main def main(): Unit =
  val dogBox = Box(Dog("Rex"))
  val animalBox = Box(Animal("Sam"))
  println(dogBox.value.name)
  println(animalBox.value.name)

Example

class Animal(val name: String)
class Dog(name: String) extends Animal(name)
class Cat(name: String) extends Animal(name)
class Shelf[+A](val item: A)
class Feeder[-A <: Animal](val label: String):
  def feed(a: A): String = label + " " + a.name
@main def main(): Unit =
  val dogShelf: Shelf[Dog] = Shelf(Dog("Rex"))
  val animalShelf: Shelf[Animal] = dogShelf
  val animalFeeder: Feeder[Animal] = Feeder("Fed")
  val dogFeeder: Feeder[Dog] = animalFeeder
  println(animalShelf.item.name)
  println(dogFeeder.feed(dogShelf.item))

Key points

  • Covariance: Shelf[+A] means a Shelf[Dog] counts as a Shelf[Animal] when Dog extends Animal.
  • Contravariance: Feeder[-A] means a Feeder[Animal] counts as a Feeder[Dog] when Dog extends Animal.
  • Invariance: Box[A] with no sign means a Box[Dog] never counts as a Box[Animal].
  • Reading direction: covariance fits containers that hand values out.
  • Writing direction: contravariance fits containers that take values in.

In this example, a shelf of dogs is read as a shelf of animals, a feeder of animals is used as a feeder of dogs, and the dog from the shelf is fed by that feeder.

Note

the sign sits on the type parameter, as in +A or -A, not on the container name.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Scastie

Exercise

When Dog extends Animal, what does Shelf[+A] allow?