【譯】Swift 泛型宣言

原文:Generics Manifesto -- Douglas Gregor

譯者注

在我慢慢地深入使用 Swift 之后,碰壁了很多次,很大一部分都是因?yàn)?Swift 的泛型系統(tǒng)導(dǎo)致的,很多抽象都沒辦法很好地表達(dá)出來,所以就翻譯了這篇文章來學(xué)習(xí)一下 Swift 的泛型。

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

簡介

“完善的泛型系統(tǒng)” 這個 Swift 3 的目標(biāo)到目前為止都不是那么的明確:

完善的泛型系統(tǒng): 泛型功能已經(jīng)在大量的 Swift 庫中使用,特別是標(biāo)準(zhǔn)庫。然而,標(biāo)準(zhǔn)庫所需的的一大堆泛型功能,都需要泛型系統(tǒng)完整的實(shí)現(xiàn),包括了 Recursive Protocol Constraints 協(xié)議遞歸約束,Condition Comformance 讓受約束的拓展遵循一個新協(xié)議的能力(例如,一個元素 Equatable 的數(shù)組也應(yīng)該是 Equatable 的),諸如此類。Swift 3.0 應(yīng)該提供這些標(biāo)準(zhǔn)庫需要的泛型功能,因?yàn)樗鼈儠绊懙綐?biāo)準(zhǔn)庫的 ABI。

這條信息將“完善的泛型系統(tǒng)”展開來具體描述。這不是任何一個核心團(tuán)隊的 Swift 3.0 開發(fā)計劃,但這包含了大量核心團(tuán)隊和 Swift 開發(fā)者的討論,包括編譯器和標(biāo)準(zhǔn)庫。我希望可以實(shí)現(xiàn)這幾個事情:

  • 討論出一個 Swift 泛型的具體愿景,討論應(yīng)該在最初的泛型設(shè)計文檔的基礎(chǔ)上進(jìn)行,讓我們可以有一些更加具體的全面的東西可以討論。

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

  • 參與更多社區(qū)的討論,讓我們可以考慮社區(qū)里一些功能設(shè)計。甚至還可以直接實(shí)現(xiàn)其中一部分。

像這樣的信息可以在獨(dú)立的討論進(jìn)程里進(jìn)行。為了讓我們的討論盡可能獨(dú)立,我會要求討論進(jìn)程里只討論主題功能的愿景:如何讓各個設(shè)計更好得融合到一起,還缺乏哪些設(shè)計,這些設(shè)計是否符合 Swift 的長期愿景,諸如此類。關(guān)于特定語言功能的討論,例如,Conditional Conformance 的語法和語義,或者是編譯器的實(shí)現(xiàn),標(biāo)準(zhǔn)庫的使用,請重新開一個討論進(jìn)程,并且使用的官方對于該功能的稱謂。

這條信息涵蓋了很多細(xì)節(jié);我已經(jīng)嘗試過不同功能的粗略分類,并且保持簡要的描述去限制總體長度。這些大部分都不是我的主意,我提供的一些語法只是通過代碼表達(dá)我的想法,也是之后會改的東西。并非所有的功能都會得到實(shí)現(xiàn),或許在不久的將來,也或許永遠(yuǎn)不會,但它們都交織在一起形成了一個整體。比起那些之后會很有趣的功能,我會在我覺得近期重要的討論后面加上 *??傮w而言, * 號意味著這個功能會對于 Swift 標(biāo)準(zhǔn)庫的設(shè)計和實(shí)現(xiàn)有著顯著的影響。

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

去除不必要的限制

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

遞歸協(xié)議遵循 Recursive protocol constraints(*)

這個功能已經(jīng)在 SE-0157 里通過了,并且會在 SR-1445 里進(jìn)行跟進(jìn)。

