聯合

聯合型別 在 Crystal

1 個練習

關於 聯合型別

Crystal 允許一個變數由多種型別組成。 這稱為聯合型別。 在 Crystal 中,聯合型別通常由編譯器推斷而來。

Note

聯合型別在執行期仍然是單一型別,即使它是由多種型別組成。 這表示如果一個聯合型別是由 String 和 Int32 組成,它不會同時是兩者。 它會是 String 或 Int32 其中之一。

宣告聯合型別的方式,是用豎線(|)分隔各個型別。 它們通常會放在括號中,但這並非必要。 最常見的聯合型別是 (T | Nil),其中 T 是某種型別;這會出現在可能回傳 Nil 的方法中。 這也稱為變數可為 nil。

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

聯合型別不限於兩種型別,你想要幾種都可以。

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof 與 Object#class

取得變數型別的方法有兩種。 可以使用 typeof,或使用 Object#class。 差別在於 typeof 會回傳變數在編譯期的型別,而 Object#class 則會回傳執行期的型別。 這表示如果你想確認某個變數是不是聯合型別,Object#class 沒辦法告訴你,因為它只會回傳執行期的型別,而執行期的型別是單一型別。

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

聯合型別的運算

由於聯合型別在執行期是單一型別,所有標準運算都能作用在它上面。 但在編譯程式碼時,編譯器需要知道它究竟是哪一種型別。 因此,當你想使用特定型別的運算時,程式碼必須安排成讓它只可能是其中一種型別。

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

Crystal 確實有一個專門處理聯合型別的方法:is_a? 方法,它接受一個型別作為引數,並回傳布林值。 nil? 方法是 is_a?(Nil) 的簡寫。 把 is_a? 方法放進控制運算式,就能告訴編譯器它屬於哪一種型別,藉此保證它就是該型別。 而對於 else 分支,則可保證它不是該型別。

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

is_a? 的引數不限於單一型別,也可以傳入聯合型別。 它也可以搭配 && 來處理多種型別。

Note

is_a? 方法在搭配控制運算式使用時,不能是實例變數或類別變數。 這些必須先指定給區域變數。

as

讓聯合型別變成單一型別的方法之一,是安排成只有在型別是特定型別時才會進入某個分支。 另一種做法是使用 as 方法。 它會透過執行期檢查,把聯合型別變成單一型別。 如果型別不是預期的型別,就會拋出例外。

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

這種做法只適用於你確定型別就是預期的型別,或者你希望在型別不符時拋出例外。

在設定不當的情況下使用這種做法,可能會導致非預期的行為。

as?

as? 的運作方式和 as 非常類似,但如果型別不是預期的型別,它會回傳 nil 而不是拋出例外。 這表示它會回傳預期型別與 Nil 的聯合型別。

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

可為 nil 的簡寫

可為 nil 表示變數可以是某種型別或 Nil。 這可以寫成 (T | Nil)。 但由於可為 nil 的型別相當常見,因此有個簡寫:T?。

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

推斷聯合型別

如果編譯器不確定是哪一種型別,就會推斷出聯合型別。 例如,在下列程式碼中,編譯器不知道 foo 是哪一種型別,因為它可能是 String 或 Int32。

if true
  foo = 1
else
  foo = "Hello"
end

typeof(foo) # => (Int32 | String)

這種推斷會自動發生,其他情境下也是如此;例如編譯器會推斷某個方法回傳聯合型別。

character = "Hello world"[10]?
typeof(character) # => (Char | Nil)
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 聯合型別

練習已鎖定

再解鎖 1 個練習,就能練習 聯合型別