TypeScript 联合类型和交叉类型的命名

97

我无法理解 TypeScript 中 交叉类型联合类型 的逻辑。我认为接口(或类)是属性集。

逻辑与运算符 & 相当于集合论中的 交集,定义为:

两个集合 AB 的交集是包含所有属于 A 且也属于 B 的元素的集合

在 TypeScript 中,交叉类型 也使用 & 运算符构建,并定义为:

接口 Colorful 和接口 Circle 的交叉类型是一个新类型,它具有 ColorfulCircle所有 成员

这与数学和集合论中所定义的交集完全相反。

我相信还有其他看法,但我想不出来。


4
类型 T | U 的成员是 members(T) | members(U),而类型 T & U 的成员是既属于 T 又属于 U 的成员,因此它们在 members(T)members(U) 的交集中。 - Lee
@jcalz - 我所说的 members(T) 是指类型 T 的值集,而不是由 T 定义的成员集。 - Lee
那样更有意义!抱歉! - jcalz
1
我不能否认命名太糟糕了。他们最好没有名称,而是解释 | 表示任意和 & 表示两者都是一种答案。现在我想知道实际的“交集”类型在哪里? - Changdae Park
1
∨ := ∪ (union)v 表示 or,因此 union 是 or,在 TypeScript 中用 | 表示。 - Magne
8个回答

59

这里还有一种思考方式。考虑四个集合:蓝色物品、红色物品、大物品和小物品。

如果你把所有蓝色物品和所有小物品进行交集(intersect),那么你得到的是属性的并集——集合中的所有物品都具有蓝色属性和小的属性。

但是,如果你将“蓝色小物品”和“红色小物品”的并集取出来,那么结果集合中普遍的只有小的属性。“蓝色小”的交集和“红色小”的交集产生的结果都是“小”。

换句话说,取值域的并集生成了一个交集的属性集合,反之亦然。

如下图所示:

图片描述

8
谢谢,翻译如下:非常描述得很清楚,并且准确地指出了我的错误:我之前是在想属性的集合,而不是实例的集合。 - sevcsik
8
你为什么会将类型看作实例的集合?是类型的实例吗?哪种类型...?这不是循环推理吗? - JoyalToTheWorld
3
将现存的物体归为具有名称的分类(我们现在称之为“类型”)是人类几十万年来一直在做的事情。 - Ryan Cavanaugh
4
从用户的角度来思考,说“A实现B或C”和“A同时实现B和C”比说“我们确定A具有既在B又在C中的属性”和“A具有任何在B或C中的属性”更清晰明了。 - SOFe
1
声明“这些集合的并集具有其属性的交集”毫无意义!你也可以说“这些集合的交集具有其属性的交集!”那也是正确的,但同样没有信息。 - Jon
显示剩余6条评论

22

A | B类型表示的是对象可以是A或者B中的任意一种。换句话说,这种类型的值来自于AB的值的并集

A & B类型表示的是对象同时是AB中的两种。换句话说,这种类型的值来自于AB的值的交集

在其他语言如C++中,命名和语义是相同的。


为了消除OP的误解:类型需要被视为开放描述(属性的下限),而不是封闭描述(属性的确切集合)。在数学中,类型是一组约束条件或公式,用于选择无限数量的可能元素。这就是多态性的起源。一个元素也可能满足其他未在类型中指定的约束条件。更大的约束集“A&B”意味着可以满足它们的元素集更小。 “A | B”弱化了约束条件。想象将集合编码为位向量,每个索引表示不同的元素。 - ChrisoLosoph

19

在这种情况下,你不能把类型看作对象属性的集合。我们可以通过查看标量变量及其可允许值的集合(而不是对象)来避免有关联合和交叉类型如何工作的混淆:

查看此处的标量变量
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

这里的"并集"和"交集"术语,在应用于可接受值集合时与集合论术语完全对应。

将可接受值(实例)的概念应用于对象类型有点棘手(因为集合论类比不太适用):

type A = {
  x: number
  y: number
}

type B = {
  y: number
  z: number
}

type I = A & B
type U = A | B
  • 类型为 A 的变量可以存储具有属性 xy(但没有其他属性)的对象实例。
  • 类型为 B 的变量可以存储具有属性 yz(但没有其他属性)的对象实例。
  • 在集合论中,上述两组对象实例的交集为空。然而,类型为 I交集类型变量可以存储既具有类型A的属性又具有类型B的属性(即 xyz;因此使用了 & 符号),这对应于两种类型属性的并集(因此导致混淆)。
  • 在集合论中,上述两组对象实例的并集不包括具有所有三个属性的对象。然而,类型为 U并集类型变量可以存储具有类型A的属性或具有类型B的属性的对象(逻辑上的OR,而不是XOR,即 xyyz,或 xyz;因此使用了 | 符号),这意味着两种类型属性的交集(在我们的示例中是y)保证存在(因此导致混淆)。
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 };

11
这里的混淆可能源于我们如何想象集合,即将交集/并集视为涉及类型的成员而不是类型本身。我制作了一张图片,希望能澄清这个概念:

Union/Intersection diagram