目前,一個 associatedType 不能遵循與之關(guān)聯(lián)的協(xié)議(或者協(xié)議的父協(xié)議)。例如,在標(biāo)準(zhǔn)庫里一個 SequanceSubSequence 必須是它自身 —— 一個 Sequence

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  ...
  associatedtype SubSequence : Sequence   
  // 目前這樣的寫法是不合法的,但它應(yīng)該合法
}

它讓"子序列必須是一個序列"這個要求,遞歸地約束到每一個子序列的子序列的子序列的子序列...不幸的是,編譯器目前會不接受這個協(xié)議,并且沒有別的辦法表達(dá)出這一個抽象的準(zhǔn)確含義。

泛型嵌套 Nested Generics

這個功能已經(jīng)在 SR-1446 跟進(jìn)了,并且在 Swift 3.1 實(shí)現(xiàn)了。

目前,一個泛型類型沒辦法嵌套在另一個泛型類型里,例如這樣:

struct X<T> {
  struct Y<U> { }  
  // 目前這樣的寫法是不合法的,但它本應(yīng)是合法的
}

這點(diǎn)沒什么好說的:編譯器只需要簡單地改進(jìn)對于泛型嵌套的處理就可以了。

Concrete same-type requirements

這個功能已經(jīng)在 SR-1009 跟進(jìn)并且在 Swift 3.1 實(shí)現(xiàn)了。

目前,一個受約束的拓展不能使用具體的類型來對泛型參數(shù)進(jìn)行約束。例如:

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

這是一個呼聲很高的功能,可以很好地融入現(xiàn)在的語法和語義。這樣做還能引入一些新的語法,例如,拓展 Array<String>,這基本上就是另一個新功能的范疇了:請查看“參數(shù)化拓展 Parameterized extensions”。

參數(shù)化其它聲明

有很多 Swift 的聲明都不能使用泛型參數(shù); 其中有一些可以很自然地拓展泛型格式,并且不會破壞現(xiàn)有的語法,但如果能夠直接使用泛型的話會變得更加強(qiáng)大。

泛型類型別名 Generic typealiases

這個功能已經(jīng)在 SE-0048 里通過并且在 Swift 3.1 里實(shí)現(xiàn)了。

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

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

var d1 = StringDictionary<Int>()
var d2: Dictionary<String, Int> = d1 
// okay: d1 和 d2 都是相同的類型, Dictionary<String, Int>

泛型下標(biāo) Generic subscripts

這個功能已經(jīng)在 SE-0148, was tracked by SR-115 里通過,在 SR-115 跟進(jìn),并且在 Swift 4.0 里實(shí)現(xiàn)了。

下標(biāo)被允許使用泛型參數(shù)。例如,我們可以給 Collection 帶上一個泛型下標(biāo),允許我們通過任意滿足要求的索引去獲取到相應(yīng)的值:

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
      }
    }
  }
}

泛型常數(shù) Generic constants

let 常數(shù)被允許帶上泛型參數(shù),可以根據(jù)不同的使用方式來產(chǎn)生不同的值。例如,特別是在使用字面量時會很實(shí)用:

let π<T : ExpressibleByFloatLiteral>: T = 
    3.141592653589793238462643383279502884197169399

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

參數(shù)化拓展 Parameterized extensions

讓拓展自身可以被參數(shù)化,可以模式匹配到一些結(jié)構(gòu)化的類型上,例如,可以拓展一個元素為 Optional 的數(shù)組:

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
  }
}

我們還可以把它使用到協(xié)議拓展上:

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
  }
}

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

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

輔助性拓展

我們可以對泛型系統(tǒng)進(jìn)行一些輔助性拓展,雖然不會對于 Swift 表達(dá)能力產(chǎn)生根本性的改變,但可以讓它表達(dá)得更加準(zhǔn)確。

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

這個功能已經(jīng)在 SE-0142 里通過并且在 Swift 4 里實(shí)現(xiàn)了。

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

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  ...
  associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}

where 扔在 associatedType 后面并不是那么理想,但這應(yīng)該是另一個討論進(jìn)程該探討的問題。

