Skip to content

运行时量纲检查

LunarUnits 的核心选择是:先把量纲检查做成运行时模型,而不是一开始追求编译期量纲类型系统。

问题

单位错误常常发生在数据进入程序之后:命令行参数、配置文件、表单输入、CSV、JSON、公式参数、数据库字段。对于这些动态输入,编译器无法直接知道用户写下的 9.8 m/s^22 kg20 degC 是否合理。

如果库只提供裸 Double,调用方很容易把“数值正确但单位错误”的数据传进公式。这样的错误不会崩溃,却会产生看起来很正常的错误结果。

数学视角

量纲检查可以看作在运行时维护一组代数不变量。加法、减法和换算不是对任意两个数量都成立的全局操作,而是只在同一量纲上有定义的部分操作:

text
add : Quantity[D] x Quantity[D] -> Quantity[D]
sub : Quantity[D] x Quantity[D] -> Quantity[D]
to  : Quantity[D] x Un[D]       -> Quantity[D]

乘法、除法和整数幂则负责生成新的量纲。它们对应量纲向量的加法、减法和倍乘:

text
mul : Quantity[D1] x Quantity[D2] -> Quantity[D1 + D2]
div : Quantity[D1] x Quantity[D2] -> Quantity[D1 - D2]
pow : Quantity[D]^n               -> Quantity[nD]

因此,运行时检查不是给普通数字额外加一层标签,而是在动态输入进入系统之后,继续保证“哪些运算有定义、结果落在哪个量纲上”这些规则不被破坏。

选择

LunarUnits 让数值携带单位,并在运行时检查加法、减法和换算的量纲兼容性。乘法、除法和整数幂负责组合量纲,而不是把所有计算都退化成普通数字。

这让库可以覆盖两类场景:

  • 静态代码中的工程计算,例如力、功率、能量、速度。
  • 动态输入驱动的工具,例如单位换算 CLI、公式计算 CLI 和 Web UI。

避免的错误

运行时检查可以阻止这类问题:

  • 把长度加到时间上。
  • 把质量直接换算成温度。
  • 把公式输入单位改成 g 后,数值没有归一化却继续参与计算。
  • 应用层只拼接单位字符串,结果显示看起来有单位,实际计算却没有单位语义。

代价

运行时检查不能在编译期阻止所有错误。调用方仍然可能在更高层把量纲相同、但业务角色不同的值混用。例如两个长度都能通过量纲检查,但“管道长度”和“海拔高度”仍然不应在领域逻辑中互相替代。

这也是 LunarUnits 的边界:它保证量纲和单位换算正确,不替代领域建模。业务语义不同但量纲相同的概念,应由变量名、函数边界、领域类型或公式定义继续表达。对于力矩和能量这类依赖角度语义的概念,本库选择把 angle 建成扩展维度,使二者在量纲层面也能被区分。

Quantity 与“只在变量名里写单位”的差异也在这里:单位不是注释,而是参与运行时检查的数据。只要计算还停留在 Quantity 中,调用方就可以继续读取数值、单位和量纲,并让库负责兼容性检查;一旦过早退回裸数值,这些信息就需要调用方自己维护。

为什么不是只靠类型系统

编译期量纲类型系统很有吸引力,但它对语言能力、泛型表达、错误信息和库易用性要求更高。LunarUnits 的目标是先提供一套可运行、可发布、可被 CLI/Web UI 复用的单位语义基础,再在长期维护中评估更强的静态能力。