【譯】Swift 泛型宣言

四娘發表於2017-11-26

原文:Generics Manifesto -- Douglas Gregor

譯者注

在我慢慢地深入使用 Swift 之後,碰壁了很多次,很大一部分都是因為 Swift 的泛型系統導致的,很多抽象都沒辦法很好地表達出來,所以就翻譯了這篇文章來學習一下 Swift 的泛型。

文章裡特別提到了要用官方提到的用語來討論,所以那些 feature 的名稱我都會保留英文。

簡介

“完善的泛型系統” 這個 Swift 3 的目標到目前為止都不是那麼的明確:

完善的泛型系統: 泛型功能已經在大量的 Swift 庫中使用,特別是標準庫。然而,標準庫所需的的一大堆泛型功能,都需要泛型系統完整的實現,包括了 Recursive Protocol Constraints 協議遞迴約束,Condition Comformance 讓受約束的擴充遵循一個新協議的能力(例如,一個元素 Equatable 的陣列也應該是 Equatable 的),諸如此類。Swift 3.0 應該提供這些標準庫需要的泛型功能,因為它們會影響到標準庫的 ABI。

這條資訊將“完善的泛型系統”展開來具體描述。這不是任何一個核心團隊的 Swift 3.0 開發計劃,但這包含了大量核心團隊和 Swift 開發者的討論,包括編譯器和標準庫。我希望可以實現這幾個事情:

  • 討論出一個 Swift 泛型的具體願景,討論應該在最初的泛型設計文件的基礎上進行,讓我們可以有一些更加具體的全面的東西可以討論。

  • 建立一些專門用語來概括 Swift 開發者使用的功能,讓我們的討論可以更加高效(“噢,你建議的這個東西我們稱為 'conditional conformances';你可以看一下這個討論程式“)。

  • 參與更多社群的討論,讓我們可以考慮社群裡一些功能設計。甚至還可以直接實現其中一部分。

像這樣的資訊可以在獨立的討論程式裡進行。為了讓我們的討論儘可能獨立,我會要求討論程式裡只討論主題功能的願景:如何讓各個設計更好得融合到一起,還缺乏哪些設計,這些設計是否符合 Swift 的長期願景,諸如此類。關於特定語言功能的討論,例如,Conditional Conformance 的語法和語義,或者是編譯器的實現,標準庫的使用,請重新開一個討論程式,並且使用的官方對於該功能的稱謂。

這條資訊涵蓋了很多細節;我已經嘗試過不同功能的粗略分類,並且保持簡要的描述去限制總體長度。這些大部分都不是我的主意,我提供的一些語法只是通過程式碼表達我的想法,也是之後會改的東西。並非所有的功能都會得到實現,或許在不久的將來,也或許永遠不會,但它們都交織在一起形成了一個整體。比起那些之後會很有趣的功能,我會在我覺得近期重要的討論後面加上 。總體而言, 號意味著這個功能會對於 Swift 標準庫的設計和實現有著顯著的影響。

官話說夠了,讓我們來討論一下功能吧。

去除不必要的限制

由於 Swift 編譯器的實現,在使用泛型的時候有很多限制。去掉這些限制也只是實現問題,不需要引入新的語法或語義。我把這些列出來的主要原因有兩個:第一,這是一個對於現有模型功能的回顧,第二,我們需要這些功能實現上的幫助。

遞迴協議遵循 Recursive protocol constraints(*)

這個功能已經在 SE-0157 裡通過了,並且會在 SR-1445 裡進行跟進。

目前,一個 associatedType 不能遵循與之關聯的協議(或者協議的父協議)。例如,在標準庫裡一個 SequanceSubSequence 必須是它自身 —— 一個 Sequence

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  ...
  associatedtype SubSequence : Sequence   
  // 目前這樣的寫法是不合法的,但它應該合法
}複製程式碼

它讓"子序列必須是一個序列"這個要求,遞迴地約束到每一個子序列的子序列的子序列的子序列...不幸的是,編譯器目前會不接受這個協議,並且沒有別的辦法表達出這一個抽象的準確含義。

泛型巢狀 Nested Generics

