1

This came up while I looked at the following question: F# Unit of Measure, Casting without losing the measure type Please note that I am not trying to use this unbox code, I just discovered some weird behavior while answering the question.

Why does the following code work

let intToFloat (x:int<'u>) : float<'u> = unbox float x
intToFloat 1<second>

while this yields a System.InvalidCastException: Unable to cast object of type 'float32ToFloat@86-6' to type 'Microsoft.FSharp.Core.FSharpFunc`2[System.Single,System.Double]'.?

let float32ToFloat (x:float32<'u>) : float<'u> = unbox float x
float32ToFloat 1.0f<second>

If I put parantheses around the (float x) the code works as expected, so I assume it must be some expression evaluation/type inference rule. What exactly is going on here and why are the parantheses needed in the second case?

Community
  • 1
  • 1
Johannes Rudolph
  • 35,298
  • 14
  • 114
  • 172
  • Tomas's answer explains the error, but as a side-note, `unbox` is not the best way to do this. You should use `LanguagePrimitives.FloatWithMeasure<'u>`, which translates to a no-op in .NET bytecode, whereas `unbox` adds some runtime type checking which is not necessary in this case. – Tarmil Feb 15 '14 at 14:20
  • @Tarmil, I was aware of that. Thanks for confirming that `FloatWithMeasure` is a better option though (see my answer to the linked question). Would you mind adding/confirm this over there as well? – Johannes Rudolph Feb 15 '14 at 14:28
  • Oh, I didn't see your post in the question you linked. So we pretty much said the same thing :) – Tarmil Feb 15 '14 at 14:40

1 Answers1

3

The subtle thing in your code snippets is unbox float x - the compiler treats this as (unbox float) x. As a result the two functions are actually treated like this:

let intToFloat (x:int<'u>) : float<'u> = 
  let f = unbox float in f x

let float32ToFloat (x:float32<'u>) : float<'u> = 
  let f = unbox float in f x

So, you are taking the float function, casting it (unsafely) to a function of another type and then calling it. The type is int<'u> -> float<'u> in the first case and float32<'u> -> float<'u> in the second case.

I think the first one works because when the compiler sees the float function (without any type annotations), it defaults to int -> float and, since units of measure are erased at runtime, this can be converted to int<'u> -> float<'u> (but not to the second type - because you are performing unsafe cast).

So, the main problem is wrong parentheses in your implementation (which leads to a really subtle issue). I think you probably wanted something like this:

let intToFloat (x:int<'u>) : float<'u> = unbox (float x)
let float32ToFloat (x:float32<'u>) : float<'u> = unbox (float32 x)
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553