協(xié)議的別名和協(xié)議拓展 Typealiases in protocols and protocol extensions(*)

這個功能已經(jīng)在 SE-0092 里通過并且在 Swift 3 里實(shí)現(xiàn)了。

現(xiàn)在 associatedType 已經(jīng)有了單獨(dú)的關(guān)鍵字了(謝天謝地!),在這里再一次使用 typealias 就變得很合理了。再次借用 Sequence 協(xié)議的例子:

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  typealias Element = Iterator.Element   
  // 歡呼吧! 現(xiàn)在我們可以通過 SomeSequence.Element 來引用了
  // 而不是冗長的 SomeSequence.Iterator.Element
}

默認(rèn)泛型參數(shù) Default generic arguments

泛型參數(shù)可以有提供默認(rèn)值的能力,在類型參數(shù)未被指定,并且類型推導(dǎo)無法決定具體類型參數(shù)時很實(shí)用。例如:

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 類型推導(dǎo)的結(jié)果是 Promise<Int, Error>

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

這個功能是SE-0092 提案實(shí)現(xiàn)后的形態(tài),并且在 Swift 4 里實(shí)現(xiàn)了。

class 約束目前只可以在定義協(xié)議時使用。我們還可以拿它來約束 associatedtype 和類型參數(shù)聲明:

protocol P {
  associatedtype A : class
}

func foo<T : class>(t: T) { }

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

typealias AnyObject = protocol<class>

更多細(xì)節(jié),請查看 "Existentials" 小節(jié),特別是 “Generalized existentials”。

允許子類重寫默認(rèn)的實(shí)現(xiàn) Allowing subclasses to override requirements satisfied by defaults(*)

當(dāng)一個父類遵循一個協(xié)議,并且協(xié)議里的一個要求被協(xié)議拓展實(shí)現(xiàn)了,那子類就沒辦法重寫這個要求了。例如:

protocol P {
  func foo()
}

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

class C : P {
  // 獲得協(xié)議拓展給予的能力
}

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

let p: P = D()
p.foo() 
// gotcha:這里打印了 "P",而不是 “D”!

D.foo 應(yīng)該顯式地標(biāo)記為 "override" 并且被動態(tài)調(diào)用。

泛型模型的主要拓展

不像那些輔助性拓展,泛型模型的主要拓展給 Swift 的泛型系統(tǒng)提供了更強(qiáng)大的表達(dá)能力,并且有更顯著的設(shè)計和實(shí)現(xiàn)成本。

有條件的遵循 Conditional conformances(*)

這個功能已經(jīng)在 SE-0092 里通過,并且正在開發(fā)中。(譯者注:截止到發(fā)稿時,這個功能已經(jīng)實(shí)現(xiàn)了,并且標(biāo)準(zhǔn)庫里已經(jīng)開始使用這個功能開始重構(gòu)了)

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

extension Array : Equatable where Element : Equatable { }

func ==<T : Equatable>(lhs: Array<T>, rhs: Array<T>) -> Bool { ... }

Conditional Conformance 是一個非常強(qiáng)勁的功能。這個功能其中一個重要的點(diǎn)就在于如何處理協(xié)議的疊加遵循。舉個例子,想象一個遵循了 Sequence 的類型,同時有條件得遵守了 CollectionMutableCollection

struct SequenceAdaptor<S: Sequence> : Sequence { }
extension SequenceAdaptor : Collection where S: Collection { ... }
extension SequenceAdaptor : MutableCollection where S: MutableCollection { }

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

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

關(guān)于同一個類型多次遵循統(tǒng)一個協(xié)議的問題,可以查看 "Private conformances" 小節(jié)。

譯者注:

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

“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 優(yōu)先級越高。第一段代碼最后一句改成 extension SequenceAdaptor : Collection where S: MutableCollection { } 可能會更好,由于 MutableCollection 繼承自 Collection,所以 where S: MutableCollectionwhere S: Collection 更加具體,系統(tǒng)會優(yōu)先使用這一個 conformance 里的實(shí)現(xiàn)。

