当前位置:网站首页>Swift5.7 扩展 some 到泛型参数
Swift5.7 扩展 some 到泛型参数
2022-07-03 11:56:00 【DerekYuYi】
介绍
Swift 中的泛型语法是为了类型通用性设计,这种通用性允许在函数输入和输出时,使用复杂的类型集合来表达,前提是类型必须前后一致。例如下面这个例子是从两个序列构建一个数组:
func eagerConcatenate<Sequence1: Sequence, Sequence2: Sequence>(
_ sequence1: Sequence1, _ sequence2: Sequence2
) -> [Sequence1.Element] where Sequence1.Element == Sequence2.Element
这个函数声明中有不少内容:两个函数参数是由调用者确定的不同类型,分别由 Sequence1
和Sequence2
来捕获。这两个类型都需要遵守Sequence
协议,而且,where
条件语句决定两个序列中的元素也必须是相同类型。最后, 该函数的返回值是Sequence1
中元素类型组成的数组。只要满足上述约束条件,就可以将此操作用于许多不用的输入,例如:
eagerConcatenate([1, 2, 3], Set([4, 5, 6])) // okay, produces an [Int]
eagerConcatenate([1: "Hello", 2: "World"], [(3, "Swift"), (4, "!")]) // okay, produces an [(Int, String)]
eagerConcatenate([1, 2, 3], ["Hello", "World"]) // error: sequence element types do not match
那么这种语法有没有可能做个简化?对于不需要引入这些复杂约束的场景,这种语法就显的比较重。比如,下面这个函数描述在水平方向组合两个 SwiftUI 中的视图:
func horizontal<V1: View, V2: View>(_ v1: V1, _ v2: V2) -> some View {
HStack {
v1
v2
}
}
有很多模版文件可以声明只使用一次的泛型参数类型 V1
和 V2
, 在上面例子中的模版是<V1: View, V2: View>
。因为 V1
和 V2
并不是它真正的类型,还需要往模版里找到它真实的类型定义。这实际上比它本身看起来更复杂(会比平常的调用理解多一层, 当前的参数寻找过程是:v1 -> V1 -> View, 而非 v1 -> View)。对返回的结果来说,又可以使用不透明类型(opaque result type)some
来隐藏实际的返回值,仅通过它符合的协议来描述它。
本篇提议把不透明类型的语法扩展到了参数上,允许指定泛型函数参数,而不需要声明与泛型参数列表关联的模版。此时上述的 horizontal
函数可以省略模版声明 <V1: View, V2: View>
, 可以这样写:
func horizontal(_ v1: some View, _ v2: some View) -> some View {
HStack {
v1
v2
}
}
改进后的horizontal
函数本质上与第一个表达等价,但是改进后的函数去掉了泛型参数和模版之前对应关系,更加方便阅读和理解:它接受两个视图(视图的具体类型这里不重要),并返回一个视图(返回的视图类型也不重要)。
提议的解决方案
这篇提议把some
关键字的用法扩展到函数,初始化器(initializer)和下标声明的参数类型中。与不透明类型一样,some P
表示的类型没有名字,只有一个遵守协议P
的约束。当某个参数类型内出现了一个不透明类型时,这个不透明类型会被没有名字的泛型参数代替。举个例子:
func f(_ p: some P) { }
与下面的例子是等价的。此时参数p
表示一个遵循协议P
的任何类型。
func f<_T: P>(_ p: _T) { }
与不透明结果类型不同,调用方通过类型推断确定不透明参数类型的真实类型。例如,我们假设Int
和String
都遵循协议P
,则可以使用Int
或String
来完成函数调用,或者引用函数:
f(17) // ,推断不透明类型为 Int
f("Hello") // ,推断不透明类型为 String
let fInt: (Int) -> Void = f // ,推断不透明类型为 Int (f 函数无返回值,与返回 Void 等价)
let fString: (String) -> Void = f // ,推断不透明类型为 String
SE-0328 这篇提议是讲结构化的不透明结果类型,它扩展了不透明结果类型,允许在结果类型中的任何结构位置多次使用some P
类型。参数中的不透明类型也允许相同的结构用法,例如:
func encodeAnyDictionaryOfPairs(_ dict: [some Hashable & Codable: Pair<some Codable, some Codable>]) -> Data
上述用法跟下面等价(分析:泛型参数_T1,_T2,_T3
和它们对应的模版 <_T1: Hashable & Codable, _T2: Codable, _T3: Codable>
:
func encodeAnyDictionaryOfPairs<_T1: Hashable & Codable, _T2: Codable, _T3: Codable>(_ dict: [_T1: Pair<_T2, _T3>]) -> Data
声明中some
修饰的每一个实例都代表每个不同的隐式泛型参数。
不透明结果类型和不透明参数类型其实很相似,都是使用
some
关键字来修饰,前者用在返回结果中,后者用在参数中。本质都是表达遵循同一协议类型的泛型类型。
详细设计实现
不透明参数类型只能用于函数,初始化器(initializer), 和下标声明中的参数修饰,不能把它们用作别名(typealias),或者函数类型中的入参(function type)。例如:
typealias Fn = (some P) -> Void // error: 不能用作别名
let g: (some P) -> Void = f // error: 不能用于函数类型的参数值
除了别名和函数类型中参数值这两个限制。还有2个场景限制使用:可变泛型和函数类型的参数。
可变泛型
不透明类型不能在可变参数中使用。比如下例中的可变参数 P...
,不能使用some
类型:
func acceptLots(_: some P...)
这个限制之所以存在,是因为如果 Swift 获得可变泛型,则当前提议所实现的效果就会不成立。继续看上面这个例子,上述函数在当前提议的规则下,some P...
该语法糖对应的真实表达应该是<_T: P>(_: _T...)
, 跟下面的表达等价:
func acceptLots<_T: P>(_: _T...)
由于这里支持可变参数,并且可变参数的类型都要求一样,明显调用函数传入不同参数时,会报错:
acceptLots(1, 1, 2, 3, 5, 8) // okay
acceptLots("Hello", "Swift", "World") // okay
acceptLots("Swift", 6) // error: argument for `some P` could be either String or Int
可以看出当前提议规则生成<_T: P>
是支持相同类型的泛型,如果支持可变泛型,则函数允许不同类型的输入,前后不一致无法兼容。
针对上述不同参数的报错,有一种可能的解决方案是:对于可变泛型,可以将隐式泛型参数改为泛型参数包,也就是模版中P
改为P...
,此时约束从遵循同一类型的泛型变成支持不同类型的泛型(感觉支持了所有类型,已经感知不到约束):
func acceptLots<_ Ts: P...>(_: _Ts...)
这时,acceptLots
可以接受各种不同类型的参数:
acceptLots(1, 1, 2, 3, 5, 8) // okay, Ts 包括 6个 Int 参数
acceptLots("Hello", "Swift", "World") // okay, Ts 包括 3个 String 参数
acceptLots("Swift", 6) // okay, Ts 包括 1个 String 参数和1个 Int 参数
当前提议不包括这种解决方案,社区提出了这种解决方案,可能在后续的版本会考虑。
函数类型的参数中使用不透明参数
SE-0328 禁止在函数类型的参数中使用不透明参数。例如函数f()
返回值是函数类型 (some P) -> Void
:
func f() -> (some P) -> Void { ... } // ,不能在函数类型的参数中使用不透明参数 some
然后我们再按正常使用调用 f()
, 把f()
的结果赋值给fn
, 例如:
let fn = f()
fn(/* 这里应该怎么构造函数中的值?这里不知道怎么写 */)
很显然在调用fn
函数时,很难使用。因为调用者无法轻松创建未知的,未命名类型的参数值。
相同的规则也运用在函数类型作为参数的情况。其实本质还是 some P
不能作为函数类型中的参数类型。例如:
func g(fn: (some P) -> Void { ... } // ,不能在函数类型的参数中使用不透明参数
在函数 g
的实现过程中,如果some P
类型的值在其他地方没有命名,则很难生成该类型的值。
对源代码兼容性影响
当前提议特性是一个纯语言扩展,没有向后兼容性问题,因为some
在参数上的所有使用,目前正在其他版本都会报错。
对 ABI 稳定性影响
不影响 ABI 和运行时,因为some
本质上是泛型的语法糖。
对 API 扩展性影响
不会破坏 ABI 或者 API。some
是语法糖,表达的是带模版的显式泛型参数(回忆下最初的目的是想把:<V1: View, V2: View>(_ v1: V1, _ v2: V2)
转为 (_ v1: some View, _ v2: some View)
写法)。也就是与现有的这种语法是等价的,但在从 Swift 5.7 你可以使用更为简洁的 some P
来修饰参数,而非仅仅是返回结果。唯一的前提是前后写法的约束类型必须相同。
总结
通过当前提议 SE-0341,你应当知道:
- Swift5.7 通过运用
some
到泛型参数类型,是为了去除泛型模块声明的冗余表达; some
对应的是与之等价的泛型模版表达式;- 内部通过类型推断,确定真实的不透明参数类型所对应的类型
边栏推荐
- 102. Sequence traversal of binary tree
- Use of atomicinteger
- Wechat applet development - page Jump transfer parameters
- Is it safe to open an account for online stock speculation? Who can answer
- (construction notes) learn the specific technology of how to design reusable software entities from three levels: class, API and framework
- elastic_ L02_ install
- Sword finger offer03 Repeated numbers in the array [simple]
- (construction notes) learning experience of MIT reading
- Kubectl_ Command experience set
- Dart: about Libraries
猜你喜欢
阿里大于发送短信(用户微服务--消息微服务)
剑指Offer06. 从尾到头打印链表
If you can't learn, you have to learn. Jetpack compose writes an im app (II)
(construction notes) ADT and OOP
Use bloc to build a page instance of shutter
Shutter widget: centerslice attribute
Eureka self protection
Flutter 退出登录二次确认怎么做才更优雅?
剑指Offer07. 重建二叉树
2.8 overview of ViewModel knowledge
随机推荐
实现验证码验证
OpenGL index cache object EBO and lineweight mode
Flutter Widget : KeyedSubtree
239. Sliding window maximum
Swagger
剑指Offer03. 数组中重复的数字【简单】
Use Tencent cloud IOT platform to connect custom esp8266 IOT devices (realized by Tencent continuous control switch)
Flutter: self study system
Pki/ca and digital certificate
Wechat applet pages always report errors when sending values to the background. It turned out to be this pit!
Pragma pack syntax and usage
PHP export word method (one MHT)
DEJA_ Vu3d - cesium feature set 053 underground mode effect
elastic_ L04_ introduction. md
ES6 standard
Dart: view the dill compiled code file
Sword finger offer06 Print linked list from end to end
(构造笔记)从类、API、框架三个层面学习如何设计可复用软件实体的具体技术
temp
(构造笔记)GRASP学习心得