在 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”,就等於形同虛設。
🔚