Will you be my covariant?
Agenda
- Refresher
- Pop Quiz
- Purpose
- What is variance?
- What makes something covariant
- What makes something contravariant
- What makes something Invariant
- Another Quiz
- Demo
Refresher
What is inheritance: example

What is inheritance: more generically

What is inheritance?
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: void
};
declare const cat: Cat;
cat.live // ok
cat.meow // ok
// subtype polymorphism
const animal: Animal = cat;
animal === cat // true
animal.live // ok
animal.meow // meow does not exist on animal
It's complicated
declare const cat: Cat;
const animal: Animal = cat; // ok
declare const cats: Array<Cat>;
const animals: Array<Animal> = cats; // maybe
Is this ok?
If this is ok
Pop Quiz
Can we do this?
const cats: Cat[] = [];
const dogs: Dog[] = [];
const animals: Animal[] = cats;
declare const dog: Dog;
animals.push(dog);
cats[0].meow;
Is this legal?
Yes!
But it shouldn't be
That's why variance is important
Purpose
Purpose
Understand how it is helpful
Understand when variance applies
Understand how to profit from it
What is variance?
What is variance?
Variance refers to the subtyping relationship between complex types and the subtyping relationship of their components.
What is variance?


Covariance


Covariance
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
interface List<T> {
peek: () => T
}
declare const cats: List<Cat> ;
const animals: List<Animal> = cats; // ok
Covariance
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
interface List<T> {
peek: () => T
}
declare const animals: List<Animal>;
const cats: List<Cat> = animals; // error
// Property 'meow' is missing in type 'Animal'
// but required in type 'Cat'
Contravariance


Contravariance
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
type CardboardBox<T> = {
put: (t: T) => void
}
declare const animal: CardboardBox<Animal>;
const cat: CardboardBox<Cat> = animal // actually ok;
Contravariance
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
type CardboardBox<T> = {
put: (t: T) => void
}
declare const cat: CardboardBox<Cat>;
const animal: CardboardBox<Animal> = cat; // error
Contravariance
So why is Array covariant but CardboardBox Contravariant?
We'll get to it!
But first, invariance.
Invariant


Invariant
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
type Groomer<T> = {
groom: (t: T) => T
}
declare const animal: Groomer<Animal>;
declare const cat: Groomer<Cat>;
const catGroomer: Groomer<Cat> = animal; // error
const animalGroomer: Groomer<Animal> = cat; // error
// Type 'Animal' is not assignable to type 'Cat'.
Bivariance


Bivariance
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
type Vet<T> = {
treat(t: T): void
}
declare const animal: Vet<Animal>;
declare const cat: Vet<Cat>;
const catVet: Vet<Cat> = animal; // ok
const animalVet: Vet<Animal> = cat; // ok
How do we determine variance?
How do we determine variance?
In some languages you can specify
class List[+T] {
// List is covariant on T
}
class Printer[-T] {
// Printer is Contravariant on T
}
Typescript allows to infer it by position as well
What makes something covariant?


What makes something covariant?
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
interface List<T> {
peek: () => T
}


What makes something covariant?
interface List<T> {
peek: () => T
}
declare const cats: List<Cat>;
const animals: List<Animal> = cats;
const animal = animals.peek(); // ok


Is List<T> covariant?
What makes something covariant?
declare const animals: List<Animal>;
const cats: List<Cat> = animals;


Is List<T> contravariant?
const cat = cats.peek(); // error
What makes something covariant?
const animal = animals.last; // ok
interface List<T> {
readonly last: T, // read only is important
}
declare const cats: List<Cat> ;
const animals: List<Animal> = cats;


Is List<T> covariant now? It has a field instead of a method.
What makes something covariant?
cats.last; // dangerous
// last could be a dog
declare const animals: List<Animal>;
const cats: List<Cat> = animals;


Is List<T> contravariant?
What makes something covariant?
interface List<T> {
last: T, // writable
}
declare const cats: List<Cat> ;
const animals: List<Animal> = cats;
animals.last = new Dog() // uh oh!


Is List<T> covariant with a writable field?
Unfortunately yes
What makes something covariant?
If T is used as
Return a function
A read only field *


What makes something contravariant?


Contravariant?
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
type CardboardBox<T> = {
put: (t: T) => void
}


Contravariant?
type CardboardBox<T> = {
put: (t: T) => void
}
declare const animals: CardboardBox<Animal>;
const cats: CardboardBox<Cat> = animals;
cats.put(new Cat()) //ok


Is CardboardBox<T> contravariant?
Contravariant?
type CardboardBox<T> = {
put: (t: T) => void
}
declare const cats: CardboardBox<Cat>;
const animals: CardboardBox<Animal> = cats;
animals.put(new Cat()); // safe?
animals.put(new Dog()); // No!
cats[1].meow // uh oh


Is CardboardBox<T> covariant?
What makes something contravariant?
If T is used as
an input parameter to a function


What makes something invariant?


What makes something invariant?


type Groomer<T> = {
groom: (t: T) => T
}
If T is used in both covariant and contravariant positions
declare const animals: Groomer<Animal>;
const cats: Groomer<Cat> = animals; // error
declare const cats: Groomer<Cat>;
const animals: Groomer<Animal> = cats; // error
Another Quiz
interface Animal {
live: boolean
};
interface Cat extends Animal {
meow: () => void
};
interface Lilo extends Cat {
goodGirl: true
}
interface Stitch extends Cat {
badBoy: true
}

Another Quiz
type F0 = (c: Cat) => Cat;
type F1 = (l: Lilo) => Animal;
type F2 = (a: Animal) => Stitch;

const fn1: F0 = {} as F1 //(1)
const fn2: F0 = {} as F2 //(2)
Demo
Will you be my covariant?
By Leon Tager
Will you be my covariant?
- 126