Issue with generically casting a float with units to different units in F#

Viewed 78

I am having a major issue handling units of measure in F#. I have handled them successfully so far, but I have only either been removing units to get back a float or casting a float to one with units. However, now I have a use case where I need to generically drop units off of a float and give them new units. At present, I see no way that this is possible in F#.

For example:

> let removeUnits (x: float<'Unit>) = float x;;
val removeUnits: x: float<'Unit> -> float

> let castUnits<[<Measure>] 'Unit> (x: float) = LanguagePrimitives.FloatWithMeasure<'Unit> x;;
val castUnits: x: float -> float<'Unit>

> let convertUnits<[<Measure>] 'NewUnit> (x: float<'OldUnit>) : float<'NewUnit> =
    x |> removeUnits |> castUnits<'NewUnit>;;

  let convertUnits<[<Measure>] 'NewUnit> (x: float<'OldUnit>) : float<'NewUnit> =
  -------------------------------------------------^^^^^^^^

stdin(8,47): warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The unit-of-measure variable 'OldUnit has been constrained to be measure '1'.

val convertUnits: x: float -> float<'NewUnit>

This almost seems like a bug to me. The types of the pipeline should be float<'OldUnit> -> float -> float<'NewUnit>, because those are the type signatures of the component functions. I'm not understanding why out of nowhere that 'OldUnit is being constrained to 1.

How can I do this properly? I want a function with signature val convertUnits: x: float<'OldUnit> -> float<'NewUnit>, where the function only changes the units and leaves the underlying float values alone.

Is this a bug in the compiler?

1 Answers

That's just because you explicitly gave your function only one type parameter - 'NewUnit, - but in its signature you're using two - 'OldUnit and 'NewUnit.

Give the function both parameters like this:

let convertUnits<[<Measure>] 'NewUnit, [<Measure>] 'OldUnit> (x: float<'OldUnit>) : float<'NewUnit> =
    x |> removeUnits |> castUnits<'NewUnit>

Or, alternatively, let the compiler infer them by itself:

let convertUnits (x: float<'OldUnit>) : float<'NewUnit> =
    x |> removeUnits |> castUnits<'NewUnit>
Related