Swift 4.1 新特性 (5) associatedtype的遞歸約束

本系列的最後一篇文章,我們來了解一下 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 對關聯類型的遞歸約束做了正式的支持,於是乎 SequenceSubSequence 可以遞歸定義了,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個關聯類型,並且看到了一系列的約束,梳理如下:

  1. Iterator 關聯類型約束了它必須是個 IteratorProtocol
  2. Iterator 關聯類型約束了它的ElementSequenceElement必須一致,建立起了關聯類型間的連接。
  3. SubSequence 關聯類型約束了它必須是個 Sequence,這裏形成了關聯類型的約束的遞歸定義,也就是本文所提及的 Swift 4.1 的新特性。
  4. SubSequence 關聯類型約束了它的ElementSequenceElement一致。
  5. SubSequence.Iterator.Element 關聯類型,由於上述約束以及遞歸定義,自動和之前所提及的所有Element 保持了一致,因此無需顯式申明。
  6. SubSequence.SubSequenceSubSequence 是同一個類型。
  7. SubSequence 的默認類型推斷是 AnySequence,這就意味着 AnySequence<Element> 滿足所有對 SubSequence 的要求,特別值得注意的是它對於上一條約束的滿足: AnySequencedropFirst 方法的返回值類型必須是 AnySequence。還有個值得一提的例子是:ArraySubSequenceArraySlice,它也滿足上述特徵。

新特性介紹完了,接下來是一些衍生的相關知識。

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,在我看來它有兩個作用:

  1. 幫助隱藏具體類型
  2. 用通用的泛型類型表達泛型約束

於是我們的問題解決:

// 編譯通過
let intLists = [AnyList(IntArrayList()),AnyList(IntLinkedList())]
for l in intLists {
    _ = l.element(at: 0)
}

// 預期中的編譯不過
let mixedLists = [AnyList(IntLinkedList()), AnyList(DoubleLinkedList())]

在 Swift 標準庫中,有許多 Type eraser,例如:AnySequenceAnyIteratorAnyHashable 等等,希望上面已經用AnyList已經闡明瞭它們的設計初衷。

小結

在本文中,我們討論了:

  1. 什麼是關聯類型的遞歸約束
  2. 關聯類型的遞歸約束在 Sequence 上的應用
  3. 如何實現一個最簡單的 Sequence 類型
  4. 什麼是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的改進

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章