Reputation: 737
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>
.
Upvotes: 2
Views: 182
Reputation: 2149
A common pattern is to create a module for the type, which contains its definition plus create
functions and other validation logic.
Scott has some examples on his website, as part of his 'Designing with Types' series.
https://fsharpforfunandprofit.com/posts/designing-with-types-non-strings/
You can't enforce the restrictions on the units of measure themselves, but you could create dedicated types to represent your compound measurements as Scott does with SafeDate
and NonNegativeInt
etc.
These could still use the 'standard' units of measure for their component properties.
A quote from the article:
"Units of measure can indeed be used to avoid mixing up numeric values of different type, and are much more powerful than the single case unions we’ve been using.
On the other hand, units of measure are not encapsulated and cannot have constraints. Anyone can create a int with unit of measure say, and there is no min or max value."
Upvotes: 0