Demystifying Type Assignability in TypeScript: Covariance and Contravariance Explained
Published on
Types relationship
The most common question in Typescript as a type system is "How do those types relate to each other?" or "Are those types assignable/interchangeable?".
And when you are not certain about the relationship between two types, you can always ask the Typescript compiler to help you with that.
tstypeAnswer = string extends 'abc' ? true : false
You might be wondering why this article is even needed. Typescript is a structural type system, meaning that if two types have the same structure, they are the same type.
Covariance >
Yes, it is, but what if we have a variable of the type Person and try to assign the variable with more properties than the type has?
tstypePerson = {name : stringage : number}typeProfessional = {name : stringage : numberprofession : string}constprofessional :Professional = {name : 'John',age : 30,profession : 'warehouse worker',}constperson :Person =professional
So what happened here was we are trying to assign a professional to the person, and this is OK behaviour because the professional type satisfies all the requirements of the person type. Hence, the typescript knows it has nothing to worry about. So, the Professional is more than > just a Person, meaning that the Professional is covariant to the Person.
tstypeAnswer =Professional extendsPerson ? true : false
Contravariance <
So now we know - we need to satisfy the contract, and the typescript will not complain.
Let's make a function type:
tstypetoString = (a : number) => stringconstmyToStringImplementation :toString = (a : number) =>a .toString ()
No errors, no cry
Now, let's try break the contract:
tstypetoString = (a : number) => stringfunctionsomeImplementation (a : number) {returna }constType '(a: number) => number' is not assignable to type 'toString'. Type 'number' is not assignable to type 'string'.2322Type '(a: number) => number' is not assignable to type 'toString'. Type 'number' is not assignable to type 'string'.: myToStringImplementation toString =someImplementation
So what happened here? We looked at the function signature and found that the return type should be a string, but if we take a number and return a number, we probably made an error unintentionally, and Typescript is happy to help us here.
tssomeImplementation typeAnswer = number extends string ? true : false
Now, let's try to break the contract in another way:
tstypetoString = (a : number) => stringfunctionsomeImplementation () {return 'meaning of life'}constmyToStringImplementation :toString =someImplementation
So here we are, not getting any errors, and typescript is happy with our implementation. Why is that?
Is typescript a make fun of us? In the type above, we specify that the function should take a number and return a string, but someImplementation doesn't accept any arguments.
Here, contravariance comes and tries to save the day.
The direction of the assignment is reversed for the function parameters. So here is where < logic comes into play.
Let's think about the type of someImplementation. it equals ()=>string, and that means (params: never)=>string
So the next question: does never is an assignable to a number?
tstypeAnswer = never extends number ? true : false
So it does, and if we think about it, it makes sense from the type system perspective. The javascript function can be called with any number of parameters. If we are specifying a function that is not taking parameters, then in the scope of the function, we are not using them, which means that even if we got some parameters, we would not operate on them. The context of the function is good enough to produce a value or do some side effects. And of course, we can access the arguments, but this is not typesafe or the point of this article.
Conclusion
So, in our language, we have two directions of the assignability
Covariant > - For all the things except the function parameters
In case of covariance more is better, and less is worse also means tha to satisfy the contract we at least should have all properties required by the contract.
Contravariant < - for the function parameters
In case of contravariance less is better, and more is worse that means we cannot assign function parameters of wider type to the function parameters of narrower type.