Skip to content

Scala – Covariant and Contravariant Parameters

At this moment I do not have a personal relationship with a computer.

Janet Reno

In Scala, the symbols + and - can be used as part of a parameter declaration to indicate whether the parameter is covariant (+) or contravariant (-) with respect to its type parameter.

A covariant parameter can accept a subtype of its declared type parameter, while a contravariant parameter can accept a supertype of its declared type parameter. The + and - symbols can only be used for type parameters, and only if the containing class or trait is also declared with the + or - variance annotation.

Here’s an example of a covariant parameter:

trait AnimalList[+A] {
  def add(animal: A): AnimalList[A]
}

case class Cat(name: String)

case class Dog(name: String)

class CatList[+A <: Cat](val cats: List[A]) extends AnimalList[A] {
  override def add(animal: A): CatList[A] = new CatList(animal :: cats)
}

class AnimalListTest {
  val cat1 = Cat("Fluffy")
  val cat2 = Cat("Mittens")
  val catList = new CatList(List(cat1, cat2))
  val animalList: AnimalList[Cat] = catList
  animalList.add(Dog("Fido")) // won't compile, because Dog is not a subtype of Cat
}

In this example, AnimalList is a trait with a covariant type parameter A. CatList is a class that implements AnimalList for the Cat type, and is also declared with a covariant type parameter A that is bounded by Cat. This means that CatList is a subtype of AnimalList[Cat]. We can create an instance of CatList, and assign it to a variable of type AnimalList[Cat]. When we try to add a Dog to the list, the code won’t compile, because Dog is not a subtype of Cat.

Here’s an example of a contravariant parameter:

trait Printer[-A] {
  def print(value: A): Unit
}

class AnimalPrinter extends Printer[Animal] {
  override def print(animal: Animal): Unit = println(s"I am an animal named ${animal.name}")
}

case class Cat(name: String) extends Animal

case class Dog(name: String) extends Animal

class AnimalPrinterTest {
  val cat = Cat("Fluffy")
  val dog = Dog("Fido")
  val printer: Printer[Cat] = new AnimalPrinter
  printer.print(cat) // Output: I am an animal named Fluffy
  printer.print(dog) // won't compile, because Dog is not a subtype of Cat
}

In this example, Printer is a trait with a contravariant type parameter A. AnimalPrinter is a class that implements Printer for the Animal type, and is also declared with a contravariant type parameter A that is bounded by Animal. This means that AnimalPrinter is a supertype of Printer[Cat]. We can create an instance of AnimalPrinter, and assign it to a variable of type Printer[Cat]. When we try to print a Dog, the code won’t compile, because Dog is not a subtype of Cat.

Published inPersonal PostsScalaTechnical Posts