You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

F#中如何为复合度量单位类型成员设置合法取值范围限制

实现方案

核心思路是通过私有化类型构造器+智能构造函数的组合,从根源上避免非法值被创建,完全符合「让非法状态无法表达」的设计原则:

  • 将复合单位类型的构造器私有化,禁止外部直接创建实例
  • 实现智能构造函数,输入任意英石/磅、英尺/英寸数值时自动做归一化校验和转换,确保输出的结果永远符合取值规则
  • 可额外提供从总磅、总英寸直接生成复合单位的便捷方法,适配不同使用场景

完整代码实现

首先补全基础单位模块的转换常量:

module Units
    // Mass
    [<Measure>] type kg // Kilogram
    [<Measure>] type g  // Gram
    [<Measure>] type lb // Pound (mass)
    [<Measure>] type st // Stone (mass)
    
    // 补充单位转换常量
    let lbPerSt = 14.0<lb/st>
    let kgPerLb = 0.45359237<kg/lb>

    // Length
    [<Measure>] type m      // Metre
    [<Measure>] type cm     // Centimetre
    [<Measure>] type inch   // Inch
    [<Measure>] type ft     // Foot

    // 补充单位转换常量
    let inchPerFt = 12.0<inch/ft>
    let cmPerInch = 2.54<cm/inch>

复合单位模块的安全实现:

module CompoundUnits
    open Units

    // 私有化构造器,外部无法直接创建实例
    type StonesAndPounds = private {
        Stones: float<st>
        Pounds: float<lb>
    } with
        // 智能构造函数,自动归一化
        static member create (stones: float<st>) (pounds: float<lb>) =
            // 先转换为总磅数做统一计算
            let totalLb = stones * lbPerSt + pounds
            // 处理负值,可根据业务需求调整为返回Result类型代替抛出异常
            if totalLb < 0.0<lb> then
                invalidArg (nameof totalLb) "重量不能为负值"
            // 归一化计算
            let normalizedSt = System.Math.Floor(float (totalLb / lbPerSt)) * 1.0<st>
            let normalizedLb = totalLb % lbPerSt
            { Stones = normalizedSt; Pounds = normalizedLb }
        // 便捷方法:从总磅数直接创建
        static member fromTotalPounds (totalLb: float<lb>) =
            StonesAndPounds.create 0.0<st> totalLb
        // 便捷方法:转换为总磅数方便后续换算
        member this.TotalPounds = this.Stones * lbPerSt + this.Pounds

    // 英尺英寸的实现逻辑完全一致
    type FeetAndInches = private {
        Feet: float<ft>
        Inches: float<inch>
    } with
        static member create (feet: float<ft>) (inches: float<inch>) =
            let totalInch = feet * inchPerFt + inches
            if totalInch < 0.0<inch> then
                invalidArg (nameof totalInch) "长度不能为负值"
            let normalizedFt = System.Math.Floor(float (totalInch / inchPerFt)) * 1.0<ft>
            let normalizedInch = totalInch % inchPerFt
            { Feet = normalizedFt; Inches = normalizedInch }
        static member fromTotalInches (totalInch: float<inch>) =
            FeetAndInches.create 0.0<ft> totalInch
        member this.TotalInches = this.Feet * inchPerFt + this.Inches

使用示例

你提到的非法输入会被自动归一化为符合日常表达习惯的格式:

// 自动归一为8英石11磅
let eightStoneEleven = StonesAndPounds.create 6.0<st> 39.0<lb>

// 自动归一为5英尺10英寸
let fiveFootTen = FeetAndInches.create 4.0<ft> 22.0<inch>

如果希望更贴合纯函数式的错误处理风格,也可以把构造函数的返回值改成Result<复合单位类型, string>类型代替抛出异常,上层调用方可以自行处理非法输入的分支。

内容的提问来源于stack exchange,提问作者JansthcirlU

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.09 05:21:03