Swift 5.7 针对主要关联类型的轻量级同类型优化

2022-10-08 18:17:38 浏览数 (1)

介绍

本篇提议引入一种新的语法,用来遵守泛型参数并通过同一类型约束关联类型。

目的

先来看一个例子,该例中函数是用来返回源文件中的多个行数。

代码语言: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 数组。例如:

代码语言:Swift复制
func readSyntaxHighlightedLines(_ file: String)
  -> SyntaxTokenSequence<LineSequence> {
  ...
}

注意:这里隐藏了一个等价关系:Token相当于序列的元素,是序列的关联类型,[Token]与Sequence是等价的。这与我们平时使用关联类型用法也是一样的。

这个函数的返回结果是不是很复杂?借鉴前面提议实现的特性some, 貌似这里我们使用它来隐藏背后的实际类型,例如:

代码语言:Swift复制
func readSyntaxHighlightedLines(_ file: String)
  -> some Sequence {
  ...
}

但是, 函数readSyntaxHighlightedLines()已经无法充分表达原有的意思,因为后者无法表达:结果Sequence的关联类型Element[Token]中的元素是等价的。

再来看另外一个例子,这个例子中的函数作用是操作两个String数组:

代码语言:Swift复制
func concatenate(_ lhs: Array<String>, _ rhs: Array<String>) -> Array<String> {
  ...
}

函数concatenate()中的参数类型和返回类型都是Array<String>, 我们可以把它概括为抽象的序列实现,比如使用泛型来隐藏具体类型,并通过条件语句来限制泛型类型。比如这样用:

代码语言:Swift复制
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()函数可以这样表达:

代码语言:Swift复制
func readSyntaxHighlightedLines(_ file: String) -> some Sequence<[Token]> {
  ...
}

上述concatenate()函数现在可以这样写:

代码语言:Swift复制
func concatenate<S: Sequence<String>>(_ lhs: S, _ rhs: S) -> S {
  ...
}

主要关联类型旨在用于调用方提供的关联类型。这些关联类型通常由遵循类型的泛型表达。例如,ElementSequence的主要关联类型,因为Array<Element>Set<Element>都遵循Sequence协议,Element则是由它们对应具体类型的泛型参数来表示。比如Set<Int>的泛型参数类型此时是Int, 则Element此时是与Int类型对应。

具体设计细节

在协议声明中,协议名称后面可以有一个主要关联类型列表,关联类型声明在尖括号'<>'里。

这个关联列表是可选的,你可以写也可以像之前声明协议样,后面不用跟任何声明。

如果关联列表存在,必须至少有一个主要关联类型存在。多个关联类型在'<>'中以逗号隔开。

关联类型列表中的每个关联类型必须要定义在对应的协议声明内,或者继承的协议声明内。

所以原来协议定义语法需要加上'主要关联类型列表',修改如下:

  • 协议声明 -> attributes(可选) 访问修饰符(可选) protocol 协议名 主要关联类型列表 协议继承语句 泛型条件语句 协议体
  • 主要关联类型列表 -> < 主要关联类型条目 >
  • 主要关联类型条目 -> 主要关联类型 | 主要关联类型 , 主要关联类型条目
  • 主要关联类型 -> 类型名称 看两个例子:
代码语言:Swift复制
// 关联类型`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 语法,例如:
代码语言:Swift复制
extension Collection<String> { ... }

// 等价于
extension Collection where Element == String { ... }
  • 第二种情况是继承协议,也就是遵循某一个协议,例如:
代码语言:Swift复制
protocol TextBuffer: Collection<String> { ... }

// 等价于

protocol TextBuffer Collection where Element == String  { ... }
  • 第三种情况是继承泛型,也就是需要指定元素类型为某个类型,比如集合的指定元素为 String类型:
代码语言:Swift复制
  func sortLines<S : Collection<String>>(_ lines: S) -> S

  // 等价于:

  func sortLines<S : Collection>(_ lines: S) -> S

    where S.Element == String
  • 第四种情况是继承关联类型,比如:
代码语言:Swift复制
protocol Document {

   associatedtype Lines: Collection<String>

}

// 等价于:

protocol Document {

  associatedtype Lines: Collection where Lines.Element == String

}
  • 第五种情况是在where子句的右侧,例如:
代码语言:Swift复制
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 不透明参数声明在这篇提议中已经实现。举个例子说明:
代码语言:Swift复制
func sortLines(_ lines: some Collection:<String>)

// 等价于:

func sortLines<C: Collection<String>>(_ lines: C)

// 也等价于:

func sortLines<C: Collection>(_ lines: C) where C.Element == String
  • 第七种情况是协议参数包括嵌套的不透明参数. 例如:
代码语言:Swift复制
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,并在后面追加参数约束,

代码语言:Swift复制
T :P

T.PrimaryType1 == Arg1

T.PrimaryType2 == Arg2

...

如果Arg1some参数,可以像上述第7种情况处理。

约束协议在不透明结果类型处表达

约束类型有可能在不透明结果类型处出现。比如:

代码语言:Swift复制
func transformElements<S : Sequence<E>, E>(_ lines: S) -> some Sequence<E>

使用some, 例如:

代码语言:Swift复制
func transform(_: some Sequence<some Equatable>) -> some Sequence<some Equatable>

0 人点赞