這個功能已經在 SR-1446 跟進了,並且在 Swift 3.1 實現了。

目前,一個泛型型別沒辦法巢狀在另一個泛型型別裡,例如這樣:

struct X<T> {
  struct Y<U> { }  
  // 目前這樣的寫法是不合法的,但它本應是合法的
}複製程式碼

這點沒什麼好說的:編譯器只需要簡單地改進對於泛型巢狀的處理就可以了。

Concrete same-type requirements

這個功能已經在 SR-1009 跟進並且在 Swift 3.1 實現了。

目前,一個受約束的擴充不能使用具體的型別來對泛型引數進行約束。例如:

extension Array where Element == String {
  func makeSentence() -> String {
    // 第一個單詞首字母大寫,用空格把單詞串聯起來,加個句號,之類的
  }
}複製程式碼

這是一個呼聲很高的功能,可以很好地融入現在的語法和語義。這樣做還能引入一些新的語法,例如,擴充 Array<String>,這基本上就是另一個新功能的範疇了:請檢視“引數化擴充 Parameterized extensions”。

引數化其它宣告

有很多 Swift 的宣告都不能使用泛型引數; 其中有一些可以很自然地擴充泛型格式,並且不會破壞現有的語法,但如果能夠直接使用泛型的話會變得更加強大。

泛型型別別名 Generic typealiases

這個功能已經在 SE-0048 裡通過並且在 Swift 3.1 裡實現了。

型別別名被允許帶上泛型引數,並且只是別名(並不會引入新的型別)。例如:

typealias StringDictionary<Value> = Dictionary<String, Value>

var d1 = StringDictionary<Int>()
var d2: Dictionary<String, Int> = d1 
// okay: d1 和 d2 都是相同的型別, Dictionary<String, Int>複製程式碼

泛型下標 Generic subscripts

這個功能已經在 SE-0148, was tracked by SR-115 裡通過,在 SR-115 跟進,並且在 Swift 4.0 裡實現了。

下標被允許使用泛型引數。例如,我們可以給 Collection 帶上一個泛型下標,允許我們通過任意滿足要求的索引去獲取到相應的值:

extension Collection {
  subscript<Indices: Sequence where Indices.Iterator.Element == Index>(indices: Indices) -> [Iterator.Element] {
    get {
      var result = [Iterator.Element]()
      for index in indices {
        result.append(self[index])
      }

      return result
    }

    set {
      for (index, value) in zip(indices, newValue) {
        self[index] = value
      }
    }
  }
}複製程式碼

泛型常數 Generic constants

let 常數被允許帶上泛型引數,可以根據不同的使用方式來產生不同的值。例如,特別是在使用字面量時會很實用:

let π<T : ExpressibleByFloatLiteral>: T = 
    3.141592653589793238462643383279502884197169399複製程式碼

並且 Clang importer 可以在引入巨集的時候很好地利用這個功能。

引數化擴充 Parameterized extensions

讓擴充自身可以被引數化,可以模式匹配到一些結構化的型別上,例如,可以擴充一個元素為 Optional 的陣列:

extension<T> Array where Element == T? {
  var someValues: [T] {
    var result = [T]()
    for opt in self {
      if let value = opt { result.append(value) }
    }
   return result
  }
}複製程式碼

我們還可以把它使用到協議擴充上:

extension<T> Sequence where Element == T? {
  var someValues: [T] {
    var result = [T]()
    for opt in self {
      if let value = opt { result.append(value) }
    }
   return result
  }
}複製程式碼

請注意這裡是在擴充一個抽象型別,我們還可以使用 Concrete same-type constraint 來簡化語法:

extension<T> Array<T?> {
  var someValues: [T] {
    var result = [T]()
    for opt in self {
      if let value = opt { result.append(value) }
    }
   return result
  }
}複製程式碼

當我們與具體型別打交道時,就可以使用這種語法來優化泛型型別特例化之後的表達(也就是上面所說的 Concrete same-type requirements):

extension Array<String> {
  func makeSentence() -> String {
    // 第一個單詞首字母大寫,用空格把單詞串聯起來,加個句號,之類的
  }
}複製程式碼

輔助性擴充

