在 F# 中的类型成员中定义允许的值范围

Defining allowed value ranges in type member in F#

我正在玩弄 F# 中的 units of measure,我目前正在尝试创建长度和质量的复合度量单位以反映英制系统中的口语,例如在美国和英国,“我身高 5 英尺 10 英寸”或“她体重 8 英石 11 磅”。

我已经为标准(非复合)单位定义了一个模块,如下所示:

module Units
    // Mass
    [<Measure>] type kg // Kilogram
    [<Measure>] type g  // Gram
    [<Measure>] type lb // Pound (mass)
    [<Measure>] type st // Stone (mass)
    
    // Conversions
    ...

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

    // Conversions
    ...

我在另一个模块中定义了复合单元:

module CompoundUnits
    open Units
    
    // Mass
    type StonesAndPounds = {
        Stones: float<st>
        Pounds: float<lb>
    }

    // Length
    type FeetAndInches = {
        Feet: float<ft>
        Inches: float<inch>
    }

但是,按照我目前编写复合质量和长度类型的方式,存在非法状态(例如负值)和技术上正确但不喜欢:

// 39 lbs = 2 st 11 lbs
let eightStoneEleven: StonesAndPounds = { Stones = 6.0<st>; Pounds = 39.0<lb> }
// 22" = 1' 10"
let fiveFootTen: FeetAndInches = { Feet = 4.0<ft>; Inches = 22.0<inch> }

Scott Wlaschin 在他的“领域建模功能”一书中谈到了使非法状态无法表示,所以我想知道是否有一种方法可以对我的复合类型实施某种限制,以便 0<ft> <= Feet0<inch> <= Inches <= 12<inch>0<st> <= Stones0<lb> <= Pounds <= 14<lb>

一种常见的模式是为类型创建一个模块,其中包含其定义以及 create 函数和其他验证逻辑。

Scott 在他的网站上有一些示例,作为他的 'Designing with Types' 系列的一部分。

https://fsharpforfunandprofit.com/posts/designing-with-types-non-strings/

您不能对度量单位本身施加限制,但您可以创建专用类型来表示您的复合度量,就像 Scott 对 SafeDateNonNegativeInt 等所做的那样

这些仍然可以使用 'standard' 度量单位作为它们的组件属性。

引自文章:

“度量单位确实可以用来避免混淆不同类型的数值,并且比我们一直使用的单一大小写联合强大得多。

另一方面,度量单位没有封装,不能有约束。任何人都可以创建一个带有度量单位的整数,并且没有最小值或最大值。"