Covariant and contravariant are very important topics in programming theory. Used to express the compatibility (or inheritance) of the parent and child classes after safe type conversions. Is defined as: if A, B represents two types; F () stands for type conversion; A -> B means that A is A subclass of B.
- when
f()
Is covariant: ifA -> B
,f(A) -> f(B)
- when
f()
Contravariant: IfA -> B
,f(B) -> f(A)
- when
f()
If it is dual change: ifA -> B
, then all the above are true - when
f()
Is constant: ifA -> B
, the above are not valid, there is no compatible relationship
class Animal {
move(){
console.log("animal is moving"); }}class Cat extends Animal {
purr() {
console.log("cat is purring"); }}class WhiteCat extends Cat {
showoffColor() {
console.log("see my hair color"); }}Copy the code
We call it the parent of Animial, Cat is a subclass of Animal. WhiteCat is a subclass of Cat, WhiteCat -> Cat -> Animal. According to the principle that the parent class is compatible with the subclass, we can know:
let animal: Animal;
let cat: Cat;
let whiteCat: WhiteCat;
animal = cat;
animal = whiteCat;
cat = whiteCat;
Copy the code
Throw out problem
Suppose we now have a function of type (param: Cat) => Cat. So what are the compatible types?
We can break this down into two parts parameter compatibility and return value compatibility.
(param: Cat) => void
What are the compatible types of?() => Cat
What are the compatible types of?
Parameter compatibility
We assume (param: Cat) => void is A, then we have two functions:
B
:(param: WhiteCat) => void
C
:(param: Animal) => void
So which function is compatible with A?
Assuming compatibility with B
Then A = B holds:
let A: (param: Cat) = > void;
const B = (param: WhiteCat) = > {
param.move();
param.purr();
param.showoffColor();
};
A = B;
A(new Cat());
Copy the code
The function runs to param.showoffcolor () with an error. So the hypothesis doesn’t work.
Assuming compatibility with C
Then A = C holds:
let A: (param: Cat) = > void;
const C = (param: Animal) = > {
param.move();
};
A = C;
A(new Cat());
Copy the code
The function now runs successfully. So the hypothesis is true.
So (param: Animal) => void -> (param: Cat) => void. From the previous definition, we can see that the function parameters are contravariant.
Return value compatibility
We assume that () => Cat is A, where there are the following two functions:
B
:() => Animal
C
:() => WhiteCat
So which function is compatible with A?
Assuming compatibility with B
Then A = B holds:
let A: (a)= > Cat;
const B = (a)= > new Animal();
A = B;
const result = A();
result.move();
result.purr();
Copy the code
An error is reported when the function runs to result.purr(). So the hypothesis doesn’t work.
Assuming compatibility with C
Then A = C holds:
let A: (a)= > Cat;
const C = (a)= > new WhiteCat();
A = C;
const result = A();
result.move();
result.purr();
Copy the code
The function now runs successfully. So the hypothesis is true.
So () => WhiteCat -> () => Cat. From the previous definition, you can see that the return value of a function is covariant.
The reality of function parameter types
In TS, parameter types are bivariant, that is, both covariant and contravariant. Of course it’s not safe. This can be fixed by enabling strictFunctionTypes to ensure that the parameter types are contrariant.
So why does TS allow function argument types to retain dual conversions? Here is a very common example:
interface Event { timestamp: number; } interface MouseEvent extends Event { x: number; y: number } function listenEvent(eventType: EventType, handler: (n: Event) => void) { /* ... */ / listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x, e.y)); // listenEvent(EventType.mouse, (e: Event) => console.log((e as MouseEvent).x, (e as MouseEvent).y))); // listenEvent(EventType.mouse, (e: Event) => console.log((e as MouseEvent).x, (e as MouseEvent).y)); listenEvent(EventType.Mouse, ((e: MouseEvent) => console.log(e.x, e.y)) as (e: Event) => void);Copy the code
If the function argument type is double-variable, the first form of the above code will compile without the need for the latter two detour methods.