26 - Variance in Type Systems
VARIANCE IN TYPE SYSTEMS
What languages do you use? Are they typed? How are they typed?
Strong vs Static types
Type system: a program. logic assigned by the programmer to represent all valid states a program can be in. This happens statically / at compile time. No need to actually run the program. Formalizes the logic any runtime variant of a program accepts
SOUNDNESS: If the type system says it’s valid, then the runtime is valid COMPLETENESS: If the type system says it’s invalid, then the runtime isn’t valid
Ideally a type system is perfectly sound AND valid, but throwback to incompleteness theorems, we know that it can never be sure of 100% completeness.
Everything is straight-forward when you don’t with simple types.
Variance describes the rules of subtyping in a type system when dealing with higher-kinded types like arrays, maps, and functions, which themselves relate to an inner type.
Strangeloop 2019 “Typing the Untyped: Soundness in Gradual Type Systems” by Ben Weissmann
All Possible Types of Variance
The following is a description of all types of variances as they relate to the Array higher-kinded type. Some of these do not make sense for arrays, or are not desirable for arrays, but they may be so for other types. We’re just using arrays here as an example to illustrate all possible types.
Covariance
Square
is a subtype of Shape
, so [Square]
is a subtype of [Shape]
Contravariance
Square
is a subtype of Shape
, so [Shape]
is a subtype of [Square]
Invariance
Square
is a subtype of Shape
, but [Square]
is completely unrelated to and distinct from [Shape]
Bivariance
Square
is a subtype of Shape
, so [Square]
is both a subtype and supertype of [Shape]
Variance Rules
Generally speaking:
- Producers are covariant
- Function return types are producers, so they’re covariant in their return types
- Consumers are contravariant
- Function argument types are consumers, so they’re contravariant in their argument types.
- Arrays and hashmaps are usually invariant. However, if you guarantee that your array is:
- only a producer (aka. read-only), then it can be covariant
- only a consumer (aka. write-only), then it can be contravariant
- … but practically speaking, if something is writeable, then it’s also readable
These rules are derived solely based on their substitutability. More examples below!
Producers
- Let’s look at this hierarchy:
Entertainment
>Music
>Metal
-
Now let’s look at the producers of these types:
Producer of Entertainment
>Producer of Music
>Producer of Metal
- If you consume some
Entertainment
, you can ask aProducer of Entertainment
, aProducer of Music
, or aProducer of Metal
, and nothing can go wrong. - If you consume
Music
, you can ask aProducer of Music
, or aProducer of Metal
, and nothing can go wrong. - If you consume
Metal
, you can ask aProducer of Metal
, and nothing can go wrong.
Consumers
- Let’s look at this hierarchy:
Enery Sources
>Vegetables
>Bamboo
-
Now let’s look at the consumers of these types:
Consumer of Enery Sources
<Consumer of Vegetables
<Consumer of Bamboo
- If you produce some
Energy Sources
, you can ask aConsumer of Energy Sources
, and nothing can go wrong. - If you produce some
Vegetables
, you can ask aConsumer of Energy Sources
, or aConsumer of Vegetables
, and nothing can go wrong. - If you produce some
Bamboo
, you can ask aConsumer of Energy Sources
, aConsumer of Vegetables
, or aConsumer of Bamboo
, and nothing can go wrong.
Arrays and Hashes
Let’s say we have the following structure.
Shape (parent or interface)
/ \
Square Circle (children)
Arrays are invariant because they cannot be covariant or contravariant:
- Unsound with covariance: Function accepts
[Shape]
in parameter position, and[Square]
is passed on invocation as an argument. This is unsound, because since the function accepts a[Shape]
, it could add aCircle
to the array, but the array was initially declared of type[Square]
. - Unsound with contravariance: Function accepts
[Square]
in parameter position, and[Shape]
is passed on innovation as an argument. This is unsound, because the function can only readSquare
s, but since we pass a[Shape]
, we’re able to supply a[Circle]
. - Unsound with bivariance: Arrays are unsound with bivariance because they are unsound with either covariance or contravariance.