Covariance, Contravariance, and Super Type Constraints
hhvm.com
hhvm.com
coerce : a -> b
which can only be applied when a is a subtype of b then we can dispense with subtyping entirely through explicit coercion.Now, covariance occurs when you can "map" over a structure "in the same direction".
mapCovariant : (a -> b) -> (f a -> f b)
while contravariance occurs when maps "flip" mapContravariant : (a -> b) -> (f b -> f a)
and we get the subtyping relationship naturally from applying coerce covariantCoerce = mapCovariant coerce
contravariantCoerce = mapContravariant coerce
Now all of this only applies "mathematically" where things are necessarily constant. This implies that we ought to think of mapCovariant and mapContravariant as working on "transformations of data" instead of operating imperatively.In the event that we have mutability then we can violate covariance and contravariance as noted. Really, though, this is a consequence of a larger problem.
In any mutable type we can apply a transformation on that type while maintaining all references to it
a = [1, 2, 3];
a.mapCovariant(fun (x) -> repeat "a" x);
// a : [String]
// a == ["a", "aa", "aaa"]
If we're adamant about treating things as transformations then functions like mapCovariant cannot operate mutably since they allow the changing of a generic type.The definitive explanation of why inheritance should always be avoided in favour of implementation of abstract interfaces.
I see that many OOP languages (of various purity) have interfaces that may be extended by other interfaces. Is that what you're talking about?
I also found that *variance is used in the context of Haskell, but it seems to be far from being widely used.
Haskell being my template of a language with a good-looking interplay of polymorphism and algebraic typing.