Covariance and Contravariance in Scala
July 06, 2015
The code is also on gist.
Class hierarchies: the ⊑-relation
Take a look at this simple class hierarchy:
The sub type relation
Animal says, that dass
Cat is a sub type of
Animal. In colloqial speech you say that a
Cat is an
these hierarchies are also called is-a-hierarchies.
Do not make the mistake to translate the symbol ⊑ as "is-smaller-or-equal-than". It looks similiar
and a cat is a sub type, but it may have more information, methods and attributes.
An advantage of such a class hierarchy is, that you can write one function for all
You can mix
So in general at every expression that is of type
Animal you could substitute an expression
In the theory of programming languages this property is called the
Liskov substitution principle.
B, then expressions of type
B can be replaced by expressions
Generic classes with type parameters
A Generic class has at least one type parameter
This is useful for lists, sets, trees and other collections. You only have to define
and you can create lists of cats, dogs or animals in general.
Generic classes and the ⊑-relation: covariance and contravariance
In general it is not the case that a type parameter of a generic class respects the ⊑-relation.
We know, that
Animal. But how is it with
Cat is an
G[Cat] also a
There are the following possibilities:
There are only two directions for information: entering a class or leaving a class, into it or out of it, writing or reading, push or pop. Methods that read data from classes are called getters and method that write are called setters.
Take a look at this getter with type parameter
You can create a
and later you can read the cat:
You also can create an
This is the same code, only that the type of the getter now is
Getter[Animal] that returns
Animal, even if a
new Cat is used in the constructor.
What is the relationship between
Can we replace all
Getter[Animal] or the other way around?
Let's call the getters and let's try to convert the results.
You see that the results of
gc.get can be converted into
the result of
ga.get can not be converted into a cat.
gc is more general as
ga. We can't use
There is another way to see this. Extend the class
Cat with a method:
and now write the following function
f does not accept
Getter[Animal] because an
Animal has no
Getter[Animal] can be replaced by a
Getter is covariant.
A setter writes information into a class. The following class accepts an argument and forgets it immediately:
We create an instance for
Cats and for
If we now try out the different combinations, the following happens:
Setter[Animal] can take all animals as parameter, but the
Setter[Cat] takes only cats. (Simple, isn't it).
Setter[Cat] can be replaced by a
Setter[Animal] but not the other way round.
By the substitution principle:
Setter[T] is contravariant.
So the basics are explained.
If you want further information, i recommend the book by Odersky et. al.
Remark: This post was adapted to the new blog format in November 2016.