Type-oriented programming/Type variance
Consider the following type hierarchy:
type A {} type B : A {} type F[T] {}
Without type arguments, F
is a type operator
while F[A]
and F[B]
are proper types.
In general, neither is a subtype of the other.
However it may sometimes make sense for them to be in a subtype relation
depending on the hierarchy of their argument(s).
If we declare F
as follows
type F[cov T] {}
then F[B]
will be a subtype of F[A]
because B
is a subtype of A
.
Conversely, if we declare F
as
type F[con T] {}
then F[A]
will be a subtype of F[B]
.
In the former case, we say that F
is covariant
in its type argument whereas in the latter case
its contravariant (since the hierarchy is reversed).
A real-world example of type variance are function types.
When we expect a function whose return type is A
(that is, of type Func[A]
), we can always
use a function of type Func[B]
in its stead.
On the other hand, when we expect a function whose
argument is of type B
(that is, of type
Func[B,X]
, we can always use a function of type
Func[A,X]
in its stead.
In sum, function types are covariant in their return type
and contravariant in their arguments’ types.
NB: The pseudocode can be tried out using the Funcy app, which can be downloaded for free from
Apple’s App Store (iOS/macOS),
Google Play (Android) or Amazon Appstore. The code to be executed must be placed in a main {}
block.