第四章——可選型別(神奇值問題)

weixin_34253539發表於2017-12-27

每個優秀的程式設計師都知道使用“神奇”值是不好的。大多數語言都支援列舉型別,這樣可以更安全地表現一組同型別的離散值。

Swift通過“關聯值”的概念進一步擴充了列舉。下面的列舉值自己還與其他列舉值相關聯:

enum Optional<T> {
case None
case Some(T)
}
複製程式碼

在某些語言中,這被稱為“標籤聯合(tagged unions)”(或可辨識聯合)。它是一種特殊的資料結構,夠儲存一組“不同但是固定”的型別中某個型別的物件[1],通過標籤來表示具體哪一個型別正在被使用。在Swift中,這個標籤就是列舉成員(case語句)。

獲取管理值的唯一方法是通過switchif case let。和“哨兵值”不同的是,你必須顯式地檢查並解封那些包裝在Optional型別中的值。

Swift版本的find方法叫indexOf(),它返回的是Optional<Index>而不是直接的Index。這是通過協議擴充實現的:

extension CollectionType where Generator.Element: Equatable {
func indexOf(element: Generator.Element) -> Optional<Index> {
for idx in self.indices {
if self[idx] == element{
return .Some(idx)
}
}
// 沒找到,返回 .None
return .None
}
}
複製程式碼

由於可選型別非常基礎,所以Swift提供了很多語法支援,讓程式碼更加整潔。比如Optional<Index>可以寫成Index?。因為可選型別實現了NilLiteralConvertible協議,所以你可以直接寫nil而不是.None。除此之外,因為非可選型別的值(如idx)如果需要的話可以自動“升級”成可選型別,所以你可以直接寫return idx而不必寫return .Some(idx)

現在使用者就不會錯誤的使用無效的值了:

var array = ["one", "two", "three"]
let idx = array.indexOf("four")

//觸發編譯錯誤,removeIndex方法的引數是Int型別而不是Optional<Int>
array.removeAtIndex(idx)
複製程式碼

因此,如果返回值不為None,你必須“解封”可選型別才能得到其中的真正值。

switch array.indexOf("four") {
case .Some(let idx):
array.removeAtIndex(idx)
case .None:
break
}
複製程式碼

這裡的switch語句用非常普通的方式寫了支援可選型別的列舉程式碼。當列舉成員是.Some時解封了其中的關聯型別。這樣寫非常安全,但不是很適合閱讀和書寫程式碼。Swift2.0引入了字尾語法用來匹配可選型別,還引入了nil字面量來匹配列舉成員None

switch array.indexOf("four") {
case let idx? :
array.removeAtIndex(idx)
case nil:
break
}
複製程式碼

這種寫法還是顯得有些笨重,我們來看一看其他能根據實際用例,讓可選型別的處理更加簡單的寫法。

#譯者注:

[1]:比如根據上面的程式碼我們可以寫:

var t = T()
var opt = Optional.Some(t)
複製程式碼

於是opt儲存的列舉成員為Some,它的關聯值為t。

列舉型別中還可以有其他的列舉成員並且關聯其他型別的值,但列舉型別的變數在同一時間只能儲存一個列舉成員及其關聯值。這也就是之前所說的“一組不同但是固定的型別中某個型別的物件”。

相關文章