:让泛型类型抽象参数包,开启 Variadic Generics 的类型层面)
Swift 变体泛型类型SE-0398让泛型类型抽象参数包开启 Variadic Generics 的类型层面【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution本篇技术指南以 Swift Evolution 提案 SE-0398Allow Generic Types to Abstract Over Packs已在 Swift 5.9 实现为骨架系统讲解 Swift 如何将参数包Parameter Packs从泛型函数推广到泛型类型声明。你将掌握each在类型层面声明参数包的语法、变体类型的引用规则与单参数包限制、存储属性与类型别名中包展开的合法位置、需求推断Requirement Inference的三条规则以及变体泛型类的继承限制与 ABI 影响并了解 SE-0393、SE-0399、SE-0408 等提案构成的完整变体泛型生态。背景SE-0393 与参数包基础变体泛型Variadic Generics的第一步是 SE-0393Value and Type Parameter PacksSwift 5.9 实现它为泛型函数引入了抽象数量可变类型参数的能力类型参数包Type Parameter Pack在尖括号中用each关键字声明表示零个或多个类型参数的列表例如func zipeach S: Sequence(...)值参数包Value Parameter Pack表示零个或多个函数参数的列表用repeat前缀的包展开类型声明例如repeat each S包展开Pack Expansionrepeat P形式其中模式P必须包含至少一个以each拼写的包引用替换时模式会为包中每个元素重复展开例如T : {ArrayInt, SetString}时repeat Optionaleach T展开为OptionalArrayInt, OptionalSetString通用需求展开Generic Requirement Expansion形如repeat each S: Sequence的需求模式会对每个包元素逐条展开约束。SE-0393 明确把变体泛型类型列为未来方向见其 Future directions 一节该提案只支持函数上的类型参数包一个配套提案将描述泛型 struct、enum 和 class 上的类型参数包。SE-0398 正是这个配套提案——它把参数包的概念从函数推广到类型声明本身。动机为什么泛型类型需要抽象参数包当试图推广集合上的通用算法时对数量可变的类型进行抽象的泛型类型会自然涌现。最典型的例子是zip当前标准库的zip返回Zip2Sequence仅凭 SE-0393 无法定义等价的变体zip函数因为其返回类型需要任意数量的类型参数——每个输入序列对应一个func zipeach S(_ seq: repeat each S) - ??? where repeat each S: Sequence问题在于返回类型???无法表达针对每个S元素各有一个类型参数的结构。只有允许类型持有参数包才能为这种函数定义出真正的返回类型。解决方案ZipSequence完整示例在泛型类型的参数列表中each关键字声明一个泛型参数包与它在泛型函数参数列表中的用法完全一致。存储属性的类型可以包含包展开类型如ZipSequence中的let seq与var iterstruct ZipSequenceeach S: Sequence: Sequence { typealias Element (repeat (each S).Element) let seq: (repeat each S) func makeIterator() - Iterator { return Iterator(iter: (repeat (each seq).makeIterator())) } struct Iterator: IteratorProtocol { typealias Element (repeat (each S).Element) var iter: (repeat (each S).Iterator) mutating func next() - Element? { return ... } } } func zipeach S(_ seq: repeat each S) - ZipSequencerepeat each S where repeat each S: Sequence这里的关键观察点struct ZipSequenceeach S: Sequence用each声明了一个受Sequence约束的类型参数包存储属性seq的类型(repeat each S)是一个元组类型其元素为包展开——用于存放任意数量的序列值typealias Element (repeat (each S).Element)使用了 SE-0393 引入的成员类型参数包Member Type Parameter Pack(each S).Element从每个包元素投影其关联类型Element变体zip函数以ZipSequencerepeat each S作为返回类型将值包原样存入返回的变体类型。详细设计可变的泛型类型种类与变体定义Swift 具有以下泛型类型声明形式结构体Struct枚举Enum类Class与参与者Actor类型别名Type Alias一个泛型类型是**变体variadic**的如果它直接用each声明了类型参数包或者嵌套在另一个变体类型内部。本提案允许 struct、class、actor 和 type alias 成为变体enum 将在后续提案中单独处理。单参数包限制与嵌套破局一个泛型类型最多只能声明一个类型参数包。以下是合法声明struct S1each T {} struct S2T, each U {} struct S3each T, U {}但以下声明不合法struct S4each T, each U {}不过通过嵌套一个变体类型仍然可以抽象多个类型参数包——内层变体类型可以声明属于自己的参数包并可引用外层类型的参数包struct Outereach T { struct Innereach U { var fn: (repeat each T) - (repeat each U) } }这里Inner同时捕获外层包T与自身包U二者通过同一个函数类型包展开同时被展开。引用变体类型可变数量的泛型实参使用变体类型时S...语法允许指定可变数量的泛型实参。由于只有一个泛型参数包非包参数总是以固定前缀和固定后缀的形式出现在泛型实参列表两端struct ST, each U, V {} SInt, Float.self // T : Int, U : Pack{}, V : Float SInt, Bool, Float.self // T : Int, U : Pack{Bool}, V : Float SInt, Bool, String, Float.self // T : Int, U : Pack{Bool, String}, V : Float注意SInt, Float将U替换为空包这是允许的。泛型实参的最小数量等于非包泛型参数的数量上例中最小实参数为 2T与V必须始终指定SInt.self // error: expected at least 2 generic arguments如果变体类型的泛型参数列表仅由一个参数包构成可以用空的泛型实参列表引用它struct Veach T {} V .self注意V 与V并不相同前者把参数包T替换为空包后者对包不加任何约束只允许在能够推断泛型实参的上下文中出现或在V及其扩展的内部此时等同于Self。泛型实参列表中的占位符类型placeholder永远被理解为一个单独的包元素struct Veach T {} let x: V_ VInt() // okay let x: V_, _ VInt, String() // okay let x: V_ VInt, String() // error存储属性包展开的合法嵌套位置在变体类型中存储属性的类型可以包含包展开类型但存储属性的类型本身不能是包展开类型。包展开只能嵌套在元组类型、函数类型以及其他具名变体类型内部struct Seach T { var a: (repeat each ArrayT) var b: (repeat each T) - (Int) var c: Otherrepeat each T }这与泛型函数声明的参数不同——函数参数可以直接拥有包展开类型。一个未来提案可能会解除此限制引入真正的存储属性包见下文未来方向。需求推断Requirement Inference类型参数包上的泛型需求行为在函数与类型之间基本一致。但允许类型抽象参数包引入了**通用需求展开的推断Requirement Expansion Inference**问题关于需求展开的定义见 SE-0393。推断遵循以下规则若一个施加标量推断需求的泛型类型被应用到包展开内部的某个包元素上则推断出的需求是一个需求展开若一个泛型类型施加推断的需求展开则该需求会对每个具体泛型实参逐一展开若一个施加推断需求展开的泛型类型被应用到包展开内部的包元素上若推断出的需求包含多个被不同深度展开捕获的包元素则该推断无效否则嵌套的需求展开在语义上等价于最内层的需求展开。以下代码逐一演示了上述规则protocol P { associatedtype A } struct ImposeRequirementT where T: P {} struct ImposeRepeatedRequirementeach T where repeat each T: P {} struct ImposeRepeatedSameTypeT1: P, each T2 where repeat T1.A each T2 {} // Infers repeat each U: P func demonstrate1each U(_: repeat ImposeRequirementeach U) // Infers Int: P, V: P, repeat each U: P func demonstrate2each U, V(_: ImposeRepeatedRequirementInt, V, repeat each U) // Error. Would attempt to infer repeat U each U repeat V each V U.A V which is not a supported requirement in the language func demonstrate3aeach U, each V(_: repeat ImposeRepeatedSameTypeeach U, repeat each V)) // Infers Int: P, repeat each V: P func demonstrate3beach U, each V(_: repeat (each U, ImposeRepeatedRequirementInt, repeat each V))逐条解读demonstrate1ImposeRequirement是标量类型施加标量需求T: P当它出现在包展开repeat ...中作用于each U时规则 1 将需求升级为需求展开repeat each U: Pdemonstrate2ImposeRepeatedRequirementInt, V, repeat each U直接携带需求展开规则 2 对其每个实参逐一展开为Int: P, V: P以及repeat each U: Pdemonstrate3aImposeRepeatedSameTypeeach U, repeat each V中的需求展开若再被外层repeat展开会产生需求展开中的需求展开——U.A V的推断在不同深度捕获了多个包元素按规则 3.1 判定为非法demonstrate3b(each U, ImposeRepeatedRequirementInt, repeat each V)中外层包展开的模式只捕获U与V中同一深度的一个U出现在标量位置repeat each V出现在内层展开按规则 3.2 语义等价于最内层需求展开合法地推断出Int: P, repeat each V: P。一致性Conformances变体 struct、class 和 actor 可以遵守协议。协议对关联类型associated type的需求可以由底层类型包含包展开的类型别名来满足——这正是ZipSequence中typealias Element (repeat (each S).Element)的用法。类型别名Type Aliases与其他变体类型一样变体类型别名要么拥有自己的泛型参数包要么嵌套在另一个变体泛型类型内部。变体类型别名的底层类型可以像存储属性类型那样引用包展开类型——即包展开必须出现在嵌套位置而不能出现在顶层typealias Element (repeat (each S).Element) typealias Callback (repeat each S) - () typealias Factory Otherrepeat each S与函数声明参数不同类型别名底层类型不能是裸的repeat each S顶层包展开必须包在元组、函数类型或其他变体类型中。与其他类型别名一样变体类型别名可以嵌套在泛型函数内部而变体 struct 和 class 不可以嵌套在函数内。类禁止继承变体泛型类虽然对非 final 类采用类型参数包没有限制但本提案暂时限制此类类成为其他类的父类class Baseeach T { func foo(t: repeat each T) {} } // error: cannot inherit from a class with a type parameter pack class DerivedU, V: BaseU, V { override func foo(t: U, _: V) {} }尝试继承变体泛型类会输出错误。变体泛型类中 override 检查与构造器继承的正确行为将在后续提案中处理。源码兼容性变体泛型类型是一个纯新增的语言特性不影响既有代码的源码兼容性已有代码无需修改新特性只会让以前无法编译的代码变得合法。ABI 兼容性变体类型别名不属于模块的二进制接口不需要任何运行时支持其他变体类型会使用 Swift 运行时新增的入口点与行为。由于运行时支持需要对类型元数据type metadata逻辑做大量修改不支持向后部署到旧运行时——即使用变体泛型类型的框架无法在旧版本 Swift 运行时上运行用变体泛型类型替换非变体泛型类型在两个方向上都不具备二进制兼容性。二进制稳定binary-stable的框架在采用变体泛型类型时必须将其作为全新符号引入而不能改造既有类型。未来方向提案明确指出两个后续方向变体泛型枚举与变体泛型类的完整支持enum 的变体化以及类的继承/override/构造器继承语义将由后续提案解决存储属性包Stored Property Packs消除在变体类型中存储可变数量值时必须把包展开包进元组的限制struct Seach T { var a: repeat each ArrayT }提案同时说明要求今天用元组包装并不会损失表达能力——元组内容可以被转换为值包使用。备选方案Alternatives Considered带标签的泛型参数若能同时引入带标签的泛型参数可以解除单参数包限制本提案选择只允许单个无标签泛型参数包但不排除未来探索该方向完全不限制参数包数量此时会存在无法被显式拼写的变体泛型类型但仍可由类型推断构造struct Seach T, each U { init(t: repeat each T, u: repeat each U) {} } S(t: 1, hi, u: false)最终评审认为单参数包模型最简单要求变体类声明为final以拒绝子类化的方式替代使用点报错。但给类添加或移除final属于 ABI 破坏会妨碍发布在当前编译器下可用、未来允许子类化的 API因此被否决。生态延伸SE-0398 之后的变体泛型SE-0398 在 Swift 5.9 落地后变体泛型生态持续演进SE-0399Tuple of Value Pack ExpansionSwift 5.9允许在包重复模式中引用包含值包的抽象元组值填补了值包与容纳它的元组之间的功能鸿沟——例如repeat print(each abstractTuple)直接展开元组内的值包SE-0408Pack IterationSwift 6.0为包引入for-in迭代支持短路求值弥补了包展开表达式总是为每个元素求值一次的局限单参数包限制与显式类型包语法、包元素投影Int动态索引或 key-path 静态索引等方向也在 SE-0393 的未来方向中持续探讨。小结SE-0398 将参数包从泛型函数推广到泛型类型声明使 Swift 能够以类型安全的方式定义ZipSequenceeach S这类抽象数量可变类型的容器。其核心要点可归纳为each在类型参数列表中声明参数包单个类型至多声明一个参数包嵌套可间接抽象多个变体类型实参列表支持可变数量与空包存储属性与类型别名的包展开必须嵌套于元组、函数类型或具名变体类型中需求展开推断遵循三条规则变体泛型类暂不可被继承。结合 SE-0393、SE-0399 与 SE-0408Swift 的变体泛型已覆盖函数、类型、元组值与迭代四大维度为zip类算法的通用化与库 API 的瘦身提供了语言级基础设施。【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考