In your sample, the problem is that you are trying to add two things with different units (price without VAT and VAT value) - that's not allowed by the static typing - you can only add things of the same unit (which is part of the principles behind units of measure - there is not much you can do about this).
I think that the most natural solution (that, however, does not give you as strong safety guarantees) would be to make the VAT rate dimensionless number.
In general (when thinking about the physical meaning), rates are examples of number that does not have a unit - rate is generally calculated as X<unit> / Y<unit>
for some numbers X
and Y
of the same unit
and so the unit cancels out during the division.
So you could write something like this:
[<Measure>] type net
[<Measure>] type vatRate = 1
[<Measure>] type vatValue = net * vatRate
let calculateVat (netValue : float<net>) (vat : float<vatRate>) = netValue * vat
let calculateGross (netValue : float<net>) (vat : float<vatValue>) = netValue + vat
This means that float<vatRate>
will really be just ordinary float
and vatValue
is the same as net
(but you can still use the aliases in your code as a documentation).
So, this removes the distinction between price with VAT and price without VAT, but at least your program still statically distinguishes between float representing money and float representing just numbers.