原文: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 =
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”的基础上建立的例子,我们真的很希望 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
来取代,并且成为一个类型别名:
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
的类型,同时有条件得遵守了 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
函数的签名:
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>' 替换为 '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>
这读起来会更好:“任何遵循 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
。但也可以给参数加上标签:
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>
和其他类型 U
的 Self<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
的情况下:要么直接调用 f
(T
会根据参数类型决定),要么就在给定函数类型的场景下使用 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
类型也许可以同时使用 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<...>
了)
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 的类型
}
}
觉得文章还不错的话可以关注一下我的博客