f#泛型类型的泛型单位

Bra*_*ram 5 f# units-of-measurement

是否可以定义一个通用的数据类型和度量单位的函数?例如,我想做什么,但不编译(虽然它甚至没有计量单位,但我相信我传达了我想做的事情):

let inline dropUnit (x : 'a<_>) = x :> typeof(a)
Run Code Online (Sandbox Code Playgroud)

这里的想法是我已经定义了一些度量单位,例如"kg"和"l"以及一个明确的联盟:

type Unit = 
  | Weight of float< kg >
  | Volume of float < l >
Run Code Online (Sandbox Code Playgroud)

我想做的事情如下:

let isValidUnitValue myUnit =
   match myUnit with
       | Weight(x) -> (dropUnit x) > 0.
       | Volume(x) -> (dropUnit x) > 0.
Run Code Online (Sandbox Code Playgroud)

我知道,对于这个特殊情况,我可以使用

let dropUnit (x : float<_>) = (float) x
Run Code Online (Sandbox Code Playgroud)

但是在编写上述内容时,我开始怀疑一般情况.

Gus*_*Gus 7

对于您如何编写isValidUnitValue函数的具体问题,答案是:

let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero
Run Code Online (Sandbox Code Playgroud)

因此,您无需定义Discriminated Union.

关于原始问题是否有可能定义一个既通用于数据类型又具有计量单位的函数,如dropUnit简短答案为否.如果存在这样的函数,它将具有类似的签名, 'a<'b> -> 'a并且为了表示它,类型系统应该实现更高类型.

但是有一些使用重载和内联的技巧:

1)使用重载(a C#)

type UnitDropper = 
    static member drop (x:sbyte<_>  ) = sbyte   x
    static member drop (x:int16<_>  ) = int16   x
    static member drop (x:int<_>    ) = int     x
    static member drop (x:int64<_>  ) = int64   x
    static member drop (x:decimal<_>) = decimal x
    static member drop (x:float32<_>) = float32 x
    static member drop (x:float<_>  ) = float   x

[<Measure>] type m
let x = UnitDropper.drop 2<m> + 3
Run Code Online (Sandbox Code Playgroud)

但这不是一个普通的功能,你不能在它上面写一些通用的东西.

> let inline dropUnitAndAdd3 x = UnitDropper.drop x + 3 ;;
-> error FS0041: A unique overload for method 'drop' could not be determined ...
Run Code Online (Sandbox Code Playgroud)


2)使用内联,一个常见的技巧是重新输入:

let inline retype (x:'a) : 'b = (# "" x : 'b #)

[<Measure>] type m
let x = retype 2<m> + 3
let inline dropUnitAndAdd3 x = retype x + 3
Run Code Online (Sandbox Code Playgroud)

问题是retype太通用了,它会让你写:

let y = retype 2.0<m> + 3
Run Code Online (Sandbox Code Playgroud)

哪个编译但在运行时会失败.


3)使用重载和内联:这个技巧将通过使用中间类型的重载来解决这两个问题,这样你就可以获得编译时检查,并且你将能够定义泛型函数:

type DropUnit = DropUnit with
    static member ($) (DropUnit, x:sbyte<_>  ) = sbyte   x
    static member ($) (DropUnit, x:int16<_>  ) = int16   x
    static member ($) (DropUnit, x:int<_>    ) = int     x
    static member ($) (DropUnit, x:int64<_>  ) = int64   x
    static member ($) (DropUnit, x:decimal<_>) = decimal x
    static member ($) (DropUnit, x:float32<_>) = float32 x
    static member ($) (DropUnit, x:float<_>  ) = float   x

let inline dropUnit x = DropUnit $ x

[<Measure>] type m
let x = dropUnit 2<m>   + 3
let inline dropUnitAndAdd3 x = dropUnit x + 3
let y = dropUnit 2.0<m> + 3   //fails at compile-time
Run Code Online (Sandbox Code Playgroud)

在最后一行中,您将收到编译时错误: FS0001: The type 'int' does not match the type 'float'

这种方法的另一个优点是,您可以稍后使用新类型扩展它,方法是在类型定义中定义静态成员($),如下所示:

type MyNumericType<[<Measure 'U>]> =
    ...
    static member dropUoM (x:MyNumericType<_>) : MyNumericType = ...
    static member ($) (DropUnit, x:MyNumericType<_>) = MyNumericType.dropUoM(x)
Run Code Online (Sandbox Code Playgroud)