跳至內容

聯合類型

變數或表達式的類型可以由多種類型組成。這稱為聯合類型。例如,當在不同的 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 不是 ReferenceStructIntFloatValue,則結果類型為 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)