我們可以對泛型系統進行一些輔助性擴充,雖然不會對於 Swift 表達能力產生根本性的改變,但可以讓它表達得更加準確。

協議的抽象約束 Arbitrary requirements in protocols(*)

這個功能已經在 SE-0142 裡通過並且在 Swift 4 裡實現了。

目前,一個新的協議可以繼承自其它協議,引入新的 associatedType,並且給 associatedType 加上一些約束(通過重新宣告一個新的父協議 associatedType)。然而,這並不能表達更多通用的約束。在“Recursive protocol constraints”的基礎上建立的例子,我們真的很希望 SequenceSubSequenceElement 型別與 Sequence 的一樣:

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  ...
  associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}複製程式碼

where 扔在 associatedType 後面並不是那麼理想,但這應該是另一個討論程式該探討的問題。

協議的別名和協議擴充 Typealiases in protocols and protocol extensions(*)

這個功能已經在 SE-0092 裡通過並且在 Swift 3 裡實現了。

現在 associatedType 已經有了單獨的關鍵字了(謝天謝地!),在這裡再一次使用 typealias 就變得很合理了。再次借用 Sequence 協議的例子:

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  typealias Element = Iterator.Element   
  // 歡呼吧! 現在我們可以通過 SomeSequence.Element 來引用了
  // 而不是冗長的 SomeSequence.Iterator.Element
}複製程式碼

預設泛型引數 Default generic arguments

泛型引數可以有提供預設值的能力,在型別引數未被指定,並且型別推導無法決定具體型別引數時很實用。例如:

public final class Promise<Value, Reason=Error> { ... }

func getRandomPromise() -> Promise<Int, Error> { ... }

var p1: Promise<Int> = ...
var p2: Promise<Int, Error> = p1     
// okay: p1 跟 p2 都是相同的型別 Promise<Int, Error>
var p3: Promise = getRandomPromise() 
// p3 型別推導的結果是 Promise<Int, Error>複製程式碼

把 “class” 抽象為一種約束 Generalized class constraints

這個功能是SE-0092 提案實現後的形態,並且在 Swift 4 裡實現了。

class 約束目前只可以在定義協議時使用。我們還可以拿它來約束 associatedtype 和型別引數宣告:

protocol P {
  associatedtype A : class
}

func foo<T : class>(t: T) { }複製程式碼

作為這的一部分,奇妙的 AnyObject 協議可以使用 class 來取代,並且成為一個型別別名:

typealias AnyObject = protocol<class>複製程式碼

更多細節,請檢視 "Existentials" 小節,特別是 “Generalized existentials”。

允許子類重寫預設的實現 Allowing subclasses to override requirements satisfied by defaults(*)

當一個父類遵循一個協議,並且協議裡的一個要求被協議擴充實現了,那子類就沒辦法重寫這個要求了。例如:

protocol P {
  func foo()
}

extension P {
  func foo() { print("P") }
}

class C : P {
  // 獲得協議擴充給予的能力
}

class D : C {
  /*重寫是不被允許的!*/ 
  func foo() { print("D") }
}

let p: P = D()
p.foo() 
// gotcha:這裡列印了 "P",而不是 “D”!複製程式碼

D.foo 應該顯式地標記為 "override" 並且被動態呼叫。

泛型模型的主要擴充

不像那些輔助性擴充,泛型模型的主要擴充給 Swift 的泛型系統提供了更強大的表達能力,並且有更顯著的設計和實現成本。

有條件的遵循 Conditional conformances(*)

這個功能已經在 SE-0092 裡通過,並且正在開發中。(譯者注:截止到發稿時,這個功能已經實現了,並且標準庫裡已經開始使用這個功能開始重構了)

Conditional Conformance 表達了這樣的一個語義:泛型型別在特定條件下會遵循一個特定的協議。例如,Array 只會在它的元素為 Equatable 的時候遵循 Equatable

extension Array : Equatable where Element : Equatable { }

func ==<T : Equatable>(lhs: Array<T>, rhs: Array<T>) -> Bool { ... }複製程式碼

