Files
endorphin-tabularium-docs/content/docs/language-design/06-questions.md
T
codex-bot ef1ba88161
Verify and deploy Tabularium / verify-and-deploy (push) Waiting to run
Add language design question collection with discussion decisions and provenance
2026-10-28 14:02:12 +08:00

8.0 KiB
Raw Blame History

06 · 表达式与运算

0/20 项已确认。当前推进 06-01;本分类尚未完成选择。

本页整理原讨论的问题、验收要求和候选方案。勾选代表讨论中已确认的规则,不代表编译器或运行时已经实现。

06-01 普通操作数的求值顺序

  • 待讨论

明确二元运算、嵌套表达式的求值顺序,并确定是否采用一条适用于调用参数等表达式的共同规则;用有副作用的例子验收。

06-02 求值失败与中断

  • 待讨论

明确某一步发生错误或不终止时,后续哪些步骤还会执行;说明副作用是否保留。依赖【错误处理】。

06-03 运算符清单与分组规则

  • 待讨论

列明本轮支持的运算符、优先级、结合性,以及是否允许连续比较;用存在歧义的表达式验收。与【语法】核对。

06-04 数值运算的类型组合与转换

  • 待讨论

确定哪些数值组合可运算、是否隐式转换、结果类型,以及不允许组合的失败方式。依赖【类型系统】。

06-05 算术结果的具体含义

  • 待讨论

明确支持的加减乘除、余数及其他算术规则,覆盖负数、零、除零、舍入和精度。

06-06 溢出与特殊数值

  • 待讨论

确定整数越界、浮点溢出、无穷、NaN、正负零等情况是否存在,以及相应行为。依赖【数值类型】。

06-07 同类型值的相等

  • 待讨论

确定相等比较的含义、结果要求,以及复合值按结构还是其他方式比较;说明是否允许违反自反性等性质。

06-08 跨类型相等

  • 待讨论

确定不同类型的值能否相等、是否转换,以及无法比较时返回假还是失败;与数值转换规则核对。

06-09 身份比较

  • 待讨论

决定是否提供身份判断、哪些值具有身份,以及复制、共享对身份的影响。依赖【对象与类型模型】【内存与生命周期】。

06-10 大小比较

  • 待讨论

确定可比较的类型、全序或偏序、不可比较时的行为,以及大小比较与相等的关系。

06-11 哈希及其约束

  • 待讨论

决定哪些值可哈希,明确相等与哈希一致性的要求,以及可变值和自定义相等的影响。与【集合与容器】核对。

06-12 条件位置接受什么值

  • 待讨论

确定条件必须为布尔值还是允许其他值,并说明转换规则和失败行为。与【控制流】【类型系统】核对。

06-13 逻辑运算与短路

  • 待讨论

确定逻辑运算符、是否跳过右侧、结果为布尔值还是操作数,以及能否自定义这些行为。

06-14 位运算与移位

  • 待讨论

决定是否支持;若支持,明确适用类型、位宽、负数、右移和非法移位量的行为。

06-15 运算扩展的声明方式

  • 待讨论

决定用户能否定义或重载运算,以及通过函数、协议、类型成员或其他机制声明。依赖【类型系统】【定制协议】。

06-16 运算实现的选择规则

  • 待讨论

若允许扩展,确定左右操作数、类型匹配、转换和候选优先级,以及歧义的处理;若不允许扩展,记录内置选择规则。

06-17 不支持操作时的行为

  • 待讨论

明确“不支持”、运算内部出错、存在歧义的区别,以及编译期和运行期分别如何处理。依赖【类型系统】【错误处理】。

06-18 复合赋值与修改语义

  • 待讨论

决定是否支持 += 等形式;若支持,明确目标求值次数、是否原地修改、失败后的状态。依赖【变量与赋值】【对象与类型模型】。

06-19 常量求值与优化边界

  • 待讨论

明确常量表达式和普通表达式是否遵守相同结果、错误与精度规则,以及优化必须保留哪些可观察行为。依赖【执行模型】。

06-20 跨分类一致性验收

  • 待讨论

核对上述规则与相关分类的最终决定;通过包含副作用、混合数值、不可比较值、哈希、自定义运算和失败路径的代表性例子,解决所有冲突。

当前问题的方案与语言对照

以下沿用原讨论材料;建议和参考语言的做法均不自动成为本语言规则。

当前仍是 06-01:普通操作数的求值顺序。要决定的是:一个表达式包含多个计算步骤时,语言保证怎样的先后顺序。

例如下面的伪代码,假设各函数都会记录自己被调用了:

a() + b() * c()

“先乘后加”决定运算分组;它并不意味着必须先调用 b()。我们讨论的是这些函数以及普通变量读取的先后顺序。

相关语言的对照如下。这里说的是语言规则,后面的取舍分析不代表其历史设计动机。

语言 规则与具体行为 对我们设计的启发与取舍
Python 3 普通表达式从左到右求值。上例依次调用 a、b、c,再完成乘法、加法。赋值另有规则:先求右侧,再处理左侧目标。语言参考 顺序容易预测;实现需要保留可观察行为。也提醒我们应明确赋值等特殊结构的边界。
JavaScript / TypeScript JS 的普通算术表达式先求左操作数,再求右操作数;上例也是 a、b、c。TS 对这类表达式提供静态检查,运行时遵循生成的 JS 的规则,类型标注不会建立另一套求值顺序。ECMAScript 规范、TS 文档 类型检查与求值顺序可以分别设计;无论选择静态还是动态类型,都可以保证从左到右。
Rust 普通算术、调用参数、元组元素等规定按源码从左到右求值,上例也是 a、b、c。赋值和复合赋值另有专门规则。Rust Reference、赋值规则 明确顺序与编译优化可以并存;需要谨慎控制特殊规则,否则使用者记忆负担会上升。
Go 采用部分顺序保证:函数调用、方法调用、接收操作等按词法从左到右,但并非所有普通读取都固定顺序。所以上例调用仍是 a、b、c,但下面的例子可能有两个结果。Go 规范 可以只固定部分操作的顺序,给其他求值留下自由;代价是“从左到右”的直觉不足以解释所有表达式。

Go 的区别可以用这个例子看出来:

a := 1
f := func() int { a++; return a }
x := []int{a, f()}

规范允许 x 是 [1, 2] 或 [2, 2]:普通读取 a 与调用 f() 之间的顺序没有固定。这说明,仅保证函数调用从左到右,比保证所有普通操作数从左到右更弱。Go 规范中的示例

据此,我们有三个可选方案:

  • A:普通操作数统一从左到右。 变量读取、函数调用等都遵守;逻辑短路、赋值等结构单独明确。最容易推理。
  • B:普通操作数顺序不作保证。 必要的先后依赖仍须遵守;带副作用的表达式可能允许多个结果,实现选择空间更大。
  • C:部分操作保证从左到右。 类似 Go,固定调用等操作的顺序,允许其他求值顺序不确定。折中,但规范和使用都更复杂。

我建议 A,原因是它能为后续错误传播、重载和复合表达式提供清晰基础;不能观察到差别的优化仍可进行。错误发生后的传播规则留在 06-02,短路留在 06-13,赋值与复合赋值的顺序另行核对。

你选择 A、B,还是 C?

讨论来源

《语言设计 06:表达式与运算》,整理日期 2026-10-28。章节编号沿用原清单。