Using F# Units Of Measure to calculate a gross from a net amount + a vat amount

StackOverflow https://stackoverflow.com/questions/19744816

  •  03-07-2022
  •  | 
  •  

Вопрос

I've recently started playing around with Units Of Measure in F# and thought I could write a very simple example that calculates a gross amount given a VAT rate and a net total.

For instance:

The net amount might equal 600.00 The VAT rate would be 20% Which should give a Gross amount of 720.00

I have the following types

[<Measure>] type net
[<Measure>] type vatRate
[<Measure>] type vatValue = net * vatRate
[<Measure>] type gross

And the following functions

let calculateVat (netValue : float<net>) (vat : float<vatRate>) = netValue * vat
let calculateGross (netValue : float<net>) (vat : float<vatValue>) = netValue + vat

With the following tests:

let calcVatTest = calculateVat 600.00<net> 0.2<vatRate> = 120.00<vatValue>
let calcGrossTest = calculateGross 600.00<net> 120.00<vatValue> = 720.00<gross>

The problem I'm having is that I can't get the correct syntax for the calculateGross function and I'm getting a compilation error: "The unit of measure 'vatValue' does not match the unit of measure 'net'"

It seems as though I need to define gross similar to the following:

[<Measure>] type gross = net + vatValue

But the compiler doesn't like the +

Any ideas how I might achieve this?

Thanks

Это было полезно?

Решение 3

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.

Другие советы

Only operators *, /, and ^ are supported in measure expressions—although - may be used to construct a negative exponent. Logically, this makes sense because in order to use dimensional analysis, the compiler has to consider each factor to consist of a scalar and a single units or a product of units.

Honestly, this doesn't seem to be a good use for units of measure. It looks like it just complicates your code without providing too much more expressiveness.

Further reading

I know you didn't ask but this is too big a point to pass up and this is too much to put into a comment. I think probably you don't want the unit of measure for gross, vatRate etc because I'd expect gross, net and so forth to be in terms of currency.

Something more like this (assuming the VAT is a percent of a European currency):

[<Measure>] type euro

[<Measure>] type percent

let gross = 100.0<euro>
let vatRate = 5.0<percent>

I mean to say that I think you've gotten hold of the wrong way to use units of measure. Gross isn't a unit of measure--it's a number; likewise vatRate.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top