Conditional Conformance 是一個非常強勁的功能。這個功能其中一個重要的點就在於如何處理協議的疊加遵循。舉個例子,想象一個遵循了 Sequence 的型別,同時有條件得遵守了 CollectionMutableCollection

struct SequenceAdaptor<S: Sequence> : Sequence { }
extension SequenceAdaptor : Collection where S: Collection { ... }
extension SequenceAdaptor : MutableCollection where S: MutableCollection { }複製程式碼

這在大部分時候都可以被允許的,但我們需要應對“疊加”遵循被拒絕的情況:

extension SequenceAdaptor : Collection 
    where S: SomeOtherProtocolSimilarToCollection { } 
// trouble:兩種 SequenceAdaptor 遵循 Collection 的方式複製程式碼

關於同一個型別多次遵循統一個協議的問題,可以檢視 "Private conformances" 小節。

譯者注:

我個人感覺這裡的例子舉的不是很好(如果我的理解是錯的請務必留言告訴我),參考 Swift 官方文件 Protocols 小節裡的最後一段:

“If a conforming type satisfies the requirements for multiple constrained extensions that provide implementations for the same method or property, Swift will use the implementation corresponding to the most specialized constraints.”

約束越多的 conformance 優先順序越高。第一段程式碼最後一句改成 extension SequenceAdaptor : Collection where S: MutableCollection { } 可能會更好,由於 MutableCollection 繼承自 Collection,所以 where S: MutableCollectionwhere S: Collection 更加具體,系統會優先使用這一個 conformance 裡的實現。

而第二段程式碼裡那個 SomeOtherProtocolSimilarToCollection 協議可能不繼承於 Collection,所以 where S: SomeOtherProtocolSimilarToCollectionwhere S: Collection 約束是一樣多的,它們的優先順序相同,此時系統就不知道該選哪一個 conformance 裡的實現。

可變泛型 Variadic generics

目前,一個泛型引數列表只能包含固定數量的泛型引數。如果要讓一個型別可以容納任意數量的泛型引數,那就只能建立多個型別了(譯者注:我想起了 RxSwift 的 zip 函式?)。例如,標準庫裡的 zip 函式。當提供兩個引數時就會呼叫其中一個 zip 函式:

public struct Zip2Sequence<Sequence1 : Sequence,
                           Sequence2 : Sequence> : Sequence { ... }

public func zip<Sequence1 : Sequence, Sequence2 : Sequence>(
              sequence1: Sequence1, _ sequence2: Sequence2)
            -> Zip2Sequence<Sequence1, Sequence2> { ... }複製程式碼

支援三個引數只需要複製貼上就可以了,here we go:

public struct Zip3Sequence<Sequence1 : Sequence,
                           Sequence2 : Sequence,
                           Sequence3 : Sequence> : Sequence { ... }

public func zip<Sequence1 : Sequence, Sequence2 : Sequence, Sequence3 : Sequence>(
              sequence1: Sequence1, _ sequence2: Sequence2, _ sequence3: sequence3)
            -> Zip3Sequence<Sequence1, Sequence2, Sequence3> { ... }複製程式碼

可變泛型可以允許我們把一系列的泛型引數抽象出來。下面的語法無可救藥地被 C++11 可變模版影響(抱歉),在宣告的左邊加上一個省略號(“...”),讓它成為一個“引數集合“,可以包含零到多個引數;把省略號放在型別/表示式的右邊,可以把帶型別和表示式的引數集合展開成單獨的引數。重要的是我們終於可以把泛型引數的集合抽象出來了:

public struct ZipIterator<... Iterators : IteratorProtocol> : Iterator {  
  // 零或多個型別引數,每一個都遵循 IteratorProtocol 協議
  public typealias Element = (Iterators.Element...)                       
  // 一個包含了每一個迭代器的元素型別的元組

  var (...iterators): (Iterators...)    
  // 零或多個儲存屬性,每一個的型別為每一個迭代器的型別
  var reachedEnd = false

  public mutating func next() -> Element? {
    if reachedEnd { return nil }

    guard let values = (iterators.next()...) {   
    // 呼叫每一個迭代器的 "next" 方法,將結果放入一個名為 “values” 的元組
      reachedEnd = true
      return nil
    }

    return values
  }
}

