:从数值优先级到 precedencegroup 偏序体系)
文档【免费下载链接】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-0077 提案 撰写。SE-0077Improved operator declarations是 Swift 3.0 时代重构运算符声明语法与优先级机制的里程碑提案它废除了用整数表达优先级的旧方式引入precedencegroup声明式语法将单一优先级层级替换为基于偏序关系的有向无环图。读者读完本文后将能完整理解precedencegroup的associativity、higherThan、lowerThan、assignment四个核心属性的语义与取舍掌握自定义运算符接入 Swift 标准优先级体系的方法并理解 Swift 编译器如何通过传递性与可达性判定两个运算符能否省略括号。该提案已随 Swift 3.0 落地实现见 releases/swift-3_0.md。提案背景为什么要重写运算符声明在 Swift 早期Swift 2.x时代运算符声明采用数值优先级 结合性的写法声明体是一个无序的属性集合// Swift 2.x 写法 infix operator { precedence 100 associativity left }SE-0077 指出这种设计存在三类根本性缺陷构成了提案的完整动机。数值定义优先级的弊端最初运算符拥有整齐的优先级数值90、100、110、120、130、140、150、160。但随着语言演进越来越多的运算符被引入且优先级数值不能随意改动否则就是破坏性变更于是出现了大量缝合怪数值Range 运算符获得了 135as获得了 132??的优先级高于但低于as因此被硬塞给 131。结果是在与??之间再也无法插入任何自定义运算符。这是数值优先级体系的必然宿命——整数区间有限运算符不断增多最终任何两个已有运算符之间都无法再插入新运算符。单一优先级层级的弊端旧体系要求一个运算符与所有其他运算符都存在优先级关系因为所有运算符共享同一条数值线。这在很多场景下是多余的、甚至有害的。提案列举了两个高频错误模式a b c // 常见的误写模式 a / b as Double // 常见的误写模式C 编译器有时会对这类写法给出警告但 Swift 不会。根因在于优先级对任意两个运算符对都有定义。如果只与位运算相关、/只与算术运算相关那么上面两个表达式就必须加括号从而在编译期就避免这类隐蔽 bug。旧声明语法的弊端旧语法本质上是一袋无结构的词an unstructured bag of words与 Swift 其他声明的风格严重不一致SE-0077 的目标之一就是让运算符声明语法与语言整体风格对齐。解决方案总览运算符 优先级组提案的核心思路是把优先级从运算符身上剥离抽象为独立的优先级组precedence group运算符通过类似继承的语法归属到某个组// 之前 infix operator { precedence 100 associativity left } // 之后 precedencegroup ComparisonPrecedence { associativity: left higherThan: LogicalConjunctionPrecedence } infix operator : ComparisonPrecedence在 Swift 3.0 中这正是我们至今仍在使用的语法。新的声明语法详解运算符声明不再有声明体prefix/infix/postfix运算符声明都简化为一行、无大括号prefix operator ! infix operator 优先级组声明associativity 与组归属优先级组通过precedencegroup关键字声明内部可选的属性包括结合性left或right。infix运算符通过: 组名挂靠到某个组precedencegroup Additive { associativity: left } infix operator : Additive infix operator - : Additive多个运算符可以共享同一个优先级组这正是标准库中、-、、-、|、^同属AdditionPrecedence的实现方式。优先级机制偏序取代全序单一优先级层级的概念被彻底移除。两个相邻的infix运算符若要省略括号其所属优先级组之间必须存在明确的优先级关系否则编译报错precedencegroup Additive { associativity: left } precedencegroup Multiplicative { associativity: left higherThan: Additive } precedencegroup BitwiseAnd { associativity: left } infix operator : Additive infix operator * : Multiplicative infix operator : BitwiseAnd 1 2 * 3 // ok* 的优先级高于 1 2 3 // 错误 与 之间没有定义优先级关系注意BitwiseAnd与Additive/Multiplicative均无关联因此1 2 3必须显式写括号从而规避了引言中a b c这类隐蔽 bug。每个运算符 / 优先级组只能声明一次因此在已有组之间新增优先级关系这条路也被封死保证优先级关系图对读者是稳定、可预期的。传递性传播编译器会对组间关系施加传递性公理。只要A B且B C即可推断A Cprecedencegroup Exponentiative { associativity: left higherThan: Multiplicative } infix operator ** : Exponentiative 1 2 ** 3 // 等价于 1 (2 ** 3)此处Exponentiative Multiplicative、Multiplicative Additive由传递性推出Exponentiative Additive。同时一个优先级组可以声明多个优先级关系多个higherThan/lowerThan条目。DefaultPrecedence未指定组的归宿未显式声明所属组的infix运算符会被隐式归入DefaultPrecedence组precedencegroup DefaultPrecedence { higherThan: Ternary }因此以下两个声明完全等价infix operator | : DefaultPrecedence infix operator |assignment可选链中的特殊折叠行为Swift 2.2 的assignment修饰符允许标记了assignment的运算符被折叠进可选链foo?.bar 2会按foo?(.bar 2)解析而不是在类型检查阶段失败于(foo?.bar) 2。SE-0077 将该行为迁移为优先级组属性assignment: true由组整体继承这一语义。lowerThan跨模块插入低优先级运算符higherThan只能引用低于自己的组无法把新运算符插到已有运算符之下此时可使用lowerThan。若目标运算符位于另一个模块通过lowerThan即可完成插入因为无法修改别处的声明只能在自己的组中反向声明关系// module Swift precedencegroup Additive { higherThan: Range } precedencegroup Multiplicative { higherThan: Additive } // module A precedencegroup Equivalence { higherThan: Comparative lowerThan: Additive // 可行Additive 位于另一模块 } infix operator ~ : Equivalence 1 2 ~ 3 // (1 2) ~ 3因为 Additive Equivalence 1 * 2 ~ 3 // (1 * 2) ~ 3因为 Multiplicative Additive Equivalence 1 2 ~ 3 // 1 (2 ~ 3)因为 Equivalence Comparative 1 2 ~ 3 // 1 (2 ~ 3)因为 Equivalence Comparative Assignment 1 ... 2 ~ 3 // 错误Range 与 Equivalence 之间没有关系详细设计优先级图、可达性与环路检测优先级组关系构成有向无环图所有优先级组之间的higherThan/lowerThan关系构成一张Directed Acyclic GraphDAG。查询两个运算符之间的优先级关系等价于图中的**可达性Reachability**问题——这是该机制在算法层面的本质。传递性检查环路即编译错误所有优先级关系必须是传递且无环的。定义A B、B C的同时又定义A C会直接编译报错。提案给出两个典型环路示例precedencegroup A { higherThan: B } precedencegroup B { higherThan: A } // 构成 A B A 的环precedencegroup A { } precedencegroup B { higherThan: A } precedencegroup C { higherThan: B lowerThan: A } // 构成 C B A C 的环检查是否存在这类矛盾等价于检查优先级组的 DAG 是否包含有向环。禁止拼接无关的导入组通过传递性规则在两个已导入且原本无关的组之间建立新关系属于编译错误// Module X precedencegroup A { } precedencegroup C { } // Module Y import X precedencegroup B { higherThan: A lowerThan: C }编译器会报错B uses transitivity to define relationship between imported groups A and C。理由是若允许这种拼接开发者就能间接在标准库优先级组之间制造关系破坏图的清晰度、误导读者。特殊运算符编译器硬编码内置运算符is、as、as?、as!、、?:有既定优先级但不能用 Swift 语法声明它们属于语法糖。编译器内部将它们的优先级硬编码效果等同于执行了以下并非合法 Swift 的声明// NOT valid Swift infix operator is : CastingPrecedence infix operator as : CastingPrecedence infix operator as? : CastingPrecedence infix operator as! : CastingPrecedence infix operator ?: : TernaryPrecedence infix operator : AssignmentPrecedence文法变化语言层面发生如下词法/文法调整移除局部关键字assignment、precedence新增关键字precedencegroup以及局部关键字higherThan、lowerThan。完整文法如下operator-declaration → prefix-operator-declaration | postfix-operator-declaration | infix-operator-declaration prefix-operator-declaration → prefix operator operator postfix-operator-declaration → postfix operator operator infix-operator-declaration → infix operator operator infix-operator-groupₒₚₜ infix-operator-group → : precedence-group-name precedence-group-declaration → precedencegroup precedence-group-name { precedence-group-attributes } precedence-group-attributes → precedence-group-assignmentₒₚₜ precedence-group-associativityₒₚₜ precedence-group-relationsₒₚₜ precedence-group-assignment → assignment : boolean-literal precedence-group-associativity → associativity : precedence-group-associativity-option precedence-group-associativity-option → left | right precedence-group-relations → precedence-group-relation | precedence-group-relation precedence-group-relations precedence-group-relation → higherThan : precedence-group-name precedence-group-relation → lowerThan : precedence-group-name precedence-group-name → identifier注意assignment的取值是boolean-literal即true/false而associativity只允许left或right无none选项。标准库迁移完整的优先级组层次SE-0077 将标准库所有运算符声明重写为优先级组形式。以下层级在 Swift 3.0 及后续版本中长期稳定是自定义运算符接入时的坐标系全部来自提案原文的 Standard library changes 一节precedencegroup AssignmentPrecedence { assignment: true associativity: right } precedencegroup TernaryPrecedence { associativity: right higherThan: AssignmentPrecedence } precedencegroup DefaultPrecedence { higherThan: TernaryPrecedence } precedencegroup LogicalDisjunctionPrecedence { associativity: left higherThan: TernaryPrecedence } precedencegroup LogicalConjunctionPrecedence { associativity: left higherThan: LogicalDisjunctionPrecedence } precedencegroup ComparisonPrecedence { higherThan: LogicalConjunctionPrecedence } precedencegroup NilCoalescingPrecedence { associativity: right higherThan: ComparisonPrecedence } precedencegroup CastingPrecedence { higherThan: NilCoalescingPrecedence } precedencegroup RangeFormationPrecedence { higherThan: CastingPrecedence } precedencegroup AdditionPrecedence { associativity: left higherThan: RangeFormationPrecedence } precedencegroup MultiplicationPrecedence { associativity: left higherThan: AdditionPrecedence } precedencegroup BitwiseShiftPrecedence { higherThan: MultiplicationPrecedence }对应的运算符归属如下摘录postfix operator postfix operator -- // postfix operator ! prefix operator prefix operator -- prefix operator ! prefix operator ~ prefix operator prefix operator - // infix operator : AssignmentPrecedence infix operator * : AssignmentPrecedence infix operator / : AssignmentPrecedence infix operator % : AssignmentPrecedence infix operator : AssignmentPrecedence infix operator - : AssignmentPrecedence infix operator : AssignmentPrecedence infix operator : AssignmentPrecedence infix operator : AssignmentPrecedence infix operator ^ : AssignmentPrecedence infix operator | : AssignmentPrecedence // infix operator ?: : TernaryPrecedence infix operator || : LogicalDisjunctionPrecedence infix operator : LogicalConjunctionPrecedence infix operator : ComparisonPrecedence infix operator : ComparisonPrecedence infix operator : ComparisonPrecedence infix operator : ComparisonPrecedence infix operator : ComparisonPrecedence infix operator ! : ComparisonPrecedence infix operator : ComparisonPrecedence infix operator ! : ComparisonPrecedence infix operator ~ : ComparisonPrecedence infix operator ?? : NilCoalescingPrecedence // infix operator as : CastingPrecedence // infix operator as? : CastingPrecedence // infix operator as! : CastingPrecedence // infix operator is : CastingPrecedence infix operator .. : RangeFormationPrecedence infix operator ... : RangeFormationPrecedence infix operator : AdditionPrecedence infix operator - : AdditionPrecedence infix operator : AdditionPrecedence infix operator - : AdditionPrecedence infix operator | : AdditionPrecedence infix operator ^ : AdditionPrecedence infix operator * : MultiplicationPrecedence infix operator / : MultiplicationPrecedence infix operator % : MultiplicationPrecedence infix operator * : MultiplicationPrecedence infix operator : MultiplicationPrecedence infix operator : BitwiseShiftPrecedence infix operator : BitwiseShiftPrecedence从源码结构可以看出一条清晰的自底向上链Assignment Ternary Default LogicalDisjunction LogicalConjunction Comparison NilCoalescing Casting RangeFormation Addition Multiplication BitwiseShift。这套体系至今仍是 Swift 运算符优先级的事实标准后续提案如 SE-0531 字面量表达式在描述编译期常量折叠时也明确声明运算符优先级与结合性遵循 Swift 标准规则。对现有代码的影响与迁移标准库所有运算符声明被重写优先级组被引入即上文清单。用户自定义运算符同样需要重写。迁移工具会移除旧声明的大括号体infix运算符会被隐式归入DefaultPrecedence。潜在破坏依赖用户自定义运算符之间隐式优先级关系的代码可能被破坏——旧体系中任意两个运算符都有可比优先级新体系下没有显式关系的组之间不可比。这类代码需要手工将运算符加入期望的优先级组来修复。未来方向重排标准库优先级提案明确指出创建它的主要动机之一就是打破标准库运算符的单一层级例如让只与位运算相关、/只与算术相关。但重排标准库优先级是另一个提案的主题需要单独讨论——SE-0077 只负责搭建语法与机制地基。这一地基属性也体现在后续演进中SE-0389 附属宏 特别规定operator与precedencegroup声明永远不能由宏生成因为宏可能借此改写既有代码的优先级图造成看到宏展开的代码与未看到展开的代码之间出现微妙的语义差异——这从侧面印证了优先级组声明在语言语义中的核心地位。备选方案回顾SE-0077 的评审过程中讨论过多套替代设计理解它们有助于把握最终方案的设计取向。分离声明结合性与优先级associativity Multiplicative left precedence Multiplicative Additive precedence Exponentiative Multiplicative任何一条声明中出现组名即视为组定义关系声明只允许以保持一致性。对禁止拼接无关导入组的限制仍然保留。不使用优先级组让每个运算符各自声明优先级关系precedence - precedence precedence / * precedence % * precedence * 缺点关系图会大得多、也更难理解而且优先级组概念仍然存在——只是让每个组中有一个特权运算符作为代表。元循环meta-circular语法让某个特殊类型的常量参与编译期计算struct PrecedenceGroup { enum Associativity { case left, right, none } let associativity: Associativity let higherThan: [StaticString] let lowerThan: [StaticString] } let Multiplicative PrecedenceGroup(.left, [Associativity], [])评审者 John McCall 的结论是运算符优先级本质上必须通过某种方式传达给编译器才能解析代码本提案只是在决定传达的语法既然简单的声明就够了就没有理由采用概念上更复杂的方案。把拼接无关组降级为警告优点简化语言模型、减轻编译器负担当优先级层级被更新破坏时开发者可以快速 hack把所有组拼在一起让代码立即恢复运行。缺点允许了提案所担心的读者困惑场景。用precedence取代precedencegroup优点更短、声明命名风格与协议一致。缺点需要把precedence变成关键字而precedencegroup更能准确表达声明对象的含义。其他语法变体评审中还讨论了大量措辞与书写形式变体关系关键字候选有above/below、upper/lower、greaterThan/lessThan、strongerThan/weakerThan、gt/lt、before/after结合性关键字候选有associate以及associativity(left)、逗号分隔属性、/中缀风格、precedencegroup与单行声明混合等十余种写法详见提案的 Possible syntax variations 一节。最终选定的precedencegroupassociativity:/higherThan:/lowerThan:大括号风格兼顾了可读性、与 Swift 声明风格的统一性以及语法可扩展性。总结SE-0077 用一套简洁、可组合、可验证的声明式语法彻底取代了 Swift 2.x 的数值优先级体系组抽象优先级从运算符身上剥离成为可复用的precedencegroup运算符通过: 组名挂靠偏序取代全序只有在组间显式声明或由传递性推导出关系时才能省略括号从语言层面消灭了一整类优先级误写 bug图论建模组关系构成 DAG环路检测保证一致性可达性查询决定解析结果模块安全的扩展性lowerThan允许跨模块在既有运算符之下插入新组同时禁止通过传递性拼接两个无关的导入组长期稳定标准库的整套优先级层次从 Swift 3.0 沿用至今成为后续所有语言特性从字面量宏到附属宏共同依赖的语义基石。无论是为库定义新的自定义运算符还是阅读 Swift 源码中的precedencegroup声明理解 SE-0077 的设计动机与机制细节都是必不可少的。延伸阅读完整提案见 proposals/0077-operator-precedence.mdSwift 3.0 发布说明见 releases/swift-3_0.md若想了解运算符声明与宏系统的边界约束可参考 SE-0389 附属宏。赞分享文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载相关推荐Slang 运算符优先级一致性测试体系从 16 级优先级表到 36 项可执行声明的验证实践Slang 运算符优先级一致性测试体系从 16 级优先级表到 36 项可执行声明的验证实践 导读 本文基于 Slang 着色语言项目中的一致性测试文档 RE编译器图形学编程语言SE-0096Swift 中 dynamicType 从属性到运算符的演进与 type(of:) 的诞生SE 0096Swift 中 dynamicType 从属性到运算符的演进与 type of: 的诞生 导读 SE 0096Converting dynam文档Swift 进化提案解读SE-0024 可选值赋值运算符 ?? 的提出、否决与启示Swift 进化提案解读SE 0024 可选值赋值运算符 ?? 的提出、否决与启示 导读 SE 0024《Optional Value Setter ??文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考