This helps making sense of using "never" with the moving box analogy in the video. If the grammar I have for describing what I need is contravariant (i.e. "I need at least") then the only way my friend can tell me he needs a box which can take anything is actually "I need a box that takes at least nothing".
It would be also easier to understand if we add an implementation to `registerUser` function registerUser(greetFn: (names: string) => void) { greetFn("John") } This is completely correct. But if `greetFn` is `greetUser` - then it will break, because it can only accept Role.
We asked Ryan and Daniel (from the TypeScript) team about this exact thing and Ryan said he presumes that whoever wrote the ReturnType utility didn't think the false arm was reachable. The video is called "replacing `any` with `unknown` everywhere - the story of the failed `strictAny` flag" (UA-cam gets fussy if I post a link). As always, fantastic content, Andrew!
I've stumbled with this many times over the years and am at the point where I understand it at an abstract level, but the box analogy I thought was fantastic and really made an intuitive connection for me.
In other languages like java, supertypes can always have reference to subtype instance. So string having referemce to a role is not surprising but has a different name calles contravariance in typescript.
One thought I got - function is a worker. Input - is a field which is cultivated by him. Output - checks to be paid returned by the worker. so we can substitute a worker with another worker who cultivates same or bigger field and returns same or narrower amount of checks.
another possible helpful way of thinking about it is if you flatten the passed function and inline it, you're expecting to pass it a Role, so if the actual function expects a string then of course a Role can be passed into the string
Incidentally, Array is not really covariant in T. Consider the function `const addCat = (pets: string[]) => { pets.push('cat'); }`. That could not take an array of Roles as an argument. ReadonlyArray and Readonly are covariant in T, but the compiler pretends that Array is too, because a lot of bad code depends on it.
These ideas are easy but the names are beyond terrible. I like “input widening” and “output narrowing” much better, since function/type A can be replaced with B if the latter receives the same arguments as A or wider, and returns the same type or narrower. I know jargon has its place, but we are so far removed from category theory that it only gets in the way, in this case.
Thanks so much for making this video. Having grappled with this exact issue when trying to use generics in TypeScript, your explanation of covariance and contravariance and the moving box example are the best I've come across yet. I still don't *fully* understand the mechanism, at least enough to be able to explain it to someone else as well as you do in this video, but my internal understanding is much improved. I'd love to see more videos like this where you dive into similar problems in TypeScript and maybe even other languages.
Very good explanation. What helps me, is to make clear, who "calls" the provided function, in your case the registerUser function will call it. And if can be called with an arbitrary string, then you cannot pass in a function which only knows how to handle a Role (some specific strings). But a function which accepts any string is good enough to get a Role as an argument. It needs time to get used to it.
Huh I wonder if the concept of covariant and contravariants apply when dealing with function overloads? Specifically in how we create function overloads
I am returning to this video to say thank you! I ran into this issue in the wild today trying to mess around in typescript. I was suprised to find that `keyof never` is all possible property keys, it is `string | number | symbol`. At first, I was confused, and then I went to look at: microsoft/TypeScript/issues/33025 And learned about covariance and contravariance all over again! keyof operator is also contravariant. So far, to me, it seems like it just reverses the normal rules of subtyping (covariance?). I think I will be able to explain better as I get more experience. 🤞
Array is covariant in T? That seems highly unsound. That means I can pass my Role[] to some function foo(strings: string[]), which can then do any operation valid for a string[] to it, including adding any string at all (not just Role strings) to my Role[]. Is there some mechanism in the type system that I don't know about preventing something like this from happening?
@@DavidAguileraMoncusiI think that sort of misses the point. It's not about mutation, but rather about how easy it is to violate the constraints of the defined types, even on accident. The problem I pose may not exist for ReadonlyArray, presumably because it happens to be immutable, but it's still an issue for Array. It just seems like Array has the wrong variance constraints.
@@ole4707 You're absolutely right. When I first saw the example, I was thinking of the array type as being immutable. But it isn't, of course. So making it covariant is (AFAICT) a clear bug. If you stick to immutable types, though, then things should work as expected, shouldn't they?
Great explanation. This always hurts my head for the first 5 minutes I think about it. After that it kind of makes sense, but if I stop thinking about it it's back to being unintuitive. This video might help with that though, we'll see if it sticks.
Do I have something BETTER than the box analogy? I have something different... When explaining covariance and contravariance, I always use kennels. If someone needs a kennel to put his Chihuahua in, any dog-kennel will do; if person needs a dog, what comes out of a Chihuahua-kennel will serve. The reverse is not true. If you have a dog, you cannot necessary use a Chihuahua kennel, and if you are a seeking a Chihuahua, you cannot just take whatever comes out of a dog-kennel.
@@andrew-burgess You are welcome to use the analogy. I find biological taxonomy helpful in explaining type-theory to beginners; everyone understands that Chihuahua are dogs, which are canines, which are mammals, which are animals...
Challenging concept. Awesome explanation. This proves that there is a set of unknown cardinality of unrealized UA-cam videos about hard and interesting TS subjects (❁´◡`❁)
My little helper is thinking "covariant" = "I take at most" "contravariant" = "I take at least"
This helps making sense of using "never" with the moving box analogy in the video. If the grammar I have for describing what I need is contravariant (i.e. "I need at least") then the only way my friend can tell me he needs a box which can take anything is actually "I need a box that takes at least nothing".
It would be also easier to understand if we add an implementation to `registerUser`
function registerUser(greetFn: (names: string) => void) {
greetFn("John")
}
This is completely correct. But if `greetFn` is `greetUser` - then it will break, because it can only accept Role.
We asked Ryan and Daniel (from the TypeScript) team about this exact thing and Ryan said he presumes that whoever wrote the ReturnType utility didn't think the false arm was reachable. The video is called "replacing `any` with `unknown` everywhere - the story of the failed `strictAny` flag" (UA-cam gets fussy if I post a link).
As always, fantastic content, Andrew!
Omg of course Michigan typescript out here with the knowledge.
Yaay we got everyone here 🎉
I've stumbled with this many times over the years and am at the point where I understand it at an abstract level, but the box analogy I thought was fantastic and really made an intuitive connection for me.
Great video! Difficult concept explained intuitively.
In other languages like java, supertypes can always have reference to subtype instance. So string having referemce to a role is not surprising but has a different name calles contravariance in typescript.
One thought I got - function is a worker.
Input - is a field which is cultivated by him.
Output - checks to be paid returned by the worker.
so we can substitute a worker with another worker who cultivates same or bigger field and returns same or narrower amount of checks.
another possible helpful way of thinking about it is if you flatten the passed function and inline it, you're expecting to pass it a Role, so if the actual function expects a string then of course a Role can be passed into the string
Incidentally, Array is not really covariant in T. Consider the function `const addCat = (pets: string[]) => { pets.push('cat'); }`. That could not take an array of Roles as an argument.
ReadonlyArray and Readonly are covariant in T, but the compiler pretends that Array is too, because a lot of bad code depends on it.
The title is a very good javascript pun, will use it in my next pr review.
These ideas are easy but the names are beyond terrible.
I like “input widening” and “output narrowing” much better, since function/type A can be replaced with B if the latter receives the same arguments as A or wider, and returns the same type or narrower.
I know jargon has its place, but we are so far removed from category theory that it only gets in the way, in this case.
Thanks so much for making this video. Having grappled with this exact issue when trying to use generics in TypeScript, your explanation of covariance and contravariance and the moving box example are the best I've come across yet. I still don't *fully* understand the mechanism, at least enough to be able to explain it to someone else as well as you do in this video, but my internal understanding is much improved. I'd love to see more videos like this where you dive into similar problems in TypeScript and maybe even other languages.
Very good explanation. What helps me, is to make clear, who "calls" the provided function, in your case the registerUser function will call it. And if can be called with an arbitrary string, then you cannot pass in a function which only knows how to handle a Role (some specific strings). But a function which accepts any string is good enough to get a Role as an argument. It needs time to get used to it.
Huh I wonder if the concept of covariant and contravariants apply when dealing with function overloads? Specifically in how we create function overloads
Now I want to learn more about all the rules where co and contravariance apply! I think this was a great intro.
I am returning to this video to say thank you! I ran into this issue in the wild today trying to mess around in typescript. I was suprised to find that `keyof never` is all possible property keys, it is `string | number | symbol`. At first, I was confused, and then I went to look at:
microsoft/TypeScript/issues/33025
And learned about covariance and contravariance all over again! keyof operator is also contravariant. So far, to me, it seems like it just reverses the normal rules of subtyping (covariance?). I think I will be able to explain better as I get more experience. 🤞
Array is covariant in T? That seems highly unsound. That means I can pass my Role[] to some function foo(strings: string[]), which can then do any operation valid for a string[] to it, including adding any string at all (not just Role strings) to my Role[]. Is there some mechanism in the type system that I don't know about preventing something like this from happening?
Probably using ReadonlyArray?
@@DavidAguileraMoncusiI think that sort of misses the point. It's not about mutation, but rather about how easy it is to violate the constraints of the defined types, even on accident. The problem I pose may not exist for ReadonlyArray, presumably because it happens to be immutable, but it's still an issue for Array. It just seems like Array has the wrong variance constraints.
@@ole4707 You're absolutely right. When I first saw the example, I was thinking of the array type as being immutable. But it isn't, of course. So making it covariant is (AFAICT) a clear bug. If you stick to immutable types, though, then things should work as expected, shouldn't they?
I have run into this before. I don't know if there is a fix.
I think the best thing to do is to avoid or limit the use of mutation.
Not many in-depth videos on this topic. Good video 👍
That was a great video, and the moving box example landed it for me perfectly
I feel this is pretty natural in the context of category theory
Great explanation. This always hurts my head for the first 5 minutes I think about it. After that it kind of makes sense, but if I stop thinking about it it's back to being unintuitive.
This video might help with that though, we'll see if it sticks.
Yikes. What a nightmare this language is. God i feel good writing Go
Sobyou think this concepts are specific to typescript, right? Keep yourself in your bubble
@@Danielo515 I never said that. I write Go daily basis because of my job.
@@gordonfreimann Nobody cares.
Amazingly explained!
Do I have something BETTER than the box analogy? I have something different... When explaining covariance and contravariance, I always use kennels.
If someone needs a kennel to put his Chihuahua in, any dog-kennel will do; if person needs a dog, what comes out of a Chihuahua-kennel will serve.
The reverse is not true. If you have a dog, you cannot necessary use a Chihuahua kennel, and if you are a seeking a Chihuahua, you cannot just take whatever comes out of a dog-kennel.
This is so good! Mind if I update my blog post with this analogy? Will credit you, of course!
@@andrew-burgess You are welcome to use the analogy. I find biological taxonomy helpful in explaining type-theory to beginners; everyone understands that Chihuahua are dogs, which are canines, which are mammals, which are animals...
Finally an explanation of this topic that I actually understand! Thank you Andrew!
That was an awesome explanation. Thanks.
Challenging concept. Awesome explanation. This proves that there is a set of unknown cardinality of unrealized UA-cam videos about hard and interesting TS subjects (❁´◡`❁)
Great video.
Finally an explanation that I could understand