public struct ZipSequence<...Sequences : Sequence> : Sequence {
  public typealias Iterator = ZipIterator<Sequences.Iterator...>   
  // 獲取我們 Sequence 裡的迭代器 zip 之後的迭代器

  var (...sequences): (Sequences...)    
  // 零或多個儲存屬性,型別為 Sequences 裡的每一個 Sequence 的型別

  // ...
}複製程式碼

這樣的設計對於函式引數也一樣適用,所以我們可以把多個不同型別的函式引數打包起來:

public func zip<... Sequences : SequenceType>(... sequences: Sequences...)
            -> ZipSequence<Sequences...> {
  return ZipSequence(sequences...)
}複製程式碼

最後,這也可以和把元組“拍平”的操作符的討論聯絡起來。例如:

func apply<... Args, Result>(fn: (Args...) -> Result,    
// 函式接收一定數量的引數然後產生結果
                           args: (Args...)) -> Result {  
                           // 引數的元組
  return fn(args...)                                     
  // 把元組 "args" 裡的引數展開為單獨的引數
}複製程式碼

結構化型別的擴充 Extensions of structural types

目前,只有真正意義上的型別(類,結構體,列舉,協議)可以被擴充。我們可以預想到擴充結構化型別,特別是型別明確的元組型別,例如遵循協議。把 Variadic generics,Parameterized extension 和 Conditional conformances 結合起來,就可以表達“如果元組的所有元素都 Equtable,那元組也遵循 Equatable”:

extension<...Elements : Equatable> (Elements...) : Equatable {   
  // 將元組 "(Elements)" 型別擴充為 Equatable
}複製程式碼

這裡有幾個自然的邊界:擴充的型別必須是一個實際意義上的結構化型別。並非所有型別都可以被擴充:

extension<T> T { 
  // error:這既不是一個結構化型別也不是一個實際型別
}複製程式碼

在你覺得自己聰明到可以使用 Conditional conformance 讓每一個遵循協議 P 的型別 T 同時遵循 Q 之前,請檢視下面 "Conditional Conformance via protocol extensions" 小節:

extension<T : P> T : Q { 
  // error:這既不是一個結構化型別也不是一個實際的型別
}複製程式碼

改善語法

泛型語法還有很多可以改善的地方。每一個都列舉起來會很長,所以我只說幾個 Swift 開發者已經充分討論過的。

協議的預設實現 Default implementations in protocols(*)

目前,協議裡的成員絕對不可以有實現。如果遵循的型別沒有提供實現的話,就可以使用協議擴充的預設實現:

protocol Bag {
  associatedtype Element : Equatable
  func contains(element: Element) -> Bool

  func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool {
    for x in elements {
      if contains(x) { return true }
    }
    return false
  }
}

struct IntBag : Bag {
  typealias Element = Int
  func contains(element: Int) -> Bool { ... }

  // okay:containsAll 實現的要求已經被 Bag 的預設實現滿足了
}複製程式碼

現在可以直接通過協議擴充來達到這一點,因此這類的功能應該被歸為語法的加強:

protocol Bag {
  associatedtype Element : Equatable
  func contains(element: Element) -> Bool

  func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool
}

extension Bag {
  func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool {
    for x in elements {
      if contains(x) { return true }
    }
    return false
  }
}複製程式碼

where 從句移出尖括號(*)

SE-0081 裡通過並且在 Swift 3 裡實現了。

泛型函式的 where 從句很早就存在了,儘管呼叫方更關心的是函式引數和返回型別。把 where 移出尖括號這更加有助於我們忽略尖括號的內容。想一想上面 containsAll 函式的簽名:

func containsAll<S: Sequence where Sequence.Iterator.Element == Element>(elements: S) -> Bool複製程式碼

where 從句移到函式簽名的最後,那函式最重要的那些部分 —— 函式名,泛型引數,引數,返回型別 —— 就會優先於 where 從句了:

func containsAll<S: Sequence>(elements: S) -> Bool
       where Sequence.Iterator.Element == Element複製程式碼

protocol<...> 重新命名為 Any<...> (*)