而第二段代碼里那個 SomeOtherProtocolSimilarToCollection 協(xié)議可能不繼承于 Collection,所以 where S: SomeOtherProtocolSimilarToCollectionwhere S: Collection 約束是一樣多的,它們的優(yōu)先級相同,此時系統(tǒng)就不知道該選哪一個 conformance 里的實(shí)現(xiàn)。

可變泛型 Variadic generics

目前,一個泛型參數(shù)列表只能包含固定數(shù)量的泛型參數(shù)。如果要讓一個類型可以容納任意數(shù)量的泛型參數(shù),那就只能創(chuàng)建多個類型了(譯者注:我想起了 RxSwift 的 zip 函數(shù)??)。例如,標(biāo)準(zhǔn)庫里的 zip 函數(shù)。當(dāng)提供兩個參數(shù)時就會調(diào)用其中一個 zip 函數(shù):

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

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

支持三個參數(shù)只需要復(fù)制粘貼就可以了,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> { ... }

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

public struct ZipIterator<... Iterators : IteratorProtocol> : Iterator {  
  // 零或多個類型參數(shù),每一個都遵循 IteratorProtocol 協(xié)議
  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()...) {   
    // 調(diào)用每一個迭代器的 "next" 方法,將結(jié)果放入一個名為 “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 的類型

  // ...
}

這樣的設(shè)計對于函數(shù)參數(shù)也一樣適用,所以我們可以把多個不同類型的函數(shù)參數(shù)打包起來:

public func zip<... Sequences : SequenceType>(... sequences: Sequences...)
            -> ZipSequence<Sequences...> {
  return ZipSequence(sequences...)
}

最后,這也可以和把元組“拍平”的操作符的討論聯(lián)系起來。例如:

func apply<... Args, Result>(fn: (Args...) -> Result,    
// 函數(shù)接收一定數(shù)量的參數(shù)然后產(chǎn)生結(jié)果
                           args: (Args...)) -> Result {  
                           // 參數(shù)的元組
  return fn(args...)                                     
  // 把元組 "args" 里的參數(shù)展開為單獨(dú)的參數(shù)
}

結(jié)構(gòu)化類型的拓展 Extensions of structural types

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

extension<...Elements : Equatable> (Elements...) : Equatable {   
  // 將元組 "(Elements)" 類型拓展為 Equatable
}

這里有幾個自然的邊界:拓展的類型必須是一個實(shí)際意義上的結(jié)構(gòu)化類型。并非所有類型都可以被拓展:

extension<T> T { 
  // error:這既不是一個結(jié)構(gòu)化類型也不是一個實(shí)際類型
}

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

extension<T : P> T : Q { 
  // error:這既不是一個結(jié)構(gòu)化類型也不是一個實(shí)際的類型
}

改善語法

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

協(xié)議的默認(rèn)實(shí)現(xiàn) Default implementations in protocols(*)

目前,協(xié)議里的成員絕對不可以有實(shí)現(xiàn)。如果遵循的類型沒有提供實(shí)現(xiàn)的話,就可以使用協(xié)議拓展的默認(rèn)實(shí)現(xiàn):

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 實(shí)現(xiàn)的要求已經(jīng)被 Bag 的默認(rèn)實(shí)現(xiàn)滿足了
}

現(xiàn)在可以直接通過協(xié)議拓展來達(dá)到這一點(diǎn),因此這類的功能應(yīng)該被歸為語法的加強(qiáng):

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 里實(shí)現(xiàn)了。

泛型函數(shù)的 where 從句很早就存在了,盡管調(diào)用方更關(guān)心的是函數(shù)參數(shù)和返回類型。把 where 移出尖括號這更加有助于我們忽略尖括號的內(nèi)容。想一想上面 containsAll 函數(shù)的簽名:

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

where 從句移到函數(shù)簽名的最后,那函數(shù)最重要的那些部分 —— 函數(shù)名,泛型參數(shù),參數(shù),返回類型 —— 就會優(yōu)先于 where 從句了:

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

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

