f#泛型类型的泛型单位

时间:2013-01-05 04:18:50

标签: f# units-of-measurement

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

let inline dropUnit (x : 'a<_>) = x :> typeof(a)

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

type Unit = 
  | Weight of float< kg >
  | Volume of float < l >

我想做类似的事情:

let isValidUnitValue myUnit =
   match myUnit with
       | Weight(x) -> (dropUnit x) > 0.
       | Volume(x) -> (dropUnit x) > 0.

我知道对于这个特例,我可以使用

let dropUnit (x : float<_>) = (float) x

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

1 个答案:

答案 0 :(得分:7)

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

let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero

所以你不需要定义一个被歧视的联盟。

关于原始问题是否可以定义一个既通用于数据类型又具有计量单位的函数,如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

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

> let inline dropUnitAndAdd3 x = UnitDropper.drop x + 3 ;;
-> error FS0041: A unique overload for method 'drop' could not be determined ...


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

问题是retype过于通用,它会允许你写:

let y = retype 2.0<m> + 3

编译但会在运行时失败。


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

在最后一行中,您将收到编译时错误: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)