SE-0095 裡作為 “把 'protocol' 替換為 'P1 & P2'” 通過,並且在 Swift 3 裡實現了。

protocol<...> 語法在 Swift 裡有一點怪異。它通常是用來建立一個型別的容器,把協議組合到一起:

var x: protocol<NSCoding, NSCopying>複製程式碼

它的怪異在於這是一個小寫字母開頭的型別名,而大多數的 Swift 開發者都不會跟這個功能打交道,除非他們去檢視 Any 的定義:

typealias Any = protocol<>複製程式碼

“Any” 是這個功能更好的稱謂。沒有尖括號的 Any 指的是“任意型別”,而有尖括號 “Any” 現在可以充當 protocol<>

var x: Any<NSCoding, NSCopying>複製程式碼

這讀起來會更好:“任何遵循 NSCodingNSCopying 的型別“。更多細節請檢視 "Generalized existentials" 小節。

也許會有...

有一些功能直到它們可以融入 Swift 的泛型系統之前,都需要反反覆覆地進行討論,目前它們是否適合 Swift 還不那麼明確。重要的問題是在這個類別裡的任何功能都不是“可以做”或者“我們可以很酷地表達出來的事情”,而是“Swift 開發者每天怎樣會從這個功能裡獲益?”。在沒有強大的應用場景之前,這些功能“很可能”都不會更進一步。

協議擴充成員的動態派發 Dynamic dispatch for members of protocol extensions

目前只有協議裡宣告的成員會使用動態派發,並且會在呼叫時產生意外:

protocol P {
  func foo()
}

extension P {
  func foo() { print("P.foo()") }
  func bar() { print("P.bar()") }
}

struct X : P {
  func foo() { print("X.foo()") }
  func bar() { print("X.bar()") }
}

let x = X()
x.foo() // X.foo()
x.bar() // X.bar()

let p: P = X()
p.foo() // X.foo()
p.bar() // P.bar()複製程式碼

Swift 應該選用一個模型去讓協議擴充裡的成員使用動態派發。

泛型引數名稱 Named generic parameters

當指定泛型型別的泛型引數時,引數總是依賴於它的位置:Dictionary<String, Int> 是一個 Key 型別為 StringValue 型別為 IntDictionary。但也可以給引數加上標籤:

var d: Dictionary<Key: String, Value: Int>複製程式碼

這樣的功能會在 Swift 擁有 Default generic arguments 之後更加具有存在意義,因為泛型引數的標籤可以讓我們跳過一個已經有預設值的引數。

將值作為泛型引數 Generic value parameters

目前,Swift 的泛型引數只能是型別。我們可以聯想到使用值作為泛型引數:

struct MultiArray<T, let Dimensions: Int> { 
  // 指定陣列的維度
  subscript (indices: Int...) -> T {
    get {
      require(indices.count == Dimensions)
      // ...
    }
}複製程式碼

一個恰如其分的功能也許可以讓我們表達固定長度的陣列或向量型別,作為標準庫的一部分,也許這可以讓我們更方便地實現一個維度分析庫。這個功能是否實現取決於,我們怎麼去定義一個“常量表示式”,並且需要深入型別的定義,所以這是一個“也許會“實現的功能。

更高層次的型別 Higher-kinded types

更高層次的型別允許我們表達相同抽象型別在同一個協議裡兩種不同的具象。例如,如果我們把協議裡的 Self 看作是 Self<T>,這就讓我們可以討論 Self<T> 和其他型別 USelf<U> 之間的關係。例如,讓集合的 map 操作返回相同的元素型別,但使用不同的操作:

let intArray: Array<Int> = ...
intArray.map { String($0) } // 產生 Array<String>
let intSet: Set<Int> = ...
intSet.map { String($0) }   // 產生 Set<String>複製程式碼

候選語法是從 higher-kinded types 的一個討論程式超過來的,那裡面使用了 ~= 作為“相似”約束來描述一個 Functor 協議:

protocol Functor {
  associatedtype A
  func fmap<FB where FB ~= Self>(f: A -> FB.A) -> FB
}複製程式碼

泛型引數指定型別引數後的使用 Specifying type arguments for uses of generic functions

不在 Swift 4 的計劃內

泛型函式的型別引數總是通過型別推導來決定。例如:

func f<T>(t: T)複製程式碼

不能直接指定 T 的情況下:要麼直接呼叫 fT 會根據引數型別決定),要麼就在給定函式型別的場景下使用 f(例如 let x: (Int) -> Void = f 會推匯出 T = Int)。我們允許在這裡指定型別:

