I'm playing around with units of measure in F# and I'm currently trying to create compound units of measure for length and mass to reflect colloquial speech in the imperial system, e.g. "I'm 5 foot 10" or "She weighs 8 stone and 11 pounds" in the US and the UK.
I've defined a module for standard (non-compound) units like so:
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
...
And I've defined compound units in a different module:
module CompoundUnits
open Units
// Mass
type StonesAndPounds = {
Stones: float<st>
Pounds: float<lb>
}
// Length
type FeetAndInches = {
Feet: float<ft>
Inches: float<inch>
}
However, with the way I've currently written the compound mass and length types, there's room for illegal states (such as negative values) and states that are technically correct but not preferred:
// 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> }
In his book "Domain Modeling made Functional" Scott Wlaschin talks about making illegal states unrepresentable, so I was wondering if there was a way to enforce some kind of restriction on my compound types so that 0<ft> <= Feet, 0<inch> <= Inches <= 12<inch> and 0<st> <= Stones, 0<lb> <= Pounds <= 14<lb>.