fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Possible bug with units of measure when casting them

Open bmitc opened this issue 3 years ago • 0 comments

Repro steps

See the below FSI session for an overview of the issue. Asked on Stack Overflow as well.

> 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>

Expected behavior

I expected 'OldUnit to not be constrained.

Actual behavior

'OldUnit is correctly recognized as a unit of measure by the warning, but it is constrained to be the identity unit of measure <1>. It is not clear to me why this constraining happens.

Maybe this is not a bug, but from the warning, it isn't clear to me what is going on.

Known workarounds

Alternatives are to explicilty parameterize the function for 'OldUnit or let type inference take care of things where the return value is type annotated when binding to a name. However, it isn't clear why the above solution gets constrained.

> [<Measure>] type object;;
[<Measure>]
type object

> [<Measure>] type world;;
[<Measure>]
type world

> let convertUnits2<[<Measure>] 'OldUnit, [<Measure>] 'NewUnit> (x: float<'OldUnit>) : float<'NewUnit> =
    x |> removeUnits |> castUnits<'NewUnit>;;
val convertUnits2: x: float<'OldUnit> -> float<'NewUnit>

> let convertUnits3 (x: float<'OldUnit>) : float<'NewUnit> =
    x |> removeUnits |> castUnits<'NewUnit>;;
val convertUnits3: x: float<'OldUnit> -> float<'NewUnit>

> convertUnits2<world, object> 3.0<world>;;
val it: float<object> = 3.0

> let test : float<object> = convertUnits3 3.0<world>;;
val test: float<object> = 3.0

Related information

  • Operating system: n/a
  • .NET Runtime kind (.NET Core, .NET Framework, Mono): .NET 6
  • Editing Tools (e.g. Visual Studio Version, Visual Studio): n/a

bmitc avatar Jul 27 '22 16:07 bmitc