ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Carbon 语言比较运算符(Comparison Operators)完全指南:运算符语义、隐式转换规则与 EqWith/OrderedWith 接口扩展

Carbon 语言比较运算符(Comparison Operators)完全指南:运算符语义、隐式转换规则与 EqWith/OrderedWith 接口扩展 Carbon 语言比较运算符Comparison Operators完全指南运算符语义、隐式转换规则与 EqWith/OrderedWith 接口扩展【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang导读本文以 Carbon 语言设计文档 docs/design/expressions/comparison_operators.md 为主体系统讲解 Carbon 中、!、、、、六个比较运算符的标准数学语义、优先级与结合性规则、内建类型整数/浮点/字符的隐式转换比较约束以及用户自定义类型如何通过标准库的EqWith、OrderedWith接口扩展比较行为。读完本文你将掌握 Carbon 比较运算的精确规则尤其是与 C 不同的数学正确优先策略、如何在impl中为自定义类型实现比较接口以及元组与数据类默认比较的字段级语义并了解 core/prelude/operators/comparison.carbon 等标准库源码中的真实接口定义。概述六个运算符与统一语义Carbon 提供相等与关系relational两类比较运算符每个运算符都有标准的数学含义类别运算符示例数学含义说明相等x y等于相等!x ! y≠不等于关系x y小于关系x y≤小于或等于关系x y大于关系x y≥大于或等于所有比较运算符都返回bool当比较成立时求值为true。它们全部是中缀二元运算符infix binary operators。这些运算符对部分 Carbon 内建类型 有预定义语义对结构体、元组这类简单的数据类型也有默认语义详见 基本类型与数据类型的默认实现。用户自定义类型可以通过实现标准库提供的接口来定义这些运算的含义详见 扩展性。从标准库源码看比较能力通过Core包的 prelude 统一导出core/prelude/operators.carbon 将prelude/operators/comparison库导出而比较接口与内建bool、IntLiteral的实现集中在 core/prelude/operators/comparison.carbon。运算符优先级Precedence所有比较运算符处于同一优先级层级。该层级低于用于计算非bool值的运算符如算术运算符、*高于逻辑运算符and和or参见 docs/design/expressions/logical_operators.md与not的优先级不可比较incomparable因此混用not与比较运算符会构成歧义。合法与非法示例// ✅ 合法优先级提供了求值顺序。 if (n m * 3 n * n and 3 m and m 6) { ... } // 上面等价于 if (((n (m * 3)) (n * n)) and ((3 m) and (m 6))) { ... } // ❌ 歧义而非法是 (not a) b 还是 not (a b) if (not a b) { ... } // ❌ 优先级非法请写成 a (not b)。 if (a not b) { ... } // ❌ 优先级非法请写成 not (f 5.0)。 if (not f 5.0) { ... }not与比较运算符优先级不可比较的设计意图在于not是单目运算符而比较是中缀运算符二者叠加时括号是必需的强制使用括号能消除阅读歧义这正是设计文档刻意保留的约束。结合性Associativity比较运算符是非结合non-associative的不允许链式书写// ❌ 非法请写成 3 m and m 6。 if (3 m 6) { ... } // ❌ 非法请写成 a b and b c。 if (a b c) { ... } // ❌ 非法请写成 (m 1) (n 1)。 if (m 1 n 1) { ... }如果允许a b c这类链式写法其语义究竟是(a b) c把bool与c再比较还是a (b c)存在本质歧义强制用户显式使用and连接两个比较使得多条件判断的意图一目了然。内建比较与隐式转换内建比较在以下四种情况下被允许两个操作数都是标准 Carbon 整数类型Int(n)或Unsigned(n)两个操作数都是标准 Carbon 浮点类型Float(n)一个操作数是浮点类型、另一个是整数类型且整数类型的所有取值都能在浮点类型中被精确表示两个操作数都是char或Core.CharLiteral类型。每种情况下结果都是数学上正确的答案——即便是在Int(n)与Unsigned(m)比较时也是如此。// ✅ 合法符合第 1 种情况。compared 的值为 true因为 a 小于 b // 尽管对回绕wrapping的 i32/u32 而言该比较结果为 false。 fn Compare(a: i32, b: u32) - bool { return a b; } let compared: bool Compare(-1, 4_000_000_000); // ❌ 非法不符合第 3 种情况因为 i64 的值总体上无法在 f32 中精确表示。 let float: f32 1.0e18; let integer: i64 1_000_000_000_000_000_000; let eq: bool float integer;注意上述Compare(-1, 4_000_000_000)示例的精髓按位级回绕语义-1与4_000_000_000在u32视角下反而是-1 4_000_000_000为假-1回绕后极大但 Carbon 选择数学语义结果强制为true。涉及整数与浮点常量的比较不受上述规则覆盖另行讨论见 与常量比较。与隐式转换的一致性Carbon 支持以下 隐式转换从Int(n)到Int(m)当m n时从Unsigned(n)到Int(m)或Unsigned(m)当m n时从Float(n)到Float(m)当m n时从Int(n)到Float(m)当Float(m)能表示Int(n)的全部取值时。这些规则可概括为类型T可以转换到U当且仅当T的每一个值都是U的一个值即转换是无损的、值保持的。此外某些整数常量与浮点常量在能被类型表示时可隐式转换到Int(n)与Float(n)。所有内建比较都可以被看作至多对其中一个操作数执行隐式转换从而得到一对相同或非常相近的类型再进行比较。目标类型组合对每个合适的宽度n为Int(n)与Int(n)Unsigned(n)与Unsigned(n)Int(n)与Unsigned(n)Unsigned(n)与Int(n)Float(n)与Float(n)一般而言存在多种隐式转换组合都能到达上述形式之一但无论选择哪一种结果都相同——因为所有比较都是数学正确的而所有隐式转换都是无损的。实现可以自由选择最高效的方式例如对u16 i32最可能的选择是把u16提升到i32而非u32。由于至多转换一个操作数Carbon绝不会使用比两个输入类型都大的中间类型。例如i32和f32都能隐式转换为f64但 Carbon 不允许i32与f32直接比较——即便可以在f64中完成该比较。如果允许结果可能出乎意料// i32 可以精确表示这个值。 var integer: i32 2_000_000_001; // 这个值在 f32 的可表示范围内但由于 f32 精度有限 // 它会被舍入为 2_000_000_000.0。 var float: f32 2_000_000_001.0; // ❌ 非法f32 无法精确表示 i32 的全部取值。 if (integer float) { ... } // ✅ 合法任一侧显式转换为 f64 后代码合法 // 但比较结果是不相等float 被舍入为 2_000_000_000.0 // 而 integer 会精确转换为 2_000_000_001.0。 if (integer float as f64) { ... } if (integer as f64 float) { ... }两种混合类型比较整数 vs 浮点、有符号 vs 无符号可能因定义域略宽而比同类比较 效率略低。这一设计明显背离 C 的做法C 会先把两个操作数统一转换为一个公共类型有时产生有损lossy转换从而得到错误结果有时转换两个操作数有时使用比两个操作数类型都更宽的类型。标准库中的实现证据设计文档中的规则在 prelude 中有直接的对应实现core/prelude/types/int.carbon 中Int(N)与Int(M)之间定义了final impl的EqWith/OrderedWith同时对任意T: ImplicitAs(Int(N))提供了左右两侧的implInt(N) as EqWith(T)与T as EqWith(Int(N))正是至多转换一个操作数机制的落地——由ImplicitAs完成单侧无损提升。core/prelude/types/uint.carbon 中UInt(N)与UInt(M)、UInt(N)与Int(M)、以及Int(N)与UInt(M)的交叉比较均有impl印证了Int(n)vsUnsigned(n)目标组合的存在同样以T: ImplicitAs(...)形式提供两侧的单向提升。core/prelude/types/float.carbon 目前只实现了Float(N)与Float(N)的同型比较源码中保留TODO: Support mixed-type comparisons注释说明设计文档中的混合整数-浮点比较规则仍在逐步落地中。core/prelude/types/char.carbon 中class Char { adapt u8; }即Char基于u8适配实现其比较通过int.eq/int.less等内建操作完成见 core/prelude/types/char.carbon并以CharLiteral as ImplicitAs(Char)core/prelude/types/char.carbon支持字符字面量的比较。内建bool与IntLiteral的比较实现在 core/prelude/operators/comparison.carbon 中通过 bool.eq、 int.eq等形式链接到编译器内建实现。与常量比较Carbon 允许以下涉及常量的比较常量可以与任意它能隐式转换到的类型的值比较任意两个常量之间都可以比较即使不存在能同时表示两者的类型。正如 docs/design/expressions/implicit_conversions.md 中所述整数常量可以隐式转换为任何能表示其值的整数或浮点类型浮点常量可以隐式转换为任何能表示其值的浮点类型。需要注意这条规则禁止了例如i32与无法在i32中表示的整数字面量的比较——这种比较恒为真tautological毫无意义。设计文档同时指出若该限制在实践如模板代码中字面量有时在范围内中造成问题应重新评估此决定。扩展性用户自定义类型可以通过实现接口来扩展比较运算符的行为。本节描述的各种性质是这类实现应当should满足的这些性质通常不会被强制检查但标准库在部分场景下可能检测到某些违反且 checked generic 代码可能假定这些性质成立——一旦违反将导致不可预期的行为。相等EqualityEqWith接口通过实现EqWith与OrderedWith接口可以为用户自定义类型提供比较运算符。EqWith接口用于定义给定一对类型上与!运算符的语义interface EqWith(U: type) { fn Equal(self, u: U) - bool; default fn NotEqual(self, u: U) - bool { return not (self u); } } constraint Eq { extend EqWith(Self); }给定x: T、y: U表达式x y调用x.(EqWith(U).Equal)(y)表达式x ! y调用x.(EqWith(U).NotEqual)(y)。标准库中的实际接口定义core/prelude/operators/comparison.carbon与设计一致唯一的差异是当前实现中NotEqual尚未声明为default且接口头部的Eq/Ordered命名约束以 TODO 形式标注待补——从源码结构看prelude 版本比设计文档更早期或精简属于实现逐步对齐设计的过程。自定义类的相等实现示例class Path { private var drive: String; private var path: String; private fn CanonicalPath(self) - String; impl as Eq { fn Equal(self, other: Self) - bool { return (self.drive, self.CanonicalPath()) (other.drive, other.CanonicalPath()); } } }这里Path通过元组把drive与规范化路径CanonicalPath()组合比较实现语义上的路径相等而非字符串形式的原始相等。使用like允许隐式转换EqWith的重载选择不考虑可能的隐式转换。若希望在重载的操作数中允许隐式转换可以使用like运算符class MyInt { var value: i32; fn Value(self) - i32 { return self.value; } } impl i32 as ImplicitAs(MyInt); impl like MyInt as EqWith(like MyInt) { fn Equal(self, other: Self) - bool { return self.Value() other.Value(); } } fn CompareBothWays(a: MyInt, b: i32, c: MyInt) - bool { // OK上面这个实现会被调用三次。 return a a and a ! b and b c; }like让EqWith(like MyInt)同时覆盖了MyInt与其可隐式转换来源类型此处是i32的组合a ! b与b c才得以成立。覆盖NotEqual与 NaN 场景NotEqual的行为可以独立于Equal被覆盖以支持浮点 NaN 这类既不相等、也不不相等的值——两个值比较时两个函数都返回falseimpl like MyFloat as EqWith(like MyFloat) { fn Equal(self: MyFloat, other: MyFloat) - bool { if (self.IsNaN() or other.IsNaN()) { return false; } return self.Representation() other.Representation(); } fn NotEqual(self: MyFloat, other: MyFloat) - bool { if (self.IsNaN() or other.IsNaN()) { return false; } return self.Representation() ! other.Representation(); } }但EqWith的实现不应让Equal与NotEqual对同一对值同时返回true此外这些操作不应有可观察的副作用。异构比较需双向定义异构heterogeneous比较必须双向定义impl like MyInt as EqWith(like MyFloat); impl like MyFloat as EqWith(like MyInt);TODO标准库应提供一个适配器adapter以简化反向比较的定义。排序OrderingOrderedWith接口OrderedWith接口用于定义给定一对类型上、、、四个运算符的语义choice Ordering { Less, Equivalent, Greater, Incomparable } interface OrderedWith(U: type) { fn Compare(self, u: U) - Ordering; default fn Less(self, u: U) - bool { return self.Compare(u) Ordering.Less; } default fn LessOrEquivalent(self, u: U) - bool { let c: Ordering self.Compare(u); return c Ordering.Less or c Ordering.Equivalent; } default fn Greater(self, u: U) - bool { return self.Compare(u) Ordering.Greater; } default fn GreaterOrEquivalent(self, u: U) - bool { let c: Ordering self.Compare(u); return c Ordering.Greater or c Ordering.Equivalent; } } constraint Ordered { extend OrderedWith(Self); } // Ordering.Less Ordering.Equivalent Ordering.Greater。 // Ordering.Incomparable 与三者都不可比较。 impl Ordering as Ordered;TODO在枚举类型的具体设计确定后修订上述定义。给定x: T、y: U表达式x y调用x.(OrderedWith(U).Less)(y)表达式x y调用x.(OrderedWith(U).LessOrEquivalent)(y)表达式x y调用x.(OrderedWith(U).Greater)(y)表达式x y调用x.(OrderedWith(U).GreaterOrEquivalent)(y)。一个按面积排序自定义类的完整示例class MyWidget { var width: i32; var height: i32; fn Size(self) - i32 { return self.width * self.height; } // 控件默认按尺寸排序。 impl as Ordered { fn Compare(self, other: Self) - Ordering { return self.Size().(Ordered.Compare)(other.Size()); } } } fn F(a: MyWidget, b: MyWidget) - bool { return a b; }这里MyWidget只需实现一个Compare四个关系运算符均由默认实现派生self.Size().(Ordered.Compare)(other.Size())复用了内建Int的排序比较。与EqWith一样可以使用like运算符 允许比较调用时的隐式转换且异构比较必须双向定义fn ReverseOrdering(o: Ordering) - Ordering { return Ordering.Equivalent.(Ordered.Compare)(o); } impl like MyInt as OrderedWith(like MyFloat); impl like MyFloat as OrderedWith(like MyInt) { fn Compare(self, other: Self) - Ordering { return Reverse(other.(OrderedWith(Self).Compare)(self)); } }反向实现中MyFloat一侧通过Reverse把MyInt侧的Compare结果反转从而保持排序的一致性。可覆盖的默认实现与传递性Less、LessOrEquivalent、Greater、GreaterOrEquivalent的默认实现可以被覆盖例如实现一个更高效的版本。此类覆盖的行为应遵循默认实现的语义且OrderedWith实现的成员不应有可观察的副作用。OrderedWith实现应当是可传递的transitive。给定V: type、U: OrderedWith(V)、T: OrderedWith(U) OrderedWith(V)、a: T、b: U、c: V则若a b且b c则a c进一步地若a b或b c任一成立则a c若a b且b c则a c进一步地若a b或b c任一成立则a c若a与b等价则a.Compare(c) b.Compare(c)同理若b与c等价则a.Compare(b) a.Compare(c)。OrderedWith实现还应当在反转下保持一致consistent under reversal。即给定类型T与U其中T impls OrderedWith(U)且U impls OrderedWith(T)以及值a: T、b: U若a.(OrderedWith.Compare)(b)是Ordering.Greater则b.(OrderedWith.Compare)(a)是Ordering.Less反之亦然否则a.(OrderedWith.Compare)(b)与b.(OrderedWith.Compare)(a)返回相同值。值得注意的是并不要求Ordered实现是全序、弱序或偏序。特别是浮点类型的实现三者都不是——因为 NaN 与自身既不小于、也不等价NaN NaN与NaN NaN均为假。TODO标准库应提供一种方式在 checked generic 中声明某个排序是弱序、偏序或全序并支持请求这样的排序。相等与排序的兼容性并不要求实现OrderedWith的类型对也必须实现EqWith。但如果一对类型两者都实现了那么x.(EqWith.Equal)(y)提供的相等关系应当是x.(OrderedWith.Compare)(y) Ordering.Equivalent所提供等价关系的细化refinement——即相等的值必然等价但等价的值未必相等例如排序忽略的次要字段。自定义结果类型TODO设计文档计划支持一种更低层的扩展机制允许比较结果类型不是bool例如返回Ordering或自定义枚举的三路比较。基本类型与数据类型的默认实现除标准 Carbon 数值类型外相等与关系比较对所有数据data类型也有定义元组Tuples结构体类型Struct types实现了标识其为数据类的接口的类这些类型的关系比较提供字典序lexicographical排序。每种情况下仅当所有元素类型都支持该比较时比较才可用。由于数据类之间的隐式转换可能重排字段field reordering数据类的默认实现在一般情形下不允许对参数做隐式转换取而代之相等比较允许任何两个具有相同无序字段名集合unordered set of field names的数据类进行比较只要每对对应字段都有EqWith实现。字段按左操作数中的出现顺序比较。关系比较允许任何两个具有相同有序字段名序列ordered sequence of field names的数据类进行比较只要每对对应字段都有OrderedWith实现。字段按顺序比较。也就是说相等只看字段集合是否相同不关心字段顺序而排序要求字段顺序也一致。这与设计上相等是排序等价的细化的理念一脉相承——重排字段不改变相等性但会改变字典序。元组之间的比较则允许任一侧但不能两侧同时的隐式转换。开放问题Open questionsbool类型应被视为一种 choice 类型因此当且仅当 choice 类型普遍支持相等与关系比较时bool才应支持这些比较。该决定留给未来的提案处理。从当前源码看bool的比较已先行实现在 core/prelude/operators/comparison.carbonimpl bool as EqWith(Self)但关系比较尚未实现与留给未来提案的开放状态一致。备选方案Alternatives considered设计过程中曾考虑并被否决或推迟的备选方案详见对应提案备选符号Alternative symbols链式比较Chained comparisons像 C 一样转换操作数Convert operands like C提供三路比较运算符Three-way comparison operator允许比较作为not的操作数Allow comparisons as the operand ofnot将OrderedWith改名为ComparableWith其中链式比较被否决直接对应本文非结合一节像 C 一样转换操作数被否决对应至多转换一个操作数、杜绝有损中间类型一节而三路比较与自定义结果类型则是文档中TODO标注的未来方向。参考资料提案 #702Comparison operators本文设计文档的提案来源提案 #1178Rework operator interfaces重命名/重构运算符接口数据类默认比较相关 Issue #710Default comparison for data classes见设计文档 References 一节提案 #6710charredesignchar类型重构对应Core.CharLiteral比较规则标准库实现core/prelude/operators/comparison.carbon、core/prelude/types/int.carbon、core/prelude/types/uint.carbon、core/prelude/types/float.carbon、core/prelude/types/char.carbon相关设计隐式转换、逻辑运算符、元组、类与结构体、泛型like运算符提示Carbon 语言仍处于实验阶段参见仓库 README.md上述设计文档与标准库实现正在同步演进中源码中标注的多个TODO如OrderedWith的Compare成员、混合浮点比较、Eq/Ordered命名约束即体现了这一点实际行为请以当前仓库代码为准。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表