介绍
本篇提议引入一种新的语法,用来遵守泛型参数并通过同一类型约束关联类型。
目的
先来看一个例子,该例中函数是用来返回源文件中的多个行数。
代码语言:Swift复制struct LineSequence : Sequence {
struct Iterator : IteratorProtocol {
mutating func next() -> String? { ... }
}
func makeIterator() -> Iterator {
return Iterator()
}
}
func readLines(_ file: String) -> LineSequence { ... }
如果你正在实现开发一个语法高亮库,并且定义一个函数readSyntaxHighlightedLines
读取高亮语法的行数, 这个函数使用SyntaxTokenSequence
来包装返回结果,结果中的元素类型是[Token]
, 代表每一行中语法高亮的 token 数组。例如:
func readSyntaxHighlightedLines(_ file: String)
-> SyntaxTokenSequence<LineSequence> {
...
}
注意:这里隐藏了一个等价关系:
Token
相当于序列的元素,是序列的关联类型,[Token]
与Sequence是等价的。这与我们平时使用关联类型用法也是一样的。
这个函数的返回结果是不是很复杂?借鉴前面提议实现的特性some
, 貌似这里我们使用它来隐藏背后的实际类型,例如:
func readSyntaxHighlightedLines(_ file: String)
-> some Sequence {
...
}
但是, 函数readSyntaxHighlightedLines()
已经无法充分表达原有的意思,因为后者无法表达:结果Sequence
的关联类型Element
与[Token]
中的元素是等价的。
再来看另外一个例子,这个例子中的函数作用是操作两个String
数组:
func concatenate(_ lhs: Array<String>, _ rhs: Array<String>) -> Array<String> {
...
}
函数concatenate()
中的参数类型和返回类型都是Array<String>
, 我们可以把它概括为抽象的序列实现,比如使用泛型来隐藏具体类型,并通过条件语句来限制泛型类型。比如这样用:
func concatenate<S: Sequence>(_ lhs: S, _ rhs: S) -> S where S.Element == String {
...
}
这样写有什么问题?在读写声明时都会带来开销,而且这与前面简单使用Array<String>
来实现函数声明完全不同。对于只有一个相同类型的情况,最好有一个更简单的方案来处理。下面看看本提议如何解决。
提议的解决方案
本提议会提出一种新的语法,用于声明协议一致性需求,以及协议主要关联类型上的一个或者多个相同类型的需求。这种新语法看起来像是将具体泛型类型应用到类型参数列表,并允许你编写Sequence<String>
或者Sequence<[Token]>
这种形式。这种形式类似于Array<String>
和Array<[Token]>
。
协议可以使用新语法来声明多个主要关联类型,这个新语法与具体类型的泛型参数列表表达相似,例如:
代码语言:Swift复制protocol Sequence<Element> {
associatetype Element
associatetyoe Iterator : IteratorProtocol
where Element == Iterator.Element
...
}
protocol dictionaryProtocol<Key, Value> {
associatedtype Key: Hashable
associatedtype Value
...
}
在尖括号的类型参数列表中,可以声明带有主要关联类型的协议。例如,不透明结果类型现在可以约束主要关联类型, 上述readSyntaxHighlightedLines()
函数可以这样表达:
func readSyntaxHighlightedLines(_ file: String) -> some Sequence<[Token]> {
...
}
上述concatenate()
函数现在可以这样写:
func concatenate<S: Sequence<String>>(_ lhs: S, _ rhs: S) -> S {
...
}
主要关联类型旨在用于调用方提供的关联类型。这些关联类型通常由遵循类型的泛型表达。例如,Element
是Sequence
的主要关联类型,因为Array<Element>
和Set<Element>
都遵循Sequence
协议,Element
则是由它们对应具体类型的泛型参数来表示。比如Set<Int>
的泛型参数类型此时是Int
, 则Element
此时是与Int
类型对应。
具体设计细节
在协议声明中,协议名称后面可以有一个主要关联类型列表,关联类型声明在尖括号'<>'里。
这个关联列表是可选的,你可以写也可以像之前声明协议样,后面不用跟任何声明。
如果关联列表存在,必须至少有一个主要关联类型存在。多个关联类型在'<>'中以逗号隔开。
关联类型列表中的每个关联类型必须要定义在对应的协议声明内,或者继承的协议声明内。
所以原来协议定义语法需要加上'主要关联类型列表',修改如下:
- 协议声明 -> attributes(可选) 访问修饰符(可选)
protocol
协议名 主要关联类型列表 协议继承语句 泛型条件语句 协议体 - 主要关联类型列表 ->
<
主要关联类型条目>
- 主要关联类型条目 -> 主要关联类型 | 主要关联类型
,
主要关联类型条目 - 主要关联类型 -> 类型名称 看两个例子:
// 关联类型`Element`在协议体内声明
protocol setProtocol<Element> {
associatedType Element: Hashable
}
protocol SortedMap {
associatedType Key
associatedType Value
}
// 主要关联类型`key`和`Value`在继承的协议`SortedMap`中声明
protocol PersistentSortedMap<Key, Value> : SortedMap {
...
}
在使用协议时,可以使用一个或者多个类型参数来约束协议,比如P<Arg1, Arg2...>
. 这些参数可以省略,这样该协议就不受约束。如果指定了类型参数,则类型参数的数量不能少于或者多于主关联类型的数量,否则会报错。
向协议添加主关联类型可以兼容源代码,该协议仍然可以在没有
<>
的情况下使用,就跟没有主关联类型的情况一样。
约束协议表达
约束协议语法可以出现在函数声明的多个地方,比如下面的各种协议约束语法都等同于 where
语句语法表达。下面举例说明:
- 第一种情况是 extension 的扩展类型,例子中的
<String>
等价于where Element == String
语法,例如:
extension Collection<String> { ... }
// 等价于
extension Collection where Element == String { ... }
- 第二种情况是继承协议,也就是遵循某一个协议,例如:
protocol TextBuffer: Collection<String> { ... }
// 等价于
protocol TextBuffer Collection where Element == String { ... }
- 第三种情况是继承泛型,也就是需要指定元素类型为某个类型,比如集合的指定元素为
String
类型:
func sortLines<S : Collection<String>>(_ lines: S) -> S
// 等价于:
func sortLines<S : Collection>(_ lines: S) -> S
where S.Element == String
- 第四种情况是继承关联类型,比如:
protocol Document {
associatedtype Lines: Collection<String>
}
// 等价于:
protocol Document {
associatedtype Lines: Collection where Lines.Element == String
}
- 第五种情况是在
where
子句的右侧,例如:
func merge<S : Sequence>(_ sequences: S) where S.Element : Sequence<String>
// 等价于:
func merge<S : Sequence>(_ sequences: S)
where S.Element : Sequence, S.Element.Element == String
- 第六种情况是不透明参数声明(
some
关键字声明),SE-0341 不透明参数声明在这篇提议中已经实现。举个例子说明:
func sortLines(_ lines: some Collection:<String>)
// 等价于:
func sortLines<C: Collection<String>>(_ lines: C)
// 也等价于:
func sortLines<C: Collection>(_ lines: C) where C.Element == String
- 第七种情况是协议参数包括嵌套的不透明参数. 例如:
func sort(elements: inout some Collection<some Equatable>) {}
// 等价于:
func sort<C: Collection, E: Equatable>(elements: inout C) where C.Element == E
上述7种情况发生时,这种T: P<Arg1, Arg2...>
语法糖表达都可以去糖化表达为 T: P
,并在后面追加参数约束,
T :P
T.PrimaryType1 == Arg1
T.PrimaryType2 == Arg2
...
如果Arg1
是some
参数,可以像上述第7种情况处理。
约束协议在不透明结果类型处表达
约束类型有可能在不透明结果类型处出现。比如:
代码语言:Swift复制func transformElements<S : Sequence<E>, E>(_ lines: S) -> some Sequence<E>
使用some
, 例如:
func transform(_: some Sequence<some Equatable>) -> some Sequence<some Equatable>