天天看點

【譯】Swift 泛型宣言

原文: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 不能遵循與之關聯的協定(或者協定的父協定)。例如,在标準庫裡一個

Sequance

SubSequence

必須是它自身 —— 一個

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 = 
    複制代碼
           

并且 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”的基礎上建立的例子,我們真的很希望

Sequence

SubSequence

Element

類型與

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

來取代,并且成為一個類型别名:

更多細節,請檢視 "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

的類型,同時有條件得遵守了

Collection

MutableCollection

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: MutableCollection

where S: Collection

更加具體,系統會優先使用這一個 conformance 裡的實作。

而第二段代碼裡那個

SomeOtherProtocolSimilarToCollection

協定可能不繼承于

Collection

,是以

where S: SomeOtherProtocolSimilarToCollection

where 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

函數的簽名:

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 裡有一點怪異。它通常是用來建立一個類型的容器,把協定組合到一起:

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

Any

的定義:

“Any” 是這個功能更好的稱謂。沒有尖括号的

Any

指的是“任意類型”,而有尖括号 “Any” 現在可以充當

protocol<>

這讀起來會更好:“任何遵循

NSCoding

NSCopying

的類型“。更多細節請檢視 "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

類型為

String

Value

類型為

Int

Dictionary

。但也可以給參數加上标簽:

這樣的功能會在 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>

和其他類型

U

Self<U>

之間的關系。例如,讓集合的

map

操作傳回相同的元素類型,但使用不同的操作:

let intArray: Array<Int> = ...
intArray.map { String($) } // 産生 Array<String>
let intSet: Set<Int> = ...
intSet.map { String($) }   // 産生 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 的計劃内

泛型函數的類型參數總是通過類型推導來決定。例如:

不能直接指定

T

的情況下:要麼直接調用

f

T

會根據參數類型決定),要麼就在給定函數類型的場景下使用

f

(例如

let x: (Int) -> Void = f

會推導出

T = Int

)。我們允許在這裡指定類型:

不太可能會有...

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

泛型協定 Generic protocols

一個最經常被提起的功能就是參數化協定本身。例如,一個表明

Self

類型可以使用某個特定類型的

T

來構造的協定:

protocol ConstructibleFromValue<T> {
  init( value: T)
}複制代碼
           

這個功能隐藏的含義是讓給定類型有兩種不同的方式來遵循協定。一個

Real

類型也許可以同時使用

Float

Double

來構造:

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

協定并且

Element

String

的類型”,下面 “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 的限制也作為存在形式的一部分也是合理的。也就是說,“一個所有元素都是

String

Sequence

” 是可以通過在

protocol<...>

Any<...>

中使用 where 從句表達出來的。(多說一句,

protocol<...>

已經被重命名為

Any<...>

了)

那一個

.

意味着我們在讨論的是動态類型,例如,一個遵循了

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 的類型
  }
}複制代碼
           
覺得文章還不錯的話可以關注一下我的部落格