2
这个答案并没有真正解释为什么或如何使用显示“集合并集”的维恩图来描述“交集类型”(第二行反之亦然)。 - Dai
@Dai 因为联合体意味着它可以是A对象、B对象或AB对象。如果它是一个A对象,那么B成员就不存在,反之亦然,因此既不能保证A独有成员,也不能保证B独有成员。希望这样说得清楚。 - Drazen Bjelovuk

3
他们在前几天更新了文档,现在提供了一个简单示例的澄清描述:
可能会令人困惑的是,类型的联合似乎具有这些类型属性的交集。 这不是偶然的 - union 的名称来自类型理论。 联合 `number | string` 通过获取每种类型的值的并集而组成。 注意,在具有关于每个集合的对应事实的两个集合中,只有这些事实的交集适用于集合本身的并集。 例如,如果我们有一个戴帽子的高个子人的房间和一个戴帽子的西班牙语演讲者的房间,在组合这些房间之后,我们所知道的每个人唯一的共同点就是他们必须戴着帽子。
资料来源:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types

3

我也有同样的问题,不明白如何以相反的方式看待它。读完答案后,我想我可以从语言角度解释它,提供两个词的不同含义,并为相反的命名方法留下空间。

比较单词intersect的以下含义:

这颗彗星的轨道与地球的轨道相交。

在这个句子中,intersect的意思是在一个点或一组点相交。想象两个事物有某些共同之处(例如一个点),其他方面则不同。这就是数学和SQL中所称的交集

我们需要确定最大可实现的保护与最高潜在财务回报相交的位置。

在这里,intersect的意思是相互结合并产生影响,就像两个事物成为一个新酷炫的东西的组成部分。这是TypeScript中该单词的含义。

类似地,您可以将union理解为将不同的事物结合在一起的行为,在宽松的意义上-这就是数学和SQL中联合的含义; 但它不仅可以意味着加入,还可以意味着与共同的利益或目的结合在一起,这对应于TypeScript中union的含义。

受不同翻译(俄语中的intersect)启发:пересекать(也是to cross)和скрещивать(也是to crossbreed)。


1

这样想...

交集类型(A & B)

在对象的宇宙中,只有与 A 和 B 兼容的对象才属于交集类型的领域;因此包含了 A 和 B 的所有属性(两组属性的并集)。换句话说,如果对象位于 A 和 B 的交集中,它们具备 A 和 B 的所有属性(并集)。

并集类型(A | B)

在对象的宇宙中,只有与 A 或 B 兼容的对象才属于并集类型的领域;因此包含了 A 的所有属性或 B 的所有属性,可能在 A 和 B 之间有一些或没有共同属性。换句话说,如果对象位于 A 和 B 的并集中,它们可以具备 A 的所有属性或 B 的所有属性,并且可能具有 A 和 B 的共同属性(交集)。


这没有意义。好吧,两个集合的并集包含这两个集合的交集...但是两个集合的交集也包含这两个集合的交集...因为每个集合都是其自身的子集。这就像说;让我们把所有生物的集合称为猫,因为这些生物的一个子集是猫。 - Jon
具体来说,有什么让你感到不理解的吗? - Jorge Garcia

-1
type Head = {
  skin: string, 
  bones: string, 
  nouse: number, 
  eyes: number, 
  ears: number 
}

type Body = {
  skin: string, 
  bones: string, 
  arms: number, 
  foots: number
}

type Frankenstein = Head | Body

let frank: Frankenstein

`1 rule (only Head)`

frank = {skin: 'green', bones: 'skull', nouse: 1, eyes: 2, ears: 2}

`2 rule (only Body)`

frank = {skin: 'gray', bones: 'skeleton', arms: 2, foots: 2}

`3 rule (Body and Head all together)`
 
frank = {
  skin: 'green', 
  bones: 'skull', 
  nouse: 1, 
  eyes: 2, 
  ears: 2, 
  arms: 2, 
  foots: 2
}

`4 rule (Frank without arms or foots or ears or ...)`

frank = {
  skin: 'green', 
  bones: 'skull', 
  nouse: 1, 
  eyes: 2, 
  ears: 2,
  foots: 2
 }

frank = {
  skin: 'green', 
  bones: 'skull', 
  nouse: 1, 
  eyes: 2, 
  ears: 2, 
  arms: 2
}

frank = {
  skin: 'green',
  bones: 'skull',
  nouse: 1,
  eyes: 2,
  arms: 2, 
  foots: 2
}

`-1 rule (he can't exist without general parts)`

frank = {
  bones: 'skull',
  nouse: 1,
  eyes: 2,
  ears: 2,
  foots: 2} //error

frank = {
  skin: 'green',
  nouse: 1, 
  eyes: 2,
  ears: 2,
  arms: 2} //error

`-2 rule (and the MOST NOTABLY he can not exist without full kit of one of 
his parts, why - ask his best friend - TypeScript)`

frank = {
  skin: 'green',
  bones: 'skull',
  eyes: 2,
  ears: 2,
  foots: 2} //error

frank = {
  skin: 'green',
  bones: 'skull',
  nouse: 1,
  eyes: 2,
  arms: 2
} //error

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接