简体   繁体   中英

F# types with units of measure vs System.Math

I defined my own unit of measure to represent radians:

[<Measure>]
type rad

Then I realized that having a value eg of type float<rad> I can't use many of the functions defined in System.Math (as they work with 'plain' floats)

let valueWithUnit = 5.45<rad>
let absValue = Math.Abs valueWithUnit // <--- error!!

I created a naive piece of code that allows functions to handle float<rad> values as well:

let liftToRadFunc (f : float -> float) (arg : float<rad>) =
    arg * 1.</rad>
    |> f
    |> (*) 1.<rad>

let result = (liftToRadFunc Math.Abs) valueWithUnit // <--- now works fine

but the problem with it is that it's totally non-generic. Assuming I'd like to introduce an unit to represent degrees as well - what then ? Would I need to copy the code and change all the units from "rad" to "deg" ? Or maybe there's a better solution ?

The core operators of F# are mostly unit-of-measure aware. For example, abs works with units of measure and renders System.Math.Abs redundant.

It is possible to generalize the function from the question in terms of the units used:

let liftToKeepUnits f (arg : float<'u>) : float<'u> =
    float arg |> f |> LanguagePrimitives.FloatWithMeasure

Though this simply keeps the units, which is only correct if the function has no effect on the units. So I'd use this lifting function to create specific, unit-aware functions, rather than lifting functions where they are used.

In my experience, the most useful tool is the ability to make types and functions generic in terms of units of measure. Type inference will not always do this on its own, but adding unit-of-measure type annotations will often do the trick. In more complicated cases, LanguagePrimitives.FloatWithMeasure and LanguagePrimitives.Float32WithMeasure allow, together with a type annotation, to add units where one would otherwise run into the constraint that forbids non-zero values with generic unit-of-measure arguments.

Also, types can have unit-of-measure arguments like this:

type MyType<[<Measure>] 'u> ...

When using all of these features, functions that aren't aware of units become the exception, and it's not a big problem to manually add or remove units in the remaining cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM