原文:Generics Manifesto -- Douglas Gregor
譯者注
在我慢慢地深入使用 Swift 之后,碰壁了很多次阶捆,很大一部分都是因為 Swift 的泛型系統(tǒng)導(dǎo)致的欠雌,很多抽象都沒辦法很好地表達出來策橘,所以就翻譯了這篇文章來學(xué)習(xí)一下 Swift 的泛型锚扎。
文章里特別提到了要用官方提到的用語來討論坎拐,所以那些 feature 的名稱我都會保留英文担忧。
簡介
“完善的泛型系統(tǒng)” 這個 Swift 3 的目標到目前為止都不是那么的明確:
完善的泛型系統(tǒng): 泛型功能已經(jīng)在大量的 Swift 庫中使用芹缔,特別是標準庫。然而瓶盛,標準庫所需的的一大堆泛型功能最欠,都需要泛型系統(tǒng)完整的實現(xiàn),包括了 Recursive Protocol Constraints 協(xié)議遞歸約束惩猫,Condition Comformance 讓受約束的拓展遵循一個新協(xié)議的能力(例如芝硬,一個元素
Equatable
的數(shù)組也應(yīng)該是Equatable
的),諸如此類轧房。Swift 3.0 應(yīng)該提供這些標準庫需要的泛型功能拌阴,因為它們會影響到標準庫的 ABI。
這條信息將“完善的泛型系統(tǒng)”展開來具體描述奶镶。這不是任何一個核心團隊的 Swift 3.0 開發(fā)計劃迟赃,但這包含了大量核心團隊和 Swift 開發(fā)者的討論,包括編譯器和標準庫厂镇。我希望可以實現(xiàn)這幾個事情:
討論出一個 Swift 泛型的具體愿景纤壁,討論應(yīng)該在最初的泛型設(shè)計文檔的基礎(chǔ)上進行,讓我們可以有一些更加具體的全面的東西可以討論捺信。
建立一些專門用語來概括 Swift 開發(fā)者使用的功能酌媒,讓我們的討論可以更加高效(“噢,你建議的這個東西我們稱為 'conditional conformances';你可以看一下這個討論進程“)秒咨。
參與更多社區(qū)的討論喇辽,讓我們可以考慮社區(qū)里一些功能設(shè)計。甚至還可以直接實現(xiàn)其中一部分雨席。
像這樣的信息可以在獨立的討論進程里進行茵臭。為了讓我們的討論盡可能獨立,我會要求討論進程里只討論主題功能的愿景:如何讓各個設(shè)計更好得融合到一起,還缺乏哪些設(shè)計驰凛,這些設(shè)計是否符合 Swift 的長期愿景吐句,諸如此類。關(guān)于特定語言功能的討論盛卡,例如,Conditional Conformance 的語法和語義,或者是編譯器的實現(xiàn)罢低,標準庫的使用,請重新開一個討論進程胖笛,并且使用的官方對于該功能的稱謂网持。
這條信息涵蓋了很多細節(jié);我已經(jīng)嘗試過不同功能的粗略分類长踊,并且保持簡要的描述去限制總體長度功舀。這些大部分都不是我的主意,我提供的一些語法只是通過代碼表達我的想法身弊,也是之后會改的東西辟汰。并非所有的功能都會得到實現(xiàn),或許在不久的將來阱佛,也或許永遠不會帖汞,但它們都交織在一起形成了一個整體。比起那些之后會很有趣的功能凑术,我會在我覺得近期重要的討論后面加上 *翩蘸。總體而言淮逊, * 號意味著這個功能會對于 Swift 標準庫的設(shè)計和實現(xiàn)有著顯著的影響催首。
官話說夠了,讓我們來討論一下功能吧壮莹。
去除不必要的限制
由于 Swift 編譯器的實現(xiàn)翅帜,在使用泛型的時候有很多限制。去掉這些限制也只是實現(xiàn)問題命满,不需要引入新的語法或語義涝滴。我把這些列出來的主要原因有兩個:第一,這是一個對于現(xiàn)有模型功能的回顧,第二歼疮,我們需要這些功能實現(xiàn)上的幫助杂抽。
遞歸協(xié)議遵循 Recursive protocol constraints(*)
這個功能已經(jīng)在 SE-0157 里通過了,并且會在 SR-1445 里進行跟進韩脏。
目前缩麸,一個 associatedType 不能遵循與之關(guān)聯(lián)的協(xié)議(或者協(xié)議的父協(xié)議)。例如赡矢,在標準庫里一個 Sequance
的 SubSequence
必須是它自身 —— 一個 Sequence
:
protocol Sequence {
associatedtype Iterator : IteratorProtocol
...
associatedtype SubSequence : Sequence
// 目前這樣的寫法是不合法的杭朱,但它應(yīng)該合法
}
它讓"子序列必須是一個序列"這個要求,遞歸地約束到每一個子序列的子序列的子序列的子序列...不幸的是吹散,編譯器目前會不接受這個協(xié)議弧械,并且沒有別的辦法表達出這一個抽象的準確含義。
泛型嵌套 Nested Generics
這個功能已經(jīng)在 SR-1446 跟進了空民,并且在 Swift 3.1 實現(xiàn)了刃唐。
目前,一個泛型類型沒辦法嵌套在另一個泛型類型里界轩,例如這樣:
struct X<T> {
struct Y<U> { }
// 目前這樣的寫法是不合法的画饥,但它本應(yīng)是合法的
}
這點沒什么好說的:編譯器只需要簡單地改進對于泛型嵌套的處理就可以了。
Concrete same-type requirements
這個功能已經(jīng)在 SR-1009 跟進并且在 Swift 3.1 實現(xiàn)了浊猾。
目前抖甘,一個受約束的拓展不能使用具體的類型來對泛型參數(shù)進行約束。例如:
extension Array where Element == String {
func makeSentence() -> String {
// 第一個單詞首字母大寫葫慎,用空格把單詞串聯(lián)起來单山,加個句號,之類的
}
}
這是一個呼聲很高的功能幅疼,可以很好地融入現(xiàn)在的語法和語義米奸。這樣做還能引入一些新的語法,例如爽篷,拓展 Array<String>
悴晰,這基本上就是另一個新功能的范疇了:請查看“參數(shù)化拓展 Parameterized extensions”。
參數(shù)化其它聲明
有很多 Swift 的聲明都不能使用泛型參數(shù)逐工; 其中有一些可以很自然地拓展泛型格式铡溪,并且不會破壞現(xiàn)有的語法,但如果能夠直接使用泛型的話會變得更加強大泪喊。
泛型類型別名 Generic typealiases
這個功能已經(jīng)在 SE-0048 里通過并且在 Swift 3.1 里實現(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>
泛型下標 Generic subscripts
這個功能已經(jīng)在 SE-0148, was tracked by SR-115 里通過纬纪,在 SR-115 跟進,并且在 Swift 4.0 里實現(xiàn)了滑肉。
下標被允許使用泛型參數(shù)包各。例如,我們可以給 Collection
帶上一個泛型下標靶庙,允許我們通過任意滿足要求的索引去獲取到相應(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)生不同的值。例如六荒,特別是在使用字面量時會很實用:
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)化泛型類型特例化之后的表達(也就是上面所說的 Concrete same-type requirements):
extension Array<String> {
func makeSentence() -> String {
// 第一個單詞首字母大寫,用空格把單詞串聯(lián)起來豺旬,加個句號钠惩,之類的
}
}
輔助性拓展
我們可以對泛型系統(tǒng)進行一些輔助性拓展,雖然不會對于 Swift 表達能力產(chǎn)生根本性的改變族阅,但可以讓它表達得更加準確篓跛。
協(xié)議的抽象約束 Arbitrary requirements in protocols(*)
這個功能已經(jīng)在 SE-0142 里通過并且在 Swift 4 里實現(xiàn)了。
目前坦刀,一個新的協(xié)議可以繼承自其它協(xié)議愧沟,引入新的 associatedType,并且給 associatedType 加上一些約束(通過重新聲明一個新的父協(xié)議 associatedType)鲤遥。然而沐寺,這并不能表達更多通用的約束。在“Recursive protocol constraints”的基礎(chǔ)上建立的例子盖奈,我們真的很希望 Sequence
的 SubSequence
的 Element
類型與 Sequence
的一樣:
protocol Sequence {
associatedtype Iterator : IteratorProtocol
...
associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}
把 where
扔在 associatedType 后面并不是那么理想混坞,但這應(yīng)該是另一個討論進程該探討的問題。
協(xié)議的別名和協(xié)議拓展 Typealiases in protocols and protocol extensions(*)
這個功能已經(jīng)在 SE-0092 里通過并且在 Swift 3 里實現(xiàn)了钢坦。
現(xiàn)在 associatedType 已經(jīng)有了單獨的關(guān)鍵字了(謝天謝地>吭小),在這里再一次使用 typealias
就變得很合理了爹凹。再次借用 Sequence
協(xié)議的例子:
protocol Sequence {
associatedtype Iterator : IteratorProtocol
typealias Element = Iterator.Element
// 歡呼吧厨诸! 現(xiàn)在我們可以通過 SomeSequence.Element 來引用了
// 而不是冗長的 SomeSequence.Iterator.Element
}
默認泛型參數(shù) Default generic arguments
泛型參數(shù)可以有提供默認值的能力,在類型參數(shù)未被指定禾酱,并且類型推導(dǎo)無法決定具體類型參數(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 提案實現(xiàn)后的形態(tài)绘趋,并且在 Swift 4 里實現(xiàn)了。
class
約束目前只可以在定義協(xié)議時使用得封。我們還可以拿它來約束 associatedtype 和類型參數(shù)聲明:
protocol P {
associatedtype A : class
}
func foo<T : class>(t: T) { }
作為這的一部分埋心,奇妙的 AnyObject
協(xié)議可以使用 class
來取代,并且成為一個類型別名:
typealias AnyObject = protocol<class>
更多細節(jié)忙上,請查看 "Existentials" 小節(jié)拷呆,特別是 “Generalized existentials”。
允許子類重寫默認的實現(xiàn) Allowing subclasses to override requirements satisfied by defaults(*)
當(dāng)一個父類遵循一個協(xié)議疫粥,并且協(xié)議里的一個要求被協(xié)議拓展實現(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)該顯式地標記為 "override" 并且被動態(tài)調(diào)用慷彤。
泛型模型的主要拓展
不像那些輔助性拓展娄蔼,泛型模型的主要拓展給 Swift 的泛型系統(tǒng)提供了更強大的表達能力,并且有更顯著的設(shè)計和實現(xiàn)成本底哗。
有條件的遵循 Conditional conformances(*)
這個功能已經(jīng)在 SE-0092 里通過岁诉,并且正在開發(fā)中。(譯者注:截止到發(fā)稿時跋选,這個功能已經(jīng)實現(xiàn)了涕癣,并且標準庫里已經(jīng)開始使用這個功能開始重構(gòu)了)
Conditional Conformance 表達了這樣的一個語義:泛型類型在特定條件下會遵循一個特定的協(xié)議。例如前标,Array
只會在它的元素為 Equatable
的時候遵循 Equatable
:
extension Array : Equatable where Element : Equatable { }
func ==<T : Equatable>(lhs: Array<T>, rhs: Array<T>) -> Bool { ... }
Conditional Conformance 是一個非常強勁的功能坠韩。這個功能其中一個重要的點就在于如何處理協(xié)議的疊加遵循。舉個例子炼列,想象一個遵循了 Sequence
的類型只搁,同時有條件得遵守了 Collection
和 MutableCollection
:
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: MutableCollection
比where S: Collection
更加具體缭付,系統(tǒng)會優(yōu)先使用這一個 conformance 里的實現(xiàn)柿估。而第二段代碼里那個
SomeOtherProtocolSimilarToCollection
協(xié)議可能不繼承于Collection
,所以where S: SomeOtherProtocolSimilarToCollection
跟where S: Collection
約束是一樣多的陷猫,它們的優(yōu)先級相同秫舌,此時系統(tǒng)就不知道該選哪一個 conformance 里的實現(xiàn)的妖。
可變泛型 Variadic generics
目前,一個泛型參數(shù)列表只能包含固定數(shù)量的泛型參數(shù)足陨。如果要讓一個類型可以容納任意數(shù)量的泛型參數(shù)嫂粟,那就只能創(chuàng)建多個類型了(譯者注:我想起了 RxSwift 的 zip 函數(shù)??)。例如墨缘,標準庫里的 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ù)卸亮;把省略號放在類型/表達式的右邊,可以把帶類型和表達式的參數(shù)集合展開成單獨的參數(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ù)展開為單獨的參數(shù)
}
結(jié)構(gòu)化類型的拓展 Extensions of structural types
目前京闰,只有真正意義上的類型(類颜及,結(jié)構(gòu)體,枚舉蹂楣,協(xié)議)可以被拓展俏站。我們可以預(yù)想到拓展結(jié)構(gòu)化類型,特別是類型明確的元組類型痊土,例如遵循協(xié)議肄扎。把 Variadic generics,Parameterized extension 和 Conditional conformances 結(jié)合起來赁酝,就可以表達“如果元組的所有元素都 Equtable犯祠,那元組也遵循 Equatable”:
extension<...Elements : Equatable> (Elements...) : Equatable {
// 將元組 "(Elements)" 類型拓展為 Equatable
}
這里有幾個自然的邊界:拓展的類型必須是一個實際意義上的結(jié)構(gòu)化類型。并非所有類型都可以被拓展:
extension<T> T {
// error:這既不是一個結(jié)構(gòu)化類型也不是一個實際類型
}
在你覺得自己聰明到可以使用 Conditional conformance 讓每一個遵循協(xié)議 P
的類型 T
同時遵循 Q
之前酌呆,請查看下面 "Conditional Conformance via protocol extensions" 小節(jié):
extension<T : P> T : Q {
// error:這既不是一個結(jié)構(gòu)化類型也不是一個實際的類型
}
改善語法
泛型語法還有很多可以改善的地方衡载。每一個都列舉起來會很長,所以我只說幾個 Swift 開發(fā)者已經(jīng)充分討論過的隙袁。
協(xié)議的默認實現(xiàn) Default implementations in protocols(*)
目前痰娱,協(xié)議里的成員絕對不可以有實現(xiàn)弃榨。如果遵循的類型沒有提供實現(xiàn)的話,就可以使用協(xié)議拓展的默認實現(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 實現(xiàn)的要求已經(jīng)被 Bag 的默認實現(xiàn)滿足了
}
現(xiàn)在可以直接通過協(xié)議拓展來達到這一點梨睁,因此這類的功能應(yī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 里實現(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 里實現(xiàn)了风瘦。
protocol<...>
語法在 Swift 里有一點怪異。它通常是用來創(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>
這讀起來會更好:“任何遵循 NSCoding
和 NSCopying
的類型“。更多細節(jié)請查看 "Generalized existentials" 小節(jié)刽虹。
也許會有...
有一些功能直到它們可以融入 Swift 的泛型系統(tǒng)之前酗捌,都需要反反復(fù)復(fù)地進行討論,目前它們是否適合 Swift 還不那么明確涌哲。重要的問題是在這個類別里的任何功能都不是“可以做”或者“我們可以很酷地表達出來的事情”胖缤,而是“Swift 開發(fā)者每天怎樣會從這個功能里獲益?”阀圾。在沒有強大的應(yīng)用場景之前哪廓,這些功能“很可能”都不會更進一步。
協(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
類型為 String
肾筐,Value
類型為 Int
的 Dictionary
哆料。但也可以給參數(shù)加上標簽:
var d: Dictionary<Key: String, Value: Int>
這樣的功能會在 Swift 擁有 Default generic arguments 之后更加具有存在意義,因為泛型參數(shù)的標簽可以讓我們跳過一個已經(jīng)有默認值的參數(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)
// ...
}
}
一個恰如其分的功能也許可以讓我們表達固定長度的數(shù)組或向量類型抓歼,作為標準庫的一部分讥此,也許這可以讓我們更方便地實現(xiàn)一個維度分析庫拢锹。這個功能是否實現(xiàn)取決于,我們怎么去定義一個“常量表達式”萄喳,并且需要深入類型的定義卒稳,所以這是一個“也許會“實現(xiàn)的功能。
更高層次的類型 Higher-kinded types
更高層次的類型允許我們表達相同抽象類型在同一個協(xié)議里兩種不同的具象他巨。例如充坑,如果我們把協(xié)議里的 Self
看作是 Self<T>
,這就讓我們可以討論 Self<T>
和其他類型 U
的 Self<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 的一個討論進程超過來的份企,那里面使用了 ~=
作為“相似”約束來描述一個 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)用 f
(T
會根據(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),因為它們會造成這個模型的一部分變得過于復(fù)雜骂远,有無法接受的實現(xiàn)限制囚霸,或者與現(xiàn)有的功能有重疊的部分。
泛型協(xié)議 Generic protocols
一個最經(jīng)常被提起的功能就是參數(shù)化協(xié)議本身激才。例如拓型,一個表明 Self
類型可以使用某個特定類型的 T
來構(gòu)造的協(xié)議:
protocol ConstructibleFromValue<T> {
init(_ value: T)
}
這個功能隱藏的含義是讓給定類型有兩種不同的方式來遵循協(xié)議。一個 Real
類型也許可以同時使用 Float
和 Double
來構(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>) {
// 操作字符串集合
// ...
}
這里實際的功能需求是 “任何遵循了 Sequance
協(xié)議并且 Element
為 String
的類型”劣挫,下面 “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() {
// 因為 "bar" 的存在滿足了 "foo" 的實現(xiàn)要求
bar()
}
}
func f<T: P>(t: T) { ... }
struct X : Q {
func bar() { ... }
}
f(X())
// okay: X 通過 Q 遵循了 P
這是一個很強大的功能:它允許一個類型將一個領(lǐng)域的抽象轉(zhuǎn)換到另一個領(lǐng)域(例如,每一個 Matrix
都是一個 Graph
)种玛。然而藐鹤,跟隱秘遵循一樣,它會給運行時動態(tài)轉(zhuǎn)換帶來巨大的壓力赂韵,因為它需要通過一個可能很長的遵循鏈條進行查找娱节,幾乎不可能有高效的方式去實現(xiàn)它。
可能會去掉的...
泛型系統(tǒng)似乎不會跟這個主題有太多關(guān)聯(liá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)是一個很實用的功能稠歉,被應(yīng)用在了標準庫的各個地方,并且這樣讓我們在遵循協(xié)議的時候更少直接接觸到 associatedType汇陆。但另一方面怒炸,associatedType 類型推導(dǎo)是 Swift 目前唯一一個需要進行全局類型推斷的地方:它在過去已經(jīng)成為 bug 產(chǎn)生的一個主要成了因,完整并且正確地實現(xiàn)它需要一個全新的類型推斷架構(gòu)毡代。在 Swift 這門語言里使用全局的類型推斷真的值得嗎阅羹?我們在什么時候需要防止全局類型推斷在別的地方產(chǎn)生?
存在形式 Existentials
存在形式并非是泛型教寂,但這兩個系統(tǒng)由于對協(xié)議的重度依賴導(dǎo)致它們交錯在了一起捏鱼。
泛型的存在形式 Generalized existentials
泛型存在形式的限制來自于一個實現(xiàn)瓶頸,但讓一個協(xié)議類型的實例能夠存在 Self 的約束或者是 associatedType 是合理的酪耕。例如导梆,思考一下 IteratorProtocol
是以什么樣的形式存在的:
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
let it: IteratorProtocol = ...
it.next()
// 如果這種行為被允許的話,那它就會返回 “Any?”
// 也就是說,這是一個包含了實際元素的容器
另外看尼,把 associatedType 的約束也作為存在形式的一部分也是合理的递鹉。也就是說,“一個所有元素都是 String
的 Sequence
” 是可以通過在 protocol<...>
或 Any<...>
中使用 where 從句表達出來的藏斩。(多說一句梳虽,protocol<...>
已經(jīng)被重命名為 Any<...>
了)
let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]
那一個 .
意味著我們在討論的是動態(tài)類型,例如灾茁,一個遵循了 Sequence
協(xié)議的 Self
類型窜觉。我們沒有任何理由不去支持在 Any<...>
里使用 where
從句。這個語法有點笨北专,但常用的類型我們可以用一個泛型 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)注一下我的博客