The sub type relation Cat ⊑ Animal says, that dass Cat is a sub type of
Animal. In colloqial speech you say that a Cat is an Animal. Therefore
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 Animals.
So in general at every expression that is of type Animal you could substitute an expression
of type Cat or Dog.
In the theory of programming languages this property is called the
Liskov substitution principle.
If A ⊑ B, then expressions of type B can be replaced by expressions
of type A.
Generic classes with type parameters
A Generic class has at least one type parameter T.
This is useful for lists, sets, trees and other collections. You only have to define List[T] once
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 Cat ⊑ Animal. But how is it with
G[Cat] and G[Animal]? If Cat is an
Animal, is G[Cat] also a G[Animal]?
There are the following possibilities:
If A ⊑ B and G[A] ⊑ G[B],
then T in G[T] is covariant.
If A ⊑ B and G[B] ⊑ G[A],
then T in G[T] is contravariant.
Otherwise T in G[T] is invariant.
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.
You see that the results of
gc.get can be converted into Animal, but
the result of
ga.get can not be converted into a cat.
So therefore gc is more general as ga. We can't use ga for
each gc.
Conclusion: Getter[Cat] ⊑ Getter[Animal].
There is another way to see this. Extend the class Cat with a method:
Setter[Animal] can take all animals as parameter, but the
Setter[Cat] takes only cats. (Simple, isn't it).
So a Setter[Cat] can be replaced by a Setter[Animal] but not the other way round.
By the substitution principle: Setter[Animal] ⊑ Setter[Cat]
and T in Setter[T] is contravariant.
Conclusion
So the basics are explained.
If you want further information, i recommend the book by
Odersky et. al.
The fusion of Object Oriented Programming (OOP) and Functional Programming (FP) languages has become increasingly popular in recent years, with many programming languages now offering features and constructs from both...
The topic of covariance and contravariance is explained in many places (including Wikipedia). This post is my attempt
to explain it in my own words, using Scala.