SE-0095 里作為 “把 'protocol<P1,P2>' 替換為 'P1 & P2'” 通過,并且在 Swift 3 里實(shí)現(xiàn)了。

protocol<...> 語法在 Swift 里有一點(diǎn)怪異。它通常是用來創(chuàng)建一個類型的容器,把協(xié)議組合到一起:

var x: protocol<NSCoding, NSCopying>

它的怪異在于這是一個小寫字母開頭的類型名,而大多數(shù)的 Swift 開發(fā)者都不會跟這個功能打交道,除非他們?nèi)ゲ榭?Any 的定義:

typealias Any = protocol<>

“Any” 是這個功能更好的稱謂。沒有尖括號的 Any 指的是“任意類型”,而有尖括號 “Any” 現(xiàn)在可以充當(dāng) protocol<>

var x: Any<NSCoding, NSCopying>

這讀起來會更好:“任何遵循 NSCodingNSCopying 的類型“。更多細(xì)節(jié)請查看 "Generalized existentials" 小節(jié)。

也許會有...

有一些功能直到它們可以融入 Swift 的泛型系統(tǒng)之前,都需要反反復(fù)復(fù)地進(jìn)行討論,目前它們是否適合 Swift 還不那么明確。重要的問題是在這個類別里的任何功能都不是“可以做”或者“我們可以很酷地表達(dá)出來的事情”,而是“Swift 開發(fā)者每天怎樣會從這個功能里獲益?”。在沒有強(qiáng)大的應(yīng)用場景之前,這些功能“很可能”都不會更進(jìn)一步。

協(xié)議拓展成員的動態(tài)派發(fā) Dynamic dispatch for members of protocol extensions

目前只有協(xié)議里聲明的成員會使用動態(tài)派發(fā),并且會在調(diào)用時產(chǎn)生意外:

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 應(yīng)該選用一個模型去讓協(xié)議拓展里的成員使用動態(tài)派發(fā)。

泛型參數(shù)名稱 Named generic parameters

當(dāng)指定泛型類型的泛型參數(shù)時,參數(shù)總是依賴于它的位置:Dictionary<String, Int> 是一個 Key 類型為 StringValue 類型為 IntDictionary。但也可以給參數(shù)加上標(biāo)簽:

var d: Dictionary<Key: String, Value: Int>

這樣的功能會在 Swift 擁有 Default generic arguments 之后更加具有存在意義,因?yàn)榉盒蛥?shù)的標(biāo)簽可以讓我們跳過一個已經(jīng)有默認(rèn)值的參數(shù)。

將值作為泛型參數(shù) Generic value parameters

目前,Swift 的泛型參數(shù)只能是類型。我們可以聯(lián)想到使用值作為泛型參數(shù):

struct MultiArray<T, let Dimensions: Int> { 
  // 指定數(shù)組的維度
  subscript (indices: Int...) -> T {
    get {
      require(indices.count == Dimensions)
      // ...
    }
}

一個恰如其分的功能也許可以讓我們表達(dá)固定長度的數(shù)組或向量類型,作為標(biāo)準(zhǔn)庫的一部分,也許這可以讓我們更方便地實(shí)現(xiàn)一個維度分析庫。這個功能是否實(shí)現(xiàn)取決于,我們怎么去定義一個“常量表達(dá)式”,并且需要深入類型的定義,所以這是一個“也許會“實(shí)現(xiàn)的功能。

更高層次的類型 Higher-kinded types

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

let intArray: Array<Int> = ...
intArray.map { String($0) } // 產(chǎn)生 Array<String>
let intSet: Set<Int> = ...
intSet.map { String($0) }   // 產(chǎn)生 Set<String>

候選語法是從 higher-kinded types 的一個討論進(jìn)程超過來的,那里面使用了 ~= 作為“相似”約束來描述一個 Functor 協(xié)議:

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

泛型參數(shù)指定類型參數(shù)后的使用 Specifying type arguments for uses of generic functions

不在 Swift 4 的計劃內(nèi)

泛型函數(shù)的類型參數(shù)總是通過類型推導(dǎo)來決定。例如:

func f<T>(t: T)

不能直接指定 T 的情況下:要么直接調(diào)用 fT 會根據(jù)參數(shù)類型決定),要么就在給定函數(shù)類型的場景下使用 f(例如 let x: (Int) -> Void = f 會推導(dǎo)出 T = Int)。我們允許在這里指定類型:

let x = f<Int> // x 的類型為 (Int) -> Void

不太可能會有...

這個分類里的功能已經(jīng)被提過很多次了,但它們都沒辦法很好地融入 Swift 的泛型系統(tǒng),因?yàn)樗鼈儠斐蛇@個模型的一部分變得過于復(fù)雜,有無法接受的實(shí)現(xiàn)限制,或者與現(xiàn)有的功能有重疊的部分。

泛型協(xié)議 Generic protocols

一個最經(jīng)常被提起的功能就是參數(shù)化協(xié)議本身。例如,一個表明 Self 類型可以使用某個特定類型的 T 來構(gòu)造的協(xié)議:

protocol ConstructibleFromValue<T> {
  init(_ value: T)
}

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

struct Real { ... }
extension Real : ConstructibleFrom<Float> {
  init(_ value: Float) { ... }
}
extension Real : ConstructibleFrom<Double> {
  init(_ value: Double) { ... }
}

大部分對于這個功能的需求本質(zhì)上需要的是另外的功能。例如他們可能只是想要一個參數(shù)化的 Sequence

protocol Sequence<Element> { ... }

func foo(strings: Sequence<String>) {  
  // 操作字符串集合
  // ...
}

這里實(shí)際的功能需求是 “任何遵循了 Sequance 協(xié)議并且 ElementString 的類型”,下面 “Generalized existentials” 這一小節(jié)會講到。

更重要的是,使用泛型參數(shù)去構(gòu)建 Sequence 的模型雖然很誘人,但這是錯誤的:你不會想要一個類型有多種遵循 Sequence 的途徑,抑或是讓你的 for..in 循環(huán)出問題,并且你也不會想失去 Element 類型不固定的 Sequence 的動態(tài)類型轉(zhuǎn)換能力(還是那句話,去看 "Generalized existentials" 吧)。類似于上面 ConstructableFromValue 協(xié)議的用例都太低估了協(xié)議泛型參數(shù)帶來的麻煩了。我們最好還是放棄協(xié)議泛型參數(shù)吧。

隱秘遵循 Private conformances

現(xiàn)在,協(xié)議的遵循的可見性不能低于類型和協(xié)議的最低訪問權(quán)限。因此,一個 public 的類型遵循了一個 public 的協(xié)議的話,這個遵循也必須是 public 的??梢韵胂笠幌氯サ暨@個限制,我們就可以引入隱秘遵循:

public protocol P { }
public struct X { }
extension X : internal P { ... } 
// X 遵循了 P, 但只在 module 內(nèi)部可見

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

隱秘遵循最主要的問題就在于動態(tài)類型轉(zhuǎn)換,如果我把代碼寫成這樣:

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

foo(X())

在這種情況下,應(yīng)該打印 "P"?如果 foo() 是在同一個 module 內(nèi)的時候會怎么樣?如果這個調(diào)用是在 module 內(nèi)部產(chǎn)生的時候呢?前兩個問題的回答都需要給動態(tài)類型轉(zhuǎn)換引入顯著的復(fù)雜度,并且會把問題帶到動態(tài)轉(zhuǎn)換產(chǎn)生的 module 里(第一個選擇)或數(shù)據(jù)的結(jié)構(gòu)(第二個選擇),而第三個答案會破壞掉靜態(tài)類型和動態(tài)類型的系統(tǒng)。這些都不是可接受的結(jié)果。

