F# 计量单位,不丢失计量类型的转换

14

有没有内置的类型转换函数可以保留单位,如果没有,我该如何做呢?例如,在此代码中,如果我想将intWithSecondsMeasure转换为float而不失去度量或乘以1.0<s>,该怎么办?

[<Measure>] type s
let intWithSecondsMeasure = 1<s>
let justAFloat = float intWithSecondsMeasure 
4个回答

11

@kvb提供的答案当然可行,但我宁愿不使用unbox运算符进行转换。有一种更好的内置方法,我认为应该编译为 IL 中的 NOP(我还没有检查过,但unbox可能最终会成为IL中的unbox指令,从而添加运行时类型检查)。

F#中执行单位转换的首选方法是LanguagePrimitives.TypeWithMeasure (MSDN)。

let inline float32toFloat (x:float32<'u>) : float<'u> = 
    x |> float |> LanguagePrimitives.FloatWithMeasure

我添加了IL。将int强制转换为float以与问题保持一致。https://dev59.com/1HI-5IYBdhLWcg3wW3Cp#21802111 - gradbot

8
我认为没有内置的方法来完成此操作,但是您可以轻松地定义自己的保持单位的转换函数:
let float_unit (x:int<'u>) : float<'u> = unbox float x
let floatWithSecondsMeasure = float_unit intWithSecondsMeasure

5
我编译了来自kvb和Johannes答案的代码。
Johannes的回答:
let float32toFloat (x:int<'u>) : float<'u> = 
    x |> float |> LanguagePrimitives.FloatWithMeasure

.method public static float64  float32toFloat(int32 x) cil managed
{
  // Code size       3 (0x3)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  conv.r8
  IL_0002:  ret
} // end of method Program::float32toFloat
答案已添加括号。
let float_unit (x:int<'u>) : float<'u> = unbox (float x)

.method public static float64  float_unit(int32 x) cil managed
{
  // Code size       13 (0xd)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  conv.r8
  IL_0002:  box        [mscorlib]System.Double
  IL_0007:  unbox.any  [mscorlib]System.Double
  IL_000c:  ret
} // end of method Program::float_unit

KVB答案
let float_unit (x:int<'u>) : float<'u> = unbox float x

.method public static float64  float_unit(int32 x) cil managed
{
  // Code size       19 (0x13)
  .maxstack  8
  IL_0000:  newobj     instance void Program/float_unit@4::.ctor()
  IL_0005:  call       !!0 [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::UnboxGeneric<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32,float64>>(object)
  IL_000a:  ldarg.0
  IL_000b:  tail.
  IL_000d:  callvirt   instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32,float64>::Invoke(!0)
  IL_0012:  ret
} // end of method Program::float_unit

3

请查看我对这个问题的回答:

Unit-safe square roots

根据今天的建议,可以尝试以下方法:

[<Measure>] 
type s
let intWithSecondsMeasure = 1<s>

let intUtoFloatU< [<Measure>] 'u>( x : int<'u> ) : float<'u> = //'
    let i = int x       //  drop the units
    let f = float i     //  cast
    box f :?> float<'u> //' restore the units

let floatWithS = intUtoFloatU intWithSecondsMeasure

这个带有这个签名的东西会放在库里吗?显然,它将以计划中的FloatWithMeasure为可写形式,但是拥有一个清晰地单位安全的函数直接可用会更好。 - GS - Apologise to Monica
此答案现在会发出警告:“此类型测试或向下转换将忽略度量单位'u'”。 - gradbot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接