Closed xddq closed 1 year ago
The book is correct. But you're not too dumb! This is, indeed, easy to get backwards :)
Type A
is a subtype of type B
if all values assignable to A
are also assignable to B
. Similarly, type A
is not a subtype of type B
if there's a value assignable to A
that is not assignable to B
.
In other words, with the right value, we can sort out the subtype relationship.
Here's some code (playground):
{
const a: readonly number[] = [1, 2, 3];
const b: number[] = a;
// ~ The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
}
{
const a: number[] = [1, 2, 3];
const b: readonly number[] = a; // ok
}
As you can see from the first block, the readonly number[]
value is not assignable to number[]
. So readonly number[]
is not a subtype of number[]
.
From the second block, you can see that number[]
is assignable to readonly number[]
. So number[]
is a subtype of readonly number[]
.
Perhaps a more intuitive way to think of this is to imagine a value x
that has all the usual array methods like map
, filter
, etc., except it's missing push
and pop
. This value is assignable to readonly T[]
(which does not require push
and pop
) so it's in the domain of readonly T[]
. But it is not assignable to T[]
(which does require push
and pop
) so it's not in the domain of T[]
. If you were to draw a Venn diagram, the domain of readonly T[]
would be a big circle with a smaller circle for T[]
entirely inside of it. The hypothetical value x
would be in the bigger circle but outside the smaller one.
Hopefully that makes some sense!
Hey @danvk, thanks for the quick response! Sadly, I still don't get it. You don't have to bother answering once more. I just write this down to be able to refer to my question when asking friends or elsewhere. Just to mention what I don't get. You said:
"If you were to draw a Venn diagram, the domain of readonly T[] would be a big circle with a smaller circle for T[] entirely inside of it."
...but how can T[] be entirely inside of readonly T[] if it contains values (e.g. push and pop methods) that are not even part of readonly T[]?
Is that we can assign a value of type T[] to readonly T[] since Typescript uses structural typing and T[] contains the attributes that are required to fit into readonly T[].
However, when drawing the Venn diagram to me it feels like it should look somewhat like this:
Where I would say that readonly T[] is a subtype of T[] (all values of readonly T[] live inside T[] in the Venn diagram).
To me the given example with readonly T[] and T[] is equivalent to something like this playgroundlink:
const x: {a: number, b: string} = {a: 1, b: "hello"}
const y: {a: number} = x
// This works. For me this is exactly the same as assigning a number[] to a readonly number[]
// However, I would not claim that the type {a: number, b:string} is a subtype of {a: number}
const z: {a:number} = {a: 1, b: "hello"}
// Type '{ a: number; b: string; }' is not assignable to type '{ a: number; }'.
//Object literal may only specify known properties, and 'b' does not exist in type '{ a: number; }'.(2322)
Here I would (same as before) not claim that {a: number, b:string} is a subtype of {a:number}.
Okay I think I finally understood your point.
The "subtype" and "subset" terms are based on the knowledge that Typescript is structurally typed.
If we say we have the type
type T = {
x: number
}
We have an inifinite amount of possible values that can match that type. For example:
{
x: 1
}
{
x: 2
}
but (more importanly) also
{
x: 2,
y: "hello"
}
and
{
x: 1,
y: "hello",
z: 10
}
Now lets say we have type U with:
type U = {
x: number,
y: string
}
which would also have an infinite amount of possible values. But it does not have possible values where we have
{
x: 1
}
{
x: 2
}
so therefore in the Venn diagram the type of T would be the outer circle and the type of U would be the inner circle (all values of U are inside of the outer circle). The values in the outer circle that are not inside of the inner circle are the ones that are an object with a single attribute "x" of type number.
I think you've figured it out! This has everything to do with structural typing.
...but how can T[] be entirely inside of readonly T[] if it contains values (e.g. push and pop methods) that are not even part of readonly T[]?
push
and pop
are not values. They're methods/properties on a value. The values in this diagram would be things like []
, [1, 2, 3]
, [0, 'x']
, etc.
I would not claim that the type {a: number, b:string} is a subtype of {a: number}
I would :) We can walk through it if it's helpful, but based on your last comment I think you've got it. The key is that, thanks to structural typing, a value like {a: 1, b: 2}
is assignable to {a: number}
, even though that type doesn't say anything about a b
property.
Hey @danvk !
Thanks for the great book, just skimming through it, but already gotten great advice! I feel like there is a small error on page 72 (or I am to dumb? sorry if thats the case). It says:
number[] is a subtype of readonly number[]
The sentence directly after says(Its easy to get this backwards- remember Item 7!)
I don't get how
number[]
is supposed to be a set with less possible values thanreadonly number[]
?readonly number[] lacks push, pop, etc..
I think the correct sentence should be
readonly number[] is a subtype of number[]