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?