Covariance and contravariance refer to how a programing languages type system handles subtyping. Covariance refers to types being more specific, and contravariance refers to types being more generic. Let's explore all of this in a little more detail.
Type systems are a component of programming languages,
and they contain the logical rules defining how it handles individual types, like string, float, int, etc.
Imagine you have a class called Animal, and a class called Cat, which extends Animal.
The Cat class is a subtype of Animal, and in-turn, Animal is a supertype of Cat.
This is subtyping.
Covariance allows child types to be more specific, but what does this mean?
If we use PHP as an example,
which supports covariance for return types,
it means that if you override a classes method, you can change its return type to be more specific.
So, if we continue on with the Animal and Cat example,
imagine you have the following class.
class AnimalBed
{
public function setOccupant(Animal $animal): void
{
$this->animal = $animal;
}
public function getOccupant(): Animal
{
return $this->animal;
}
}This class allows for any subtype of Animal to be the occupant,
whether it's the previously mentioned Cat class, or newly created ones such as Dog, Bird or even Horse.
Now, if you want to have a class that represents a cat bed, you'd create the following.
class CatBed extends AnimalBed
{
}And with covariance, we can override the AnimalBed::getOccupant() method, and change its return type to Cat.
class CatBed extends AnimalBed
{
public function getOccupant(): Cat
{
return $this->animal;
}
}This works because Cat is a subtype of Animal, so it's a more specific type, meaning Cat is covariant to Animal.
At this point you may find yourself wanting to override AnimalBed::setOccupant(),
to change its parameter type to Cat, which is where contravariance comes in.
Contravariance is the opposite of covariance, so it refers to child types being more generic (less specific).
Continuing on from the example above, PHP supports contravariance for parameter types,
so when you override a method, its parameter types must be either the same or more generic.
So, with the CatBed situation,
since the Animal class has no supertypes
(beyond the generic object and mixed in PHP),
your only real option would be to do a type check in the method itself.
public function setOccupant(Animal $animal): void
{
if (! ($animal instanceof Cat)) {
throw new InvalidArgumentException('The cat bed is only for cats');
}
$this->animal = $animal;
}While contravariance is more of an issue in this situation, let's look at another where it isn't.
Let's say we have a class called AnimalFood,
which extends the Food class, and in our Animal class we have the following method.
public function eat(AnimalFood $food): string
{
return $this->name . " eats " . $food->getName();
}Contextually, making sure animals only eat AnimalFood is good,
because a lot of different types of animals have very specific and strict dietary requirements.
There is, however, one type of animal that is known for eating pretty much everything, a pig.
So, if you were to have a Pig class which is a subtype of Animal,
you wouldn't want the pig limited to only eating animal food, so you'd override the Animal::eat() method.
class Pig extends Animal
{
public function eat(Food $food): string
{
return $this->name ." eats " . $food->getName();
}
}Here we've used contravariance to change the parameter type of food in the Animal::eat() method,
to be more generic, as Food is a supertype of AnimalFood, meaning Food is contravariant to AnimalFood.
You've probably noticed by now that covariance and contravariance are just prefixed versions of the word 'variance', and you may find yourself wondering what that is. If you're not, feel free to continue on with your day, but, if you are interested in a little more details, read on.
Variance is the general term for the rules that define how programming languages handle related types. Covariance and contravariance are two of the five types of variance. The other three are as follows.
If something is bivariant, it is both covariant and contravariant. This means it would accept an argument whose type is either less specific, or more specific than the type it is expecting. In reality, you'll most likely never encounter this, because any sensible programming language would avoid it.
Variant simply means that something is covariant, contravariant, or bivariant.
Invariant, or sometimes nonvariant, simply means it isn't variant, so, is not covariant, contravariant or bivariant.
For those that are curious, these are word prefixes that hold specific meaning. Some have multiple definitions, but the relevant ones for this context are as follows.
- Co: Means 'together' or 'having commonality'.
- Contra: Means 'against' or 'in opposition to'.
- Bi: Means 'two', or 'both' in the context of a set of two.
- In: Is used to reverse the meaning of a word.
- Non: Means 'no', 'none', or 'not'.