let x = f<Int> // x 的型別為 (Int) -> Void複製程式碼

不太可能會有...

這個分類裡的功能已經被提過很多次了,但它們都沒辦法很好地融入 Swift 的泛型系統,因為它們會造成這個模型的一部分變得過於複雜,有無法接受的實現限制,或者與現有的功能有重疊的部分。

泛型協議 Generic protocols

一個最經常被提起的功能就是引數化協議本身。例如,一個表明 Self 型別可以使用某個特定型別的 T 來構造的協議:

protocol ConstructibleFromValue<T> {
  init(_ value: T)
}複製程式碼

這個功能隱藏的含義是讓給定型別有兩種不同的方式來遵循協議。一個 Real 型別也許可以同時使用 FloatDouble 來構造:

struct Real { ... }
extension Real : ConstructibleFrom<Float> {
  init(_ value: Float) { ... }
}
extension Real : ConstructibleFrom<Double> {
  init(_ value: Double) { ... }
}複製程式碼

大部分對於這個功能的需求本質上需要的是另外的功能。例如他們可能只是想要一個引數化的 Sequence

protocol Sequence<Element> { ... }

func foo(strings: Sequence<String>) {  
  // 操作字串集合
  // ...
}複製程式碼

這裡實際的功能需求是 “任何遵循了 Sequance 協議並且 ElementString 的型別”,下面 “Generalized existentials” 這一小節會講到。

更重要的是,使用泛型引數去構建 Sequence 的模型雖然很誘人,但這是錯誤的:你不會想要一個型別有多種遵循 Sequence 的途徑,抑或是讓你的 for..in 迴圈出問題,並且你也不會想失去 Element 型別不固定的 Sequence 的動態型別轉換能力(還是那句話,去看 "Generalized existentials" 吧)。類似於上面 ConstructableFromValue 協議的用例都太低估了協議泛型引數帶來的麻煩了。我們最好還是放棄協議泛型引數吧。

隱祕遵循 Private conformances

現在,協議的遵循的可見性不能低於型別和協議的最低訪問許可權。因此,一個 public 的型別遵循了一個 public 的協議的話,這個遵循也必須是 public 的。可以想象一下去掉這個限制,我們就可以引入隱祕遵循:

public protocol P { }
public struct X { }
extension X : internal P { ... } 
// X 遵循了 P, 但只在 module 內部可見複製程式碼

The main problem with private conformances is the interaction with dynamic casting. If I have this code:

隱祕遵循最主要的問題就在於動態型別轉換,如果我把程式碼寫成這樣:

func foo(value: Any) {
  if let x = value as? P { print("P") }
}

foo(X())複製程式碼

在這種情況下,應該列印 "P"?如果 foo() 是在同一個 module 內的時候會怎麼樣?如果這個呼叫是在 module 內部產生的時候呢?前兩個問題的回答都需要給動態型別轉換引入顯著的複雜度,並且會把問題帶到動態轉換產生的 module 裡(第一個選擇)或資料的結構(第二個選擇),而第三個答案會破壞掉靜態型別和動態型別的系統。這些都不是可接受的結果。

通過協議擴充有條件地遵循 Conditional conformances via protocol extensions

我們經常收到讓協議遵循另一個協議的請求。這會把 "Conditional Conformance" 擴充到 protocol extension 上。例如:

protocol P {
  func foo()
}

protocol Q {
  func bar()
}

extension Q : P { 
  // 任何遵循 Q 的型別都會遵循 P
  func foo() {    
    // 因為 "bar" 的存在滿足了 "foo" 的實現要求
    bar()
  }
}

func f<T: P>(t: T) { ... }

struct X : Q {
  func bar() { ... }
}

f(X()) 
// okay: X 通過 Q 遵循了 P複製程式碼

