-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Docs: function parameter bivariance #14973
Comments
Great idea but I'm not sure that the second example fits anymore given that inspiration for the motivating example is now declared safely https://github.com/Microsoft/TypeScript/blob/master/lib/lib.dom.d.ts#L2671 |
The first one is not really true anymore either: class Animal {
pet(): void;
}
class Dog extends Animal {
bark(): void;
}
class Cat extends Animal {
meow(): void;
}
function doSomethingWithAnimals<T extends Animal>(array: T[]): void {
// The only place this function can get an instance of T is from the array itself, which is safe to add back to the array
let animal: T = array.pop();
// The only interface available to interact with T is that of Animal
animal.meow(); // compilation error
animal.pet(); // valid
array.push(animal); // Safe, the item came from the array
}
function addAnimal<T extends Animal, U extends T>(array: T[], animal: U): void {
array.push(animal); // Necessarily safe
}
const dogs: Dog[] = [new Dog()];
const cats: Cat[] = [new Cat()];
let animals: Animal[] = dogs; // No need for this to ever be allowed.
animals = [new Dog(), new Cat()]; // Safe
doSomethingWithAnimals(dogs); // Valid
doSomethingWithAnimals(cats); // Valid
addAnimal(dogs, new Cat()); // Compiler error
addAnimal(cats, new Dog()); // Compiler error
addAnimial(dogs, new Dog()); // Valid
addAnimal(animals, new Dog()); // Valid
addAnimal(animals, new Cat()); // Valid |
Oh very interesting. So with both original arguments no longer valid, what reasons remain for function parameter bivariance (as opposed to contravariance that would ensure type safety)? In my, admittedly very limited, understanding of this issue, silently allowing covariance in function arguments opens a non-trivial hole in type safety. |
If you simply make function parameters contravariant, then you lose a critical property of generics: that Fundamentally you can't fix this without explicitly differentiating things like |
@RyanCavanaugh By structural expansions, you mean the type checker would, for each generic class, examine how each of its type variables appears in its methods, and based on that, choose the variance of the class with respect to each type variable? For example, with respect to the type variable that appears only in method return types, the class would be covariant; and with respect to the type variable that appears in both argument and return types, the class would be invariant. Is this really very slow? It only needds to be done once per class definition. (Edit: it might make it even faster if the programmer can indicate the variance in generic class definition; then the type checker just needs to verify it.) And I can see that it would cause far too many generic classes to end up invariant; but I suppose it's possible to make the class hierarchy a bit more refined, by adding mix-in classes that behave covariantly because they don't have type variables in method arguments. |
I have found these two super useful, but rather unrelated, explanations of why function parameters are bivariant in TypeScript. Would it be helpful to merge them into one?
The first one is that the programmer may know that an object of type
T[]
will be only read from or only written to, but has no way of communicating that knowledge to TypeScript. Since the methods that write to an array are contravariant inT
(T
is their argument type) and the methods that read from the array are covariant inT
(T
is their return type), TypeScript permits either direction even though it cannot statically verify correctness.The second one is that the programmer may know that the correct type of one argument (which is a function) may depend on the type of another argument; and more specifically, in a common JS pattern, it is the function argument type that varies. Again, there is no way to express it to TypeScript. Rather than giving up completely, TypeScript offers at least partial type safety by asking the programmer to indicate the broadest argument type that the function might accept, which allows it to use bivariance in its type check.
Edit: discussion in #1394 and #10717 is highly relevant.
The text was updated successfully, but these errors were encountered: