- Let
C<A>
be a higher-kinded type e.g. inList<Animal>
,List
isC
andAnimal
isA
. - Let
S
be a subtype ofT
e.g. inclass Cat extends Animal
,Cat
isS
andAnimal
isT
- If
C<S>
is a subtype ofC<T>
, thenC
is covaraint onT
e.g.List<Cat>
is a subtype ofList<Animal>
- If
C<T>
is a subtype ofC<S>
, thenC
is contravariant onT
e.g.Predicate<Animal>
is a subtype ofPredicate<Cat>
- If neither
C<T>
and norC<S>
are subtypes of the other, thenC
is invariant onT
- If both
C<T>
andC<S>
are subtypes of each other, thenC
is phantom variant onT
. This is possible in languages which support phantom types like Haskell
In Scala:
- Coavariance is denoted by prefixing the type argument using a
+
e.g.trait List[+A]
- Contravariance is denoted by prefixing the type argument using a
-
e.g.trait Predicate[-A]
- Invariance is simply denoted by default without any prefixes e.g.
trait Container[A]
- We can have all covariance, contravariance and invariance in a single higher-kinded type e.g. functions are contravariant on the input but covariant on the output i.e.
trait Function1[-I, +O]
- Phantom Variance is impossible in Scala since it does not support phantom types.