F # 'unbox float x' против 'unbox int x' странный результат компиляции

Это возникло, когда я рассмотрел следующий вопрос: F # Единица измерения, приведение без потери типа меры Обратите внимание, что я не пытаюсь использовать этот код распаковки, я только что обнаружил странное поведение, отвечая на вопрос.

Почему работает следующий код

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

при этом возникает System.InvalidCastException: невозможно преобразовать объект типа 'float32ToFloat @ 86-6' в тип 'Microsoft.FSharp.Core.FSharpFunc`2 [System.Single, System.Double]'. ?

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

Если я заключу скобки вокруг (float x), код будет работать, как ожидалось, поэтому я предполагаю, что это должно быть какое-то правило оценки выражения / вывода типа. Что именно здесь происходит и зачем нужны скобки во втором случае?


person Johannes Rudolph    schedule 15.02.2014    source источник
comment
Ответ Томаса объясняет ошибку, но в качестве примечания unbox - не лучший способ сделать это. Вы должны использовать LanguagePrimitives.FloatWithMeasure<'u>, что в байт-коде .NET означает отсутствие операции, тогда как unbox добавляет некоторую проверку типа во время выполнения, которая в данном случае не требуется.   -  person Tarmil    schedule 15.02.2014
comment
@Tarmil, я знал об этом. Спасибо, что подтвердили, что FloatWithMeasure - лучший вариант (см. Мой ответ на связанный вопрос). Не могли бы вы добавить / подтвердить это и там?   -  person Johannes Rudolph    schedule 15.02.2014
comment
О, я не видел вашего сообщения в вопросе, на который вы указали. Так что мы почти сказали то же самое :)   -  person Tarmil    schedule 15.02.2014


Ответы (1)


Тонкая вещь в ваших фрагментах кода - unbox float x - компилятор рассматривает это как (unbox float) x. В результате две функции фактически обрабатываются следующим образом:

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

Итак, вы берете функцию float, преобразуете ее (небезопасно) в функцию другого типа и затем вызываете ее. Тип - int<'u> -> float<'u> в первом случае и float32<'u> -> float<'u> во втором.

Я думаю, что первый работает, потому что, когда компилятор видит функцию float (без каких-либо аннотаций типов), по умолчанию он принимает значение int -> float, и, поскольку единицы измерения стираются во время выполнения, их можно преобразовать в int<'u> -> float<'u> (но не во второй тип - потому что вы выполняете небезопасное приведение).

Итак, основная проблема - неправильные скобки в вашей реализации (что приводит к очень тонкой проблеме). Думаю, вы, наверное, хотели что-то вроде этого:

let intToFloat (x:int<'u>) : float<'u> = unbox (float x)
let float32ToFloat (x:float32<'u>) : float<'u> = unbox (float32 x)
person Tomas Petricek    schedule 15.02.2014