I have recently studied the generic concepts in Typescript. I have a problem with understanding "Why do the functions contravariance with their parameters?". I know that:
Covariance
is ifT extends U
(T
is assignable to U
), it is true that G<T> extends G<U>
(G<T>
is also assignable to G<U>
)
Contravariance
is if T extends U
, it's sure to conclude that G<U> extends G<T>
(now G<U>
is assignable to G<T>
).
before asking the question in this forum, I read the following posts, However, I still have trouble understanding why Functions in Typescript are Contravariant in their parameters:
I see that not only functions are contravariant with their parameters in typescript, but some other languages are also true.
I feel so frustrated to accept it without finding out why it is true. Could you help me explain it or if i missing related knowledge, please tell me what it is.
I'll use the concrete example of Animal
and Dog
.
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
There are two choices for allowing assignment between Dog
and Animal
, are we allowed to substitute Dog
for Animal
, and are we able to substitute Animal
for Dog
.
const animal: Animal = new Dog;
animal.move(10);
Because extends
means that we have the members of the base, this is fine. Typescript allows this
const dog: Dog = new Animal;
dog.bark();
There isn't a bark
method in Animal
, so this would fail at runtime. Typescript forbids this.
Now let's look at function types.
const dog: Dog = new Dog;
const animal: Animal = new Animal;
type UseAnimal: (animal: Animal) => void;
type UseDog: (dog: dog) => void;
const useAnimal: UseAnimal = (animal: Animal) => { animal.move(10); }
const useDog: UseDog = (dog: Dog) => { dog.bark() }
Language designers have the same choice about function parameters. Lets see what happens when we allow the two cases
const dogFn: UseDog = useAnimal;
dogFn(dog);
Because we are passing a Dog
to useAnimal
it is fine, it can move(10)
as above. Typescript allows this.
const animalFn: UseAnimal = useDog;
animalFn(animal)
The Animal
we pass to useDog
would cause a runtime failure, because it lacks bark()
. Typescript forbids this.