在 F# 中,单位类型(Units of Measure) 是一种用于在类型层面标注物理单位的语言特性,能够显著提升代码的安全性可读性

这项特性的核心优势在于能够防止物理单位混用引发的错误,例如:

  • 不小心将“千克”和“斤”混用
  • NASA 火星气候轨道器的灾难性事故:混淆了英制单位(磅力)和公制单位(牛顿),导致经过近 10 个月的长途跋涉后,上亿美元的探测器在接近火星时解体
  • 加拿大航空著名的“吉姆利滑翔机”事件:飞机的系统按公斤计算燃油,而地勤人员却以为单位加油,结果飞机只加了一半燃料,不得不在空中滑翔迫降

在多数编程语言中,物理单位往往只能通过变量命名、注释或开发规范来维持一致性,编译器无法自动检查单位是否匹配。而一些语言虽然也能通过复杂的模式来模拟单位系统,例如:

  • Go 使用类型别名
  • Rust 借助 New Type 与操作符重载
  • C++ 结合强类型定义、自定义字面量和操作符重载

但这些方法都不够简洁直观。

F# 的单位类型特性则提供了一种新写法,来避免单位混用问题:只要在整数、浮点数这些基础类型的值上标注了单位,编译器就会在执行不合理的计算(比如“1 米 + 1 英尺”)时提前报错,将 bug 扼杀在编译期。

下面,我们将以一个“华氏温度转换为摄氏温度”的简单示例,来领略一下 F# 中单位类型的魅力。

[<Measure>]
type C // 定义一个“单位”:C 表示摄氏度(Celsius/Centigrade)

[<Measure>]
type F // 定义另一个“单位”:F 表示华氏度(Fahrenheit)

// FtoC 函数:把带单位的华氏温度 float<F> 转换为摄氏温度 float<C>
let FtoC (temp: float<F>) : float<C> =
    // 华氏转摄氏公式: (F - 32) × 5/9
    // 注意:我们使用 32.0<F> 表示数值 32,单位是华氏度
    // 1.0<C / F> 是转换结果的单位:从 F 到 C
    5.0 / 9.0 * (temp - 32.0<F>) * 1.0<C / F>

// toF 函数:将一个普通的 float 数值转为带单位的 float<F>
let toF (temp: float) : float<F> = temp * 1.0<F>

// 示例变量 x 是一个普通浮点数,代表华氏温度 100°F
let x = 100.0

// 把 x 转为带单位的 float<F>  → 调用 FtoC 转为摄氏度 → 除以 1.0<C> 去掉单位用于显示
printfn "%.2f°F = %.2f°C" x (FtoC(toF x) / 1.0<C>)

运行结果

100.00°F = 37.78°C

在代码中标注物理单位,乍看之下或许只是“锦上添花”。但当你目睹单位混用如何导致探测器解体、飞机迫降、算法出错时,就会明白——写在文档里的“规范.pdf”,无论如何加强管理培训,只要不能通过编译器化身为“规范.exe”,就等于形同虚设

🔚


da_miao_zi
1 声望0 粉丝

软件工程师、技术图书译者。译有《图解云计算架构》《图解量子计算机》《计算机是怎样跑起来的》《自制搜索引擎》等。