news 2026/9/23 6:12:22

Swift 运算符声明改进(SE-0077):从数值优先级到 precedencegroup 偏序体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Swift 运算符声明改进(SE-0077):从数值优先级到 precedencegroup 偏序体系
  • 文档

【免费下载链接】swift-evolution

This 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-0077(Improved operator declarations)是 Swift 3.0 时代重构运算符声明语法与优先级机制的里程碑提案:它废除了用整数表达优先级的旧方式,引入precedencegroup声明式语法,将"单一优先级层级"替换为"基于偏序关系的有向无环图"。读者读完本文后,将能完整理解precedencegroupassociativityhigherThanlowerThanassignment四个核心属性的语义与取舍,掌握自定义运算符接入 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 运算符获得了 135;
  • as获得了 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关键字声明,内部可选的属性包括结合性(leftright)。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 // 错误,+ 与 & 之间没有定义优先级关系

注意BitwiseAndAdditive/Multiplicative均无关联,因此1 + 2 & 3必须显式写括号,从而规避了引言中a & b < c这类隐蔽 bug。

每个运算符 / 优先级组只能声明一次,因此"在已有组之间新增优先级关系"这条路也被封死,保证优先级关系图对读者是稳定、可预期的。

传递性传播

编译器会对组间关系施加传递性公理。只要A > BB > C,即可推断A > C

precedencegroup Exponentiative { associativity: left higherThan: Multiplicative } infix operator ** : Exponentiative 1 + 2 ** 3 // 等价于 1 + (2 ** 3)

此处Exponentiative > MultiplicativeMultiplicative > 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 Graph(DAG)。查询两个运算符之间的优先级关系,等价于图中的**可达性(Reachability)**问题——这是该机制在算法层面的本质。

传递性检查:环路即编译错误

所有优先级关系必须是传递且无环的。定义A < BB < 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"。理由是:若允许这种拼接,开发者就能间接在标准库优先级组之间制造关系,破坏图的清晰度、误导读者。

特殊运算符:编译器硬编码

内置运算符isasas?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

文法变化

语言层面发生如下词法/文法调整:

  • 移除局部关键字assignmentprecedence
  • 新增关键字precedencegroup,以及局部关键字higherThanlowerThan

完整文法如下:

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只允许leftright(无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 附属宏 特别规定operatorprecedencegroup声明永远不能由宏生成,因为宏可能借此改写既有代码的优先级图,造成"看到宏展开的代码"与"未看到展开的代码"之间出现微妙的语义差异——这从侧面印证了优先级组声明在语言语义中的核心地位。

备选方案回顾

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/belowupper/lowergreaterThan/lessThanstrongerThan/weakerThangt/ltbefore/after;结合性关键字候选有associate;以及associativity(left)、逗号分隔属性、>/<中缀风格、precedencegroup与单行声明混合等十余种写法(详见提案的 Possible syntax variations 一节)。最终选定的precedencegroup+associativity:/higherThan:/lowerThan:大括号风格,兼顾了可读性、与 Swift 声明风格的统一性以及语法可扩展性。

总结

SE-0077 用一套简洁、可组合、可验证的声明式语法,彻底取代了 Swift 2.x 的数值优先级体系:

  • 组抽象:优先级从运算符身上剥离,成为可复用的precedencegroup,运算符通过: 组名挂靠;
  • 偏序取代全序:只有在组间显式声明(或由传递性推导出)关系时才能省略括号,从语言层面消灭了一整类优先级误写 bug;
  • 图论建模:组关系构成 DAG,环路检测保证一致性,可达性查询决定解析结果;
  • 模块安全的扩展性lowerThan允许跨模块在既有运算符之下插入新组,同时禁止通过传递性"拼接"两个无关的导入组;
  • 长期稳定:标准库的整套优先级层次从 Swift 3.0 沿用至今,成为后续所有语言特性(从字面量宏到附属宏)共同依赖的语义基石。

无论是为库定义新的自定义运算符,还是阅读 Swift 源码中的precedencegroup声明,理解 SE-0077 的设计动机与机制细节都是必不可少的。

延伸阅读:完整提案见 proposals/0077-operator-precedence.md;Swift 3.0 发布说明见 releases/swift-3_0.md;若想了解运算符声明与宏系统的边界约束,可参考 SE-0389 附属宏。

  • 文档

【免费下载链接】swift-evolution

This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.

项目地址:https://gitcode.com/gh_mirrors/sw/swift-evolution
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 6:10:44

Node.js与PostgreSQL企业级应用部署指南

1. 项目概述与部署准备"录用通知-自助系统"是一款基于Node.js和PostgreSQL开发的企业级应用&#xff0c;主要用于自动化生成和管理员工录用通知书。系统采用前后端分离架构&#xff0c;前端使用现代JavaScript框架&#xff08;如React或Vue&#xff09;&#xff0c;后…

作者头像 李华
网站建设 2026/9/23 6:09:37

Abaqus水力裂缝与天然裂缝相交的Cohesive行为模拟实战

做压裂模拟的人应该都遇到过这种场景&#xff1a;水力裂缝扩展几十步都好好的&#xff0c;一到天然裂缝附近&#xff0c;要么裂缝停在界面上一动不动&#xff0c;要么沿着天然裂缝疯狂拐弯&#xff0c;更气人的是模型有时直接不收敛。这个问题落到Abaqus里&#xff0c;核心就是…

作者头像 李华
网站建设 2026/9/23 6:07:48

域名系统解析:从根域名到子域名的实战指南

1. 互联网地址系统的基石&#xff1a;域名体系解析每次在浏览器地址栏输入"www.example.com"时&#xff0c;我们都在使用一套精密的全球寻址系统。这个看似简单的字符串背后&#xff0c;隐藏着互联网最基础也最精妙的设计之一——多级域名体系。就像现实世界的邮政地…

作者头像 李华
网站建设 2026/9/23 6:05:44

UNIAPP实现移动端后台持续录音的技术方案

1. 项目背景与核心价值去年接手一个语音社交APP项目时&#xff0c;我们遇到了一个棘手的技术难题&#xff1a;当用户切换到其他应用或锁屏后&#xff0c;录音功能就会自动中断。这个问题直接影响了用户的使用体验&#xff0c;特别是在需要长时间录音的场景下。经过多方调研和测…

作者头像 李华
网站建设 2026/9/23 6:04:06

微电网经济调度:风光储优化与Matlab实践

1. 微电网经济调度背景与挑战微电网作为分布式能源系统的重要形态&#xff0c;正在重塑传统电力供应的格局。我从事微电网优化调度研究已有七年时间&#xff0c;亲眼见证了从单纯追求供电可靠性到兼顾经济性和环保性的转变过程。风光储微电网的经济调度问题&#xff0c;本质上是…

作者头像 李华