通過協(xié)議拓展有條件地遵循 Conditional conformances via protocol extensions

我們經(jīng)常收到讓協(xié)議遵循另一個協(xié)議的請求。這會把 "Conditional Conformance" 拓展到 protocol extension 上。例如:

protocol P {
  func foo()
}

protocol Q {
  func bar()
}

extension Q : P { 
  // 任何遵循 Q 的類型都會遵循 P
  func foo() {    
    // 因?yàn)?"bar" 的存在滿足了 "foo" 的實(shí)現(xiàn)要求
    bar()
  }
}

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

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

f(X()) 
// okay: X 通過 Q 遵循了 P

這是一個很強(qiáng)大的功能:它允許一個類型將一個領(lǐng)域的抽象轉(zhuǎn)換到另一個領(lǐng)域(例如,每一個 Matrix 都是一個 Graph)。然而,跟隱秘遵循一樣,它會給運(yùn)行時動態(tài)轉(zhuǎn)換帶來巨大的壓力,因?yàn)樗枰ㄟ^一個可能很長的遵循鏈條進(jìn)行查找,幾乎不可能有高效的方式去實(shí)現(xiàn)它。

可能會去掉的...

泛型系統(tǒng)似乎不會跟這個主題有太多關(guān)聯(lián),因?yàn)楹芏喾盒凸δ芏荚跇?biāo)準(zhǔn)庫里大量使用,只有極少部分已經(jīng)過時了,然而...

AssociatedType 類型推導(dǎo)

去掉 associatedType 類型推導(dǎo)的提案 SE-0108 已經(jīng)被駁回了

AssociatedType 類型推導(dǎo)是我們通過其它必要條件推斷出來 Associated Type 類型的過程。例如:

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

struct IntIterator : IteratorProtocol {
  mutating func next() -> Int? { ... }  
  // 通過這個聲明推斷出 Element 為 Int
}

Associated Type 類型推導(dǎo)是一個很實(shí)用的功能,被應(yīng)用在了標(biāo)準(zhǔn)庫的各個地方,并且這樣讓我們在遵循協(xié)議的時候更少直接接觸到 associatedType。但另一方面,associatedType 類型推導(dǎo)是 Swift 目前唯一一個需要進(jìn)行全局類型推斷的地方:它在過去已經(jīng)成為 bug 產(chǎn)生的一個主要成了因,完整并且正確地實(shí)現(xiàn)它需要一個全新的類型推斷架構(gòu)。在 Swift 這門語言里使用全局的類型推斷真的值得嗎?我們在什么時候需要防止全局類型推斷在別的地方產(chǎn)生?

存在形式 Existentials

存在形式并非是泛型,但這兩個系統(tǒng)由于對協(xié)議的重度依賴導(dǎo)致它們交錯在了一起。

泛型的存在形式 Generalized existentials

泛型存在形式的限制來自于一個實(shí)現(xiàn)瓶頸,但讓一個協(xié)議類型的實(shí)例能夠存在 Self 的約束或者是 associatedType 是合理的。例如,思考一下 IteratorProtocol 是以什么樣的形式存在的:

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

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

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

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

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

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

可開箱的存在形式 Opening existentials

上面說到的泛型存在形態(tài)會在把帶 Self 約束的協(xié)議或 associateType 作為函數(shù)參數(shù)時產(chǎn)生麻煩。例如,讓我們嘗試把 Equatable 作為一個泛型存在形態(tài)使用:

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 不一定擁有相同的動態(tài)類型

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

if let storedInE1 = e1 openas T {     
  // T 是 storeInE1 的類型,一個 e1 的備份
  if let storedInE2 = e2 as? T {      
    // e2 也是一個 T 嗎?
    if storedInE1 == storedInE2 { ... } 
      // okay: 現(xiàn)在 storedInT1 和 storedInE1 現(xiàn)在都是類型 T,也就是 Equatable 的類型
  }
}

覺得文章還不錯的話可以關(guān)注一下我的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容