Skip to content

Instantly share code, notes, and snippets.

@EricCrosson
Forked from jimblandy/co-contra-variance.md
Created July 26, 2020 14:24
Show Gist options
  • Save EricCrosson/c6387c0c1dc5c8831dd6446801d75b5f to your computer and use it in GitHub Desktop.
Save EricCrosson/c6387c0c1dc5c8831dd6446801d75b5f to your computer and use it in GitHub Desktop.
A simple explanation of covariance and contravariance

If you say "T is a subtype of U", that means that whenever someone wants a U, a T will do: every T works as a U. It follows from this phrasing that U is a subtype of U: certainly if someone wants a U, a U will do.

So if you imagine a type as denoting a set of values, you can write: T ⊆ U.

If every T works as a U, then a function that accepts any U is certainly also a function that accepts any T: fn(U) works as a fn(T). So fn(U) is a subtype of fn(T): fn(U)fn(T).

This is interesting, because the subtypedness gets reversed: T ⊆ U implies fn(U)fn(T). But reread the last paragraph. When you think about it, it does make sense. We call this behavior of fn with respect to its argument type "contravariance", and say "fn(T) is contravariant in T".

Then, consider return values: If every T works as a U, then a function that returns a T is certainly also a function that always returns a U. So fn()->T is a subtype of fn()->U: fn()->Tfn()->U.

In this case, the subtypedness doesn't get reversed: T ⊆ U implies fn()->Tfn()->U. We call this behavior of fn with respect to its return type "covariance", and say that "fn()->T is covariant in T".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment