I can\'t understand the logic behind the terms union types and intersection types in TypeScript.
Pragmatically, if the properties of different type
I also had the same question and couldn't understand how could it be seen in the opposite way. After reading the answers, I think I could explain it in the linguistic aspect providing different meanings of the two words and giving a room for the opposite naming approaches.
Compare the following meanings of word intersect:
The orbit of this comet intersects the orbit of the Earth.
In this sentence intersect means to cross at a point or set of points. Think of two things having something common (e.g. a point), and otherwise different. That's what is called intersection in math and SQL.
We need to pinpoint the place where maximum achievable conservation intersects with the highest potential financial return.
Here intersect means to come together and have an effect on each other, like two things becoming components of one new cool thing. That's the meaning of the word in TypeScript.
In a similar way you can think of union as an act of joining different things together in a loose sense - that is the meaning of union in math and SQL; but it can mean not just joining but joining together with a common interest or purpose which corresponds to the meaning of union in TypeScript.
Inspired (don't ask me why) by different translations of intersect into Russian: пересекать (also to cross) and скрещивать (also to crossbreed).
type A = {
a: number
b: number
}
type B = {
b: number
c: number
}
type C = A & B
type D = A | B
let a: A = { a: 2, b: 2 };
let b: B = { b: 2, c: 2 };
// intersection, narrow down, fewer options, like and, c have to match A and B
let c1: C = { a: 2, b: 2 }; // <- error
let c2: C = { b: 2, c: 2 }; // <- error
let c3: C = { a: 2, b: 2, c: 2 };
// union, broaden, more options, like or, d can match A or match B or match A and B
let d1: D = { a: 2, b: 2 };
let d2: D = { b: 2, c: 2 };
let d3: D = { a: 2, b: 2, c: 2 };
TypeScript playground click here.
Here's another way to think about it. Consider four sets: Red things, blue things, big things, and small things.
If you intersect the set of all red things and all small things, you end up with the union of the properties -- everything in the set has both the red property and the small property.
But if you took the union of red small things and blue small things, only the smallness property is universal in the resulting set. Intersecting "red small" with "blue small" produces "small".
In other words, taking the union of the domain of values produces an intersected set of properties, and vice versa.
In image form:
The type A | B
refers to objects which are either A
or B
. In other words, values of such type are drawn from the union of values for A
and values for B
.
The type A & B
refers to objects which are both A
and B
. In other words, values of such type are drawn from the intersection of values for A
and values for B
.
The naming and semantics are identical in other languages such as C++.
The confusion here probably stems from how we imagine the sets, namely, thinking of the intersection/union as involving the members of types as opposed to the types themselves. I put together a graphic that hopefully clarifies the concept:
You must not think of types as sets of object properties in this case. We can avoid the confusion about how union and intersection types work by looking at scalar variables and their sets of permissible values (instead of objects):
type A = 1 | 2
type B = 2 | 3
type I = A & B
type U = A | B
let a: A
let b: B
let i: I
let u: U
a = 1
a = 2
a = 3 // <- error
b = 1 // <- error
b = 2
b = 3
i = 1 // <- error
i = 2
i = 3 // <- error
u = 1
u = 2
u = 3
Here the terms "union" and "intersection" correspond exactly to the set theory terms when applied to the sets of permissible values.
Applying the notion of permissible values (instances) to object types is a bit trickier (because the set theory analogy doesn't hold well):
type A = {
x: number
y: number
}
type B = {
y: number
z: number
}
type I = A & B
type U = A | B
A
can hold object instances with properties x
and y
(and no other properties).B
can hold object instances with properties y
and z
(and no other properties).I
can hold objects with the properties of type A
AND those of type B
(i.e. x
, y
, and z
; hence the &
symbol) which corresponds to the union of properties of the two types (hence the confusion).U
can hold objects with the properties of type A
OR those of type B
(logical OR, not XOR, i.e. x
and y
, y
and z
, or x
, y
, and z
; hence the |
symbol) which implies that the intersection of properties of the two types (y
in our example) is guaranteed to be present (hence the confusion).let i: I
let u: U
i = { x: 1, y: 2 }; // <- error
i = { y: 2, z: 3 }; // <- error
i = { x: 1, y: 2, z: 3 };
u = { x: 1, y: 2 };
u = { y: 2, z: 3 };
u = { x: 1, y: 2, z: 3 };