跳至内容

联合类型

变量或表达式的类型可以由多种类型组成。这称为联合类型。例如,在不同 if 分支中为同一个变量赋值时

if 1 + 2 == 3
  a = 1
else
  a = "hello"
end

a # : Int32 | String

在 if 结束时,a 将具有 Int32 | String 类型,读作“Int32 和 String 的并集”。此联合类型由编译器自动创建。在运行时,a 当然只是一种类型。这可以通过调用 class 方法来查看

# The runtime type
a.class # => Int32

可以通过使用 typeof 查看编译时类型

# The compile-time type
typeof(a) # => Int32 | String

一个联合可以包含任意数量的类型。在对类型为联合类型的表达式调用方法时,联合中的所有类型都必须响应该方法,否则会给出编译时错误。方法调用的类型是这些方法返回类型的联合类型。

# to_s is defined for Int32 and String, it returns String
a.to_s # => String

a + 1 # Error, because String#+(Int32) isn't defined

如果需要,可以将变量定义为编译时的联合类型

# set the compile-time type
a = 0.as(Int32 | Nil | String)
typeof(a) # => Int32 | Nil | String

联合类型规则

在一般情况下,当组合两个类型 T1T2 时,结果是联合 T1 | T2。但是,有一些情况会导致结果类型为不同的类型。

相同层次结构下类和结构的联合

如果 T1T2 位于同一层次结构中,并且它们最近的共同祖先 Parent 不是 ReferenceStructIntFloat 也不 是 Value,则结果类型为 Parent+。这被称为虚拟类型,它基本上意味着编译器现在将该类型视为 Parent 或其任何子类型。

例如

class Foo
end

class Bar < Foo
end

class Baz < Foo
end

bar = Bar.new
baz = Baz.new

# Here foo's type will be Bar | Baz,
# but because both Bar and Baz inherit from Foo,
# the resulting type is Foo+
foo = rand < 0.5 ? bar : baz
typeof(foo) # => Foo+

相同大小元组的联合

两个相同大小元组的联合将产生一个元组类型,该类型在每个位置都有类型的联合。

例如

t1 = {1, "hi"}   # Tuple(Int32, String)
t2 = {true, nil} # Tuple(Bool, Nil)

t3 = rand < 0.5 ? t1 : t2
typeof(t3) # Tuple(Int32 | Bool, String | Nil)

具有相同键的命名元组的联合

两个具有相同键(无论其顺序如何)的命名元组的联合将产生一个命名元组类型,该类型在每个键中都有类型的联合。键的顺序将是左侧元组中的键的顺序。

例如

t1 = {x: 1, y: "hi"}   # Tuple(x: Int32, y: String)
t2 = {y: true, x: nil} # Tuple(y: Bool, x: Nil)

t3 = rand < 0.5 ? t1 : t2
typeof(t3) # NamedTuple(x: Int32 | Nil, y: String | Bool)