本系列的最後一篇文章,我們來了解一下 Swift 4.1 的最後一個重要特性,它也和泛型相關:關聯類型的遞歸約束。該特性解決了 Swift 泛型中的一大尷尬,關聯類型無法遞歸定義。
1. Sequence 的關聯類型
Swift 4.1 之前,我們是這樣約束 Sequence 的 SubSequence 關聯類型的:用註釋。
protocol Sequence {
// SubSequences themselves must be Sequences...
associatedtype SubSequence
// The subsequence conforms to Sequence...
func dropFirst(_ n: Int) -> Self.SubSequence
}
在 Swift 4.1 之前,如果 Sequence
的關聯類型 SubSequence
定義成 SubSequence : Sequence
這樣的遞歸形式,就會報以下錯誤:
Type may not reference itself as a requirement
,這就造成了實際的類型約束只能寫成註釋,而且需要在類型使用的地方都寫明註釋,叮囑大家需要遵循的規範。
Swift 4.1 對關聯類型的遞歸約束做了正式的支持,於是乎 Sequence
的 SubSequence
可以遞歸定義了,Sequence
的關聯類型完整定義如下:
protocol Sequence {
associatedtype Element
associatedtype Iterator : IteratorProtocol
where Iterator.Element == Element
associatedtype SubSequence : Sequence = AnySequence<Element>
where Element == SubSequence.Element,
SubSequence.SubSequence == SubSequence
// ...
}
在上面代碼中,我們看到了3個關聯類型,並且看到了一系列的約束,梳理如下:
-
Iterator
關聯類型約束了它必須是個IteratorProtocol
, -
Iterator
關聯類型約束了它的Element
跟Sequence
的Element
必須一致,建立起了關聯類型間的連接。 -
SubSequence
關聯類型約束了它必須是個Sequence
,這裏形成了關聯類型的約束的遞歸定義,也就是本文所提及的 Swift 4.1 的新特性。 -
SubSequence
關聯類型約束了它的Element
跟Sequence
的Element
一致。 -
SubSequence.Iterator.Element
關聯類型,由於上述約束以及遞歸定義,自動和之前所提及的所有Element
保持了一致,因此無需顯式申明。 -
SubSequence.SubSequence
與SubSequence
是同一個類型。 -
SubSequence
的默認類型推斷是AnySequence
,這就意味着AnySequence<Element>
滿足所有對SubSequence
的要求,特別值得注意的是它對於上一條約束的滿足:AnySequence
的dropFirst
方法的返回值類型必須是AnySequence
。還有個值得一提的例子是:Array
的SubSequence
是ArraySlice
,它也滿足上述特徵。
新特性介紹完了,接下來是一些衍生的相關知識。
2. 打造一個 DummySequence
好不容易做好了關聯類型的遞歸約束,試試看實現一個 struct DummySequence : Sequence
吧, 越簡單越好,只求編譯通過,你會怎麼做呢?
希望茫茫多的 Sequence 的方法沒有嚇着你,多虧了有默認實現,只要技巧正確,那麼大多數的方法我們的 DummySequence
不需要實現。我的參考答案如下:
struct DummySequence<Element>: Sequence {
typealias Iterator = AnyIterator<Element>
typealias SubSequence = AnySequence<Element>
func makeIterator() -> AnyIterator<Element> {
return AnyIterator<Element> { return nil }
}
}
其實還有優化空間,兩處的 typealias 也都可以刪除,只剩下 makeIterator
。第一處可以刪除,是因爲後續的makeIterator
可以推導出此處的類型;第二處也可以刪除,是因爲 AnySequence<Element>
就是不指定時的默認選擇類型。
3. Why AnySequence?
說到這裏,希望 AnySequence
已經引起了你足夠的好奇,標準庫中不會隨隨便便指定一個類型爲默認類型的。但是AnySequence
不是一個新的特性,對於瞭解 AnySequence
的前世今生的同學,本文已經結束了。對於不太瞭解的同學,我們一起來繼續學習,我想先舉一個簡單的例子。
protocol List {
associatedtype Element
func element(at index: Int) -> Element?
}
struct IntArrayList: List {
func element(at index: Int) -> Int? {
return nil
}
}
struct IntLinkedList: List {
func element(at index: Int) -> Int? {
return nil
}
}
struct DoubleLinkedList: List {
func element(at index: Int) -> Double? {
return nil
}
}
上述是個足夠簡化的仿造示例,對於這個例子,我想寫出以下的代碼,如何定義intLists呢?
for l in intLists {
_ = l.element(at: 0)
}
似乎很簡單,這樣不就行了?
// compile error
let intLists = [IntArrayList(), IntLinkedList()]
這樣寫的同學已經得到了編譯錯誤,似乎明確指定類型就可以了?
// compile error
let intLists: [List] = [IntArrayList(), IntLinkedList()]
// compile error
let intLists2: [List<Int>] = [IntArrayList(), IntLinkedList()]
依然是編譯錯誤,看過新特性第一篇的同學或許已經想到了些什麼。是的,List
不是泛型類型,僅僅是泛型約束。這時候就要出動 Type eraser 技術了。 代碼如下:
struct AnyList<Element>: List {
private let elementFunc: (Int) -> Element?
init<L>(_ base: L) where Element == L.Element, L : List {
elementFunc = base.element(at:)
}
func element(at index: Int) -> Element? {
return elementFunc(index)
}
}
它是一個包裝類型 (Wrapper),所以它還有個名字叫做 Type-erased wrapper,在我看來它有兩個作用:
- 幫助隱藏具體類型
- 用通用的泛型類型表達泛型約束
於是我們的問題解決:
// 編譯通過
let intLists = [AnyList(IntArrayList()),AnyList(IntLinkedList())]
for l in intLists {
_ = l.element(at: 0)
}
// 預期中的編譯不過
let mixedLists = [AnyList(IntLinkedList()), AnyList(DoubleLinkedList())]
在 Swift 標準庫中,有許多 Type eraser,例如:AnySequence
、AnyIterator
、AnyHashable
等等,希望上面已經用AnyList
已經闡明瞭它們的設計初衷。
小結
在本文中,我們討論了:
- 什麼是關聯類型的遞歸約束
- 關聯類型的遞歸約束在 Sequence 上的應用
- 如何實現一個最簡單的 Sequence 類型
- 什麼是Type Eraser,爲什麼要設計 AnySequence。
至此,Swift 4.1 的主要新特性介紹完畢了,回顧一下,所有的特性其實都與泛型相關,希望大家有所收穫。預告下番外篇:向 Swift 4.1 遷移。
Swift 4.1 新特性系列文章
Swift 4.1 新特性 (1) Conditional Conformance
Swift 4.1 新特性 (2) Sequence.compactMap
Swift 4.1 新特性 (3) 合成 Equatable 和 Hashable
Swift 4.1 新特性 (4) Codable的改進