尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Carbon 语言 `if` 表达式设计详解:语法、求值语义与公共类型推断机制

Carbon 语言 `if` 表达式设计详解:语法、求值语义与公共类型推断机制 Carbon 语言if表达式设计详解语法、求值语义与公共类型推断机制【免费下载链接】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导读if表达式ifexpression是 Carbon 语言中把条件分支值化的核心构造它以if _condition_ then _value1_ else _value2_的形式直接产生一个值从而让条件逻辑可以出现在变量初始化、return、函数实参等任何需要表达式的位置。本文以设计文档 docs/design/expressions/if.md 为骨架结合编译器检查check阶段的实现与测试数据完整讲解if表达式的语法约束、求值语义以及其背后用于统一两个分支类型的CommonType/CommonTypeWith泛型机制帮助读者理解这一特性从语言设计到工具链落地的完整脉络。概览if表达式的基本形态一个if表达式具有如下形式ifconditionthenvalue1elsevalue2其中condition条件会按照与if语句条件相同的方式转换为bool值。设计文档明确注明这些转换规则目前尚未最终确定属于待定设计。value1与value2会被隐式转换为它们的 公共类型common type而该公共类型就是整个if表达式的类型。换句话说if表达式的结果类型不是由某一个分支单独决定的而是由两个分支值经过求公共类型这一过程共同确定的。这是它与传统 C 系三目运算符cond ? a : b在设计理念上的关键差异Carbon 将两个类型合并成什么类型这一决策显式建模为可扩展的泛型约束而不是硬编码在类型检查器里。语法低优先级与上下文限制if表达式具有非常低的优先级且不能作为任何运算符的操作数——唯一的例外是可以作为赋值语句的右侧操作数。在其它允许表达式出现的位置它都可以使用例如括号内作为return语句的操作数作为变量初始化器在逗号分隔的列表中例如函数调用的实参。value1与value2本身是任意表达式也可以再是if表达式即可嵌套。value2会尽可能向右延伸如果希望value2提前结束则需要使用括号把内层if表达式括起来。设计文档给出了两个对比示例// OK等价于 if cond then (1 1) else (2 (4 * 6)) var a: i32 if cond then 1 1 else 2 4 * 6; // OK括号提前终止了 else 分支的延伸 var b: i32 (if cond then 1 1 else 2) 4 * 6;第一个例子中由于else分支尽可能向右延伸2 4 * 6整体都属于value2第二个例子通过括号把if表达式整体括起来使其作为的左操作数从而验证了if表达式不能直接作为运算符操作数的规则——一旦需要参与运算就必须加括号。另一个重要的语法消歧规则是语句开头的if关键字总是被解释为if语句绝不会被解释为if表达式即使后面跟了then关键字。这保证了语句级条件控制流见 docs/design/control_flow/conditionals.md与表达式级条件值化两种形态之间不会产生歧义。if语句要求条件带括号并使用大括号语句块if (fruit.IsYellow()) { Print(Banana!); } else if (fruit.IsOrange()) { Print(Orange!); } else { Print(Vegetable!); }求值语义惰性求值与短路if表达式的求值语义十分直接求值已转换的condition若条件为true求值已转换的value1其结果即为整个表达式的结果否则求值已转换的value2其结果即为整个表达式的结果。这意味着if表达式是惰性/短路的只有被选中的分支才会被求值未被选中的分支表达式不会产生任何运行时副作用。这与 C/C 中cond ? a : b只求值一个分支的行为一致也是它能够安全替代语句式if去初始化值的前提。求公共类型CommonType与CommonTypeWithif表达式类型检查的核心问题是T与U两种类型的公共类型是什么设计文档给出的定义是类型T与U的公共类型为(T as CommonType(U)).Result其中CommonType是Carbon.CommonType约束。CommonType的概念性定义如下实际定义因对称性处理而更复杂见下文constraint CommonType(U: CommonTypeWith(Self)) { extend CommonTypeWith(U) where .Result U.Result; }用于定制CommonType行为的关键接口是CommonTypeWithinterface CommonTypeWith(U: type) { let Result: type where Self impls ImplicitAs(.Self) and U impls ImplicitAs(.Self); }实现A as CommonTypeWith(B)表示A希望当A与B被合并时结果的类型是它的Result关联类型。文档特别强调两点两个类型都必须能隐式转换ImplicitAs到公共类型这是CommonTypeWith中where约束的语义prelude标准前奏库中提供了一些CommonTypeWith的 blanketimpl声明用于覆盖常见情况。设计文档还指出这一机制未来预计还会被复用到其它需要计算公共类型的场景中例如其它二元运算因此值得深入理解。对称性SymmetricCommonTypeWith公共类型必须满足对称性T与U的公共类型应当总是等于U与T的公共类型。这一性质分两步强制接口SymmetricCommonTypeWith隐式提供反向实现当不存在B as CommonTypeWith(A)实现、但存在A as CommonTypeWith(B)实现时自动补全反向实现CommonType基于SymmetricCommonTypeWith定义并要求A as SymmetricCommonTypeWith(B)与B as SymmetricCommonTypeWith(A)产生相同的类型。SymmetricCommonTypeWith是CommonType约束的实现细节其定义与实现如下interface SymmetricCommonTypeWith(U: type) { let Result: type where Self impls ImplicitAs(.Self) and U impls ImplicitAs(.Self); } match_first { impl forall [T: type, U: CommonTypeWith(T)] T as SymmetricCommonTypeWith(U) where .Result U.Result {} impl forall [U: type, T: CommonTypeWith(U)] T as SymmetricCommonTypeWith(U) where .Result T.Result {} }注意match_first语义与impl顺序有关第一条impl中U受CommonTypeWith(T)约束、取U.Result第二条中T受CommonTypeWith(U)约束、取T.Result。因为SymmetricCommonTypeWith不对外导出用户无法声明自己的实现只会有上述两个 blanketimpl生效。随后CommonType约束定义为constraint CommonType(U: SymmetricCommonTypeWith(Self)) { extend SymmetricCommonTypeWith(U) where .Result U.Result; }由此得到公共类型的判定规则若只有一方提供了CommonTypeWith实现则该实现决定公共类型若双方都提供且Result相同则它决定公共类型若双方都提供但Result不同则不存在公共类型CommonType约束不满足。设计文档给出了失败示例若声明MyX as CommonTypeWith(T).Result MyX与MyY as CommonTypeWith(T).Result MyY两个 blanket 实现那么MyX as CommonTypeWith(MyY)会选中 #1、MyY as CommonTypeWith(MyX)会选中 #2但由于结果类型不同MyX as CommonType(MyY)的约束不满足——这正是对称性检查发挥作用的地方。同类型final impl的特化如果T与U是同一类型公共类型就是该类型本身final impl forall [T: type] T as CommonTypeWith(T) where .Result T {}该规则在设计上被认为比文档中其它规则更特化。由于该impl声明为finalT.(CommonType(T)).Result总是被假定为T——即使在T涉及 checked binding、结果类型本应为 facet type 为type的未知类型的上下文中也是如此。这意味着编译器可以在泛型函数中直接使用if表达式的结果而无需额外的类型信息例如fn FT: Hashable - HashCode { // OKif 表达式的类型是 T。 return (if c then x else y).Hash(); }这里x、y都是受Hashable约束的泛型类型参数Tif表达式的类型被确定为T因此可以直接调用.Hash()。这一点在 docs/design/generics/details.md 的泛型细节讨论中也有呼应——设计文档记录了一个被否决的备选方案如果不用final且允许T as CommonTypeWith(T)隐式可用那么面对T: Potato与U: Hashable where .Self T这类场景将无法确定T.CommonTypeWith(U).Result到底是T还是U导致 impl 校验失败。隐式转换单向转换取目标类型如果T可以隐式转换为U那么公共类型就是Uimpl forall [T: type, U: ImplicitAs(T)] T as CommonTypeWith(U) where .Result T {}注意如果两个方向都能隐式转换即T与U互相可转换且没有更特化的实现存在那么T as CommonType(U)的约束将不满足——因为(T as CommonTypeWith(U)).Result与(U as CommonTypeWith(T)).Result会不同。要为这种情况定义公共类型必须在两个方向都提供CommonTypeWith实现以覆盖两个方向的 blanketimplimpl MyString as CommonTypeWith(YourString) where .Result MyString {} impl YourString as CommonTypeWith(MyString) where .Result MyString {} var my_string: MyString; var your_string: YourString; // also_my_string 的类型是 MyString。 var also_my_string: auto if cond then my_string else your_string;这个示例展示了设计的一个重要实践当用户自定义类型之间存在双向隐式转换时默认的 blanketimpl无法给出确定的公共类型用户必须显式声明两个方向的CommonTypeWith实现来钦定合并结果本例统一收敛到MyString从而让if表达式得以通过类型检查。工具链实现从设计到编译器设计文档中的机制在 Carbon 工具链的检查check阶段有直接对应实现核心代码位于 toolchain/check/handle_if_expr.cpp。该文件按解析节点分三个阶段处理if表达式IfExprIf遇到if关键字弹出条件表达式调用ConvertToBoolValue将条件转换为bool对应设计中条件按if语句方式转换为bool然后通过AddDominatedBlockAndBranchIf/AddDominatedBlockAndBranch创建then与else两个基本块并把控制流划分到对应指令块IfExprThen遇到then关键字弹出then分支值先ConvertToValueExpr转为值语义再调用DecayIntLiteralToSizedInt将IntLiteral字面量转换为有符号i32实现注释说明目前总是选i32随后切换到else块继续生成指令IfExprElse遇到else关键字弹出else值将else值转换为then值类型ConvertToValueOfType再通过AddConvergenceBlockWithArgAndPush创建汇合块把两个分支值作为block_arg汇聚得到整个表达式的结果值。值得特别指出的是 handle_if_expr.cpp 中的一行 TODO 注释// TODO: Find a common type, and convert both operands to it instead.这说明当前工具链实现尚未完整落地设计文档中的CommonType公共类型推断现阶段采用的是把else分支转换为then分支类型的简化策略设计文档中描述的完整CommonType/CommonTypeWith机制属于目标设计。阅读源码时需要注意设计蓝图与当前实现之间的这一差距这也是实验性语言演进过程中的正常状态。测试验证控制流与求值行为if表达式在检查阶段有专门的测试数据目录 toolchain/check/testdata/if_expr/覆盖多种场景basic.carbon基础用例演示if表达式作为return操作数其转储的 SEM IR 清晰地展示了三个基本块!if.expr.then、!if.expr.else与!if.expr.result其中then/else块分别以参数形式把各自的值br到汇合块!if.expr.result再由block_arg读取——这与HandleParseNode(IfExprElseId)中AddConvergenceBlockWithArgAndPush的实现完全对应nested.carbon嵌套用例验证if a then if b then 1 else 2 else if c then 3 else 4这种else分支尽可能右延伸的嵌套结构SEM IR 中可以看到多层!if.expr.then/!if.expr.else/!if.expr.result基本块的嵌套与逐层汇聚control_flow.carbon、constant_condition.carbon、struct.carbon 等分别覆盖控制流、常量条件与结构体值场景fail_not_in_function.carbon 对应HandleParseNode(IfExprElseId)中控制流表达式当前仅支持出现在函数内部的检查fail_partial_constant.carbon 覆盖部分常量求值的错误路径。测试文件头部还给出了运行方式例如单独运行基础用例bazel test //toolchain/testing:file_test --test_arg--file_teststoolchain/check/testdata/if_expr/basic.carbon若要转储输出可运行bazel run //toolchain/testing:file_test -- --dump_output --file_teststoolchain/check/testdata/if_expr/basic.carbon设计备选方案回顾设计文档在 Alternatives considered 一节记录了提案 p000911-conditional-expressions.md 中讨论过并最终放弃的若干备选方案理解这些方案有助于把握最终设计的取舍完全不提供条件表达式#no-conditional-expression采用 C/C 风格的cond ? expr1 : expr2语法#use-c-syntax采用无then关键字的if (cond) expr1 else expr2语法#no-then要求条件必须加括号的if (cond) then expr1 else expr2语法#require-parentheses-around-the-condition允许1 if cond then expr1 else expr2这种无需括号、if表达式直接作为运算符操作数的写法#never-require-enclosing-parentheses当两个方向都存在隐式转换时只要求一个impl指定公共类型#implicit-conversions-in-both-directions为左值lvalue条件表达式引入特殊规则#support-lvalue-conditionals。最终设计选择了if ... then ... else ... 括号化 CommonType双向约束的组合其语法归属提案 Conditional expressions #911。小结if表达式把分支与取值合二为一其语法上以极低优先级与括号规则保证可组合性语义上以短路求值保证安全性类型上则以CommonType/CommonTypeWith泛型约束将两个类型的公共类型这一看似简单的问题提升为一等公民式的可扩展机制。阅读本文后你可以在 Carbon 代码中正确书写与括号化if表达式区分它与if语句的语法边界理解两个分支类型如何通过CommonTypeWith、SymmetricCommonTypeWith与隐式转换规则统一为公共类型以及双向转换冲突时的应对方式对照 toolchain/check/handle_if_expr.cpp 与 toolchain/check/testdata/if_expr/ 了解该特性从设计到工具链落地的现状与待办差距。参考本文主体设计文档 docs/design/expressions/if.md相关设计docs/design/control_flow/conditionals.mdif语句、docs/design/generics/details.mdCommonTypeWith泛型细节提案proposals/p000911-conditional-expressions.mdConditional expressions实现toolchain/check/handle_if_expr.cpp测试toolchain/check/testdata/if_expr/【免费下载链接】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),仅供参考
返回列表