幻影型別是自訂型別的一種型別參數,它不會出現在該型別的任何值建構子裡。
這有點抽象,我們來看個例子吧:
pub type Length(unit) {
Length(amount: Float)
}
在這個例子中,unit型別參數並沒有用在 Length值建構子裡,所以 unit就是幻影型別。
這個沒被用到的型別參數看起來好像沒什麼用,不過它可以替 Length值加上更多使用上的限制。
舉例來說,我們可以寫一個函式 double,把長度乘以二。它能處理任何單位的長度,所以它的型別參數是一個泛型型別變數。
// This function accepts all Length values
pub fn double(length: Length(unit)) -> Length(unit) {
Length(length.amount *. 2.0)
}
我們也可以寫一個函式 add_inch,它只在長度單位是英吋時才能運作。
// A unit type for inches. It is never constructed so we don't
// define any constructors for it.
pub type Inches
pub fn add_inch(length: Length(Inches)) -> Length(Inches) {
Length(length.amount +. 1.0)
}
add_inch函式不會接受其他單位參數的長度,這裡就是用幻影型別來確保只會使用正確的單位。
我們也可以寫一個函式,讓兩個長度值共用同一個型別變數,藉此確保它們的單位相同。
pub fn add(a: Length(unit), b: Length(unit)) -> Length(unit) {
Length(a.amount +. b.amount)
}
let two_meters: Length(Meters) = Length(2.0)
let two_inches: Length(Inches) = Length(2.0)
add(two_meters, two_meters)
// -> Length(4.0): Length(Meters)
add(two_meters, two_inches)
// Type error! The unit type parameters do not match.
幻影型別和不透明型別搭配起來很好用。如果其他模組無法建構 Length值,我們就能確保它們不會用錯誤的單位型別來建構,也能確保只有上面定義的函式能搭配這些值使用。
Lucy 經營著一家線上貼紙商店,專門販售以大家最愛的程式語言為主題的可愛貼紙。世界各地的顧客都會來向她購買貼紙,而面對這麼多種不同的貨幣,她應付起來有點吃力。
請寫一個程式,讓 Lucy 能用它來計算價格,同時確保她每次都使用正確的貨幣。
Usd、Eur和Jpy型別這些型別用來代表 Lucy 的顧客購買貼紙時所使用的各種貨幣。
它們要當作幽靈型別使用,不需要有任何建構子。
Money型別Money型別應該有一個代表金額的Int欄位、一個貨幣幽靈型別參數,並且是一個不透明型別。
dollar、euro和yen函式定義dollar、euro和yen函式,它們接受一個Int引數,並回傳帶有正確貨幣的Money值。
total函式定義total函式,它接受一個由Money值組成的陣列,並回傳該陣列中所有金額的總和。
total([euro(120), euro(200), euro(145)])
// -> euro(465)