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
.