什么是联合类型和交叉类型?

3

什么是联合类型和交叉类型?

我已经参考了这个问题,但一些小型工作类型系统会更好,不必是实际的。

具体来说,我所指的联合类型是这篇博客文章中提到的 而不是求和类型,其中伪代码看起来像:

{String, null} findName1() {
  if (...) {
    return "okay";
  } else {
    return null;
  }
}

wikipedia页面简要解释了交叉类型和联合类型,但似乎没有更多相关参考资料。


关于上面的例子,我们能告诉你什么?我认为应该很清楚了吧? - Ingo
是的,这个例子很清楚,但我仍然不明白交叉类型和联合类型如何相互作用(可能与其他类型一起),特别是在存在高阶函数类型和参数多态性的情况下。 - shhyou
3个回答

10
联合类型和交集类型只是将类型视为值的集合(大多数情况下是无限集)。如果您这样考虑类型,那么任何结果为集合的集合操作都可以应用于类型(值的集合)以创建一个新类型(值的集合),至少在概念上是这样的。
联合类型在某些方面类似于求和类型,您似乎熟悉这一点。实际上,我经常听到求和类型被描述为“有区别的联合”类型。基本区别在于,像(Haskell表示法)data FooBar = Foo Integer | Bar String这样的求和类型允许您告诉FooBar值包含一个Integer还是一个String(因为FooBar值带有FooBar标记)。即使我们编写了data FooBar = Foo Integer | Bar Integer,其中两种类型相同,“标记”也会添加额外信息,我们可以确定FooBar值是哪个整数。
联合类型等效物将是类似于(无效的Haskell)data FooBar = Integer | String的东西。 FooBar中的值仅是所有字符串值和所有整数值。如果我们将相同的两种类型制作成联合类型,例如data FooBar = Integer | Integer,则从逻辑上讲,它应该与Integer本身无法区分,因为集合与自身的联合就是自身。
原则上,您可以对类型A和B的并集U中的值执行的操作只是适用于A并且也适用于B的操作;仅适用于A或B的任何操作可能会得到错误的输入,因为U没有信息来说明它是A还是B。1 在类似于Haskell的类型系统的语言中,(未加区分的)联合类型不会很有趣,因为具体类型是不相交的2,因此适用于As和Bs的唯一操作适用于所有值(除非A等于B,在这种情况下,它只是适用于该单个类型的所有操作)。
但在某种程度上,类型类(如果您熟悉它们)是提供类似于联合类型的一种方式。被约束为某些类型类成员的多态类型有点像所有在类型类中的类型的联合(除了您不知道那些是什么,因为类型类原则上是开放的);您可以对这样的值执行的唯一操作是已经声明为适用于类型类中每个类型的值的操作。
联合类型在具有子类型的语言中很有趣(这在面向对象编程语言中很常见)。如果将具有相同超类型的两个子类型联合起来,则得到的内容至少支持超类型的操作,但它排除了超类型的任何其他子类型,因此它与仅使用超类型不同。
交集类型正是这个概念,但使用交集而不是并集。这意味着您可以在类型I中执行值的操作,该类型是类型A和B的交集,包括适用于A的操作以及适用于B的操作; I中的任何内容都保证既是A又是B,因此可以安全地提供给任何一种操作。
这些在具有类似Haskell类型系统的语言中也不会很有趣。由于具体类型是不相交的2,因此任何非平凡交集都为空。但是,类型类约束可以提供类似于交集类型的东西;如果在同一类型变量上添加多个类型类约束,那么可以在期望该类型变量的位置使用的唯一值是所有类型类的“交集”中的类型,可以使用的操作是 适用于任何类型类的操作。
1 您可以想象将操作A -> C和操作B -> D组合在一起,以获取操作(A | B) ->(C | D),就像您可以使用求和类型的标签将求和类型路由到适当的操作一样。但是对于完全通用的联合类型,这变得模糊起来。如果A和B重叠(并且一旦有了联合类型,重叠类型就会进入战场),那么在重叠区域中对值调用哪个操作?如果您可以确定它是A还是B,那么您实际上拥有一个求和类型而不是联合类型,如果应用一些任意的解决策略(例如选择A -> C操作,因为A 在联合��型的定义中较早列出,则在简单情况下工作正常,但是如果具有诸如(A | B)&(B | A)之类的类型,则会变得非常令人困惑(其中我使用&表示交集)。

2 虽然"不相交类型"的观点是有争议的。在像data Maybe a = Nothing | Just a这样的类型中,您可以合理地认为Nothing即使对于不同的a也是“相同值”。如果是这样的话,那么Maybe StringMaybe Integer的并集只包含一个Nothing(而不是既是“无字符串”的Nothing,也是“无整数”的Nothing)。而Maybe StringMaybe Integer的交集仅包含一个值,即Nothing


2
当存在函数类型和多态性时,我们应该如何解释交集和并集?例如,我们是否可以认为id函数属于Int -> IntString -> String类型,因此属于Int->Int&String->String - shhyou
1
对于联合类型,是否可以有一些构造方式,可以在某种程度上区分我们所给出的“值”的“种类”,并将其提取出来?例如,像{String,null}这样的类型,我们是否可以首先检查给定的值是否为String,如果是,则提取出String类型的值? - shhyou
1
@suhorng 对于你的第一个问题,像(Int->Int & String->String)这样的类型被文献称为“交集类型”。id函数满足这个条件,因此<code>id</code>是(Int->Int & String->String)的子类型。 - Ian
1
@Ben 这很有趣。那么,如果存在类型类约束的存在性类型,您是否认为这也与非标记联合类型有关? - CMCDragonkai
1
@CMCDragonkai 嗯,存在类型实际上非常接近我认为的“真正”的未标记联合工作方式,如果您将其视为类中所有类型的联合体。与Haskell的其他类型一样,您不能在运行时“切换”类型,您只能调用保证适用于类型类成员类型联合体中任何值的操作。而Haskell的常规类型类规则防止这些联合体重叠,这强化了我的想法,即重叠会妨碍具有真正联合类型的可靠的Haskell式类型系统。 - Ben
显示剩余5条评论

2
Whiley编程语言支持联合类型和交集类型。如果您将类型视为集合(即类型int是所有有效整数的集合),则联合类型对应于集合并,而交集类型对应于集合交。

在Whiley中,联合类型的经典示例是表示“可空”类型,如下所示:

null|int indexOf(string str, char c):
    for i in 0..|str|:
       if str[i] == c:
          return i // found a match
    // didn't find a match
    return null

在这里,类型 null|int 可以包含任何有效的整数,或者特殊值 null。在Whiley中,你不能对这种类型进行算术运算。因此,在使用返回值之前,必须首先进行类型测试以检查是否为null值。例如,像这样:
string replaceFirst(string str, char old, char new):
    idx = indexOf(str,old)
    if idx is int:
        str[idx] = new
    // return potentially updated string
    return str

我已经在Whiley中写了一些关于联合类型的文章,可以在这里这里找到。相似的是交集类型,尽管目前编译器对其支持不太好。


1
一种类型可以被看作是一组值。例如,如果 Boolean 是值为 truefalse 的集合,则说某个值具有类型 Boolean 意味着它是值 truefalse 中的一个。
请注意,一些类型(如 String)可能具有无限多个可能的值。
正如您可能知道的那样,联合和交集是集合操作,因此它们也适用于类型。例如,当一个人拥有类型 T1 = {male, female} 和 T2 = {not-applicable} 时,可以构建类型 T3 = T1 \union T2 = {male, female, not-applicable}。这种类型会在回答问题“你的第一个孩子的性别是什么?”时非常有用。由于有些人没有孩子,他们可以回答:不适用。

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