這是一個很強大的功能:它允許一個型別將一個領域的抽象轉換到另一個領域(例如,每一個 Matrix 都是一個 Graph)。然而,跟隱祕遵循一樣,它會給執行時動態轉換帶來巨大的壓力,因為它需要通過一個可能很長的遵循鏈條進行查詢,幾乎不可能有高效的方式去實現它。

可能會去掉的...

泛型系統似乎不會跟這個主題有太多關聯,因為很多泛型功能都在標準庫裡大量使用,只有極少部分已經過時了,然而...

AssociatedType 型別推導

去掉 associatedType 型別推導的提案 SE-0108 已經被駁回了

AssociatedType 型別推導是我們通過其它必要條件推斷出來 Associated Type 型別的過程。例如:

protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

struct IntIterator : IteratorProtocol {
  mutating func next() -> Int? { ... }  
  // 通過這個宣告推斷出 Element 為 Int
}複製程式碼

Associated Type 型別推導是一個很實用的功能,被應用在了標準庫的各個地方,並且這樣讓我們在遵循協議的時候更少直接接觸到 associatedType。但另一方面,associatedType 型別推導是 Swift 目前唯一一個需要進行全域性型別推斷的地方:它在過去已經成為 bug 產生的一個主要成了因,完整並且正確地實現它需要一個全新的型別推斷架構。在 Swift 這門語言裡使用全域性的型別推斷真的值得嗎?我們在什麼時候需要防止全域性型別推斷在別的地方產生?

存在形式 Existentials

存在形式並非是泛型,但這兩個系統由於對協議的重度依賴導致它們交錯在了一起。

泛型的存在形式 Generalized existentials

泛型存在形式的限制來自於一個實現瓶頸,但讓一個協議型別的例項能夠存在 Self 的約束或者是 associatedType 是合理的。例如,思考一下 IteratorProtocol 是以什麼樣的形式存在的:

protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

let it: IteratorProtocol = ...
it.next()   
// 如果這種行為被允許的話,那它就會返回 “Any?”
// 也就是說,這是一個包含了實際元素的容器複製程式碼

另外,把 associatedType 的約束也作為存在形式的一部分也是合理的。也就是說,“一個所有元素都是 StringSequence” 是可以通過在 protocol<...>Any<...> 中使用 where 從句表達出來的。(多說一句,protocol<...> 已經被重新命名為 Any<...> 了)

let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]複製程式碼

那一個 . 意味著我們在討論的是動態型別,例如,一個遵循了 Sequence 協議的 Self 型別。我們沒有任何理由不去支援在 Any<...> 裡使用 where 從句。這個語法有點笨,但常用的型別我們可以用一個泛型 typealias 來封裝(請看上面的 "Generic typealias" 小節):

typealias AnySequence<Element> = Any<Sequence where .Iterator.Element == Element>
let strings: AnySequence<String> = ["a", "b", "c"]複製程式碼

可開箱的存在形式 Opening existentials

上面說到的泛型存在形態會在把帶 Self 約束的協議或 associateType 作為函式引數時產生麻煩。例如,讓我們嘗試把 Equatable 作為一個泛型存在形態使用:

protocol Equatable {
  func ==(lhs: Self, rhs: Self) -> Bool
  func !=(lhs: Self, rhs: Self) -> Bool
}

let e1: Equatable = ...
let e2: Equatable = ...
if e1 == e2 { ... } 
// error: e1 和 e2 不一定擁有相同的動態型別複製程式碼

根據型別安全的原則,為了讓這種操作變得合法,其中一種明顯的方式就是引入“開箱”操作,將存在內部的動態型別取出並且給予它一個名字。例如:

if let storedInE1 = e1 openas T {     
  // T 是 storeInE1 的型別,一個 e1 的備份
  if let storedInE2 = e2 as? T {      
    // e2 也是一個 T 嗎?
    if storedInE1 == storedInE2 { ... } 
      // okay: 現在 storedInT1 和 storedInE1 現在都是型別 T,也就是 Equatable 的型別
  }
}複製程式碼

覺得文章還不錯的話可以關注一下我的部落格

相關文章