5

是否可以定义一个对数据类型和度量单位都通用的函数?例如,我想做什么,但没有编译(尽管即使没有测量单位也不会,但我相信我传达了我想做的事情):

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

但我在写上面的时候开始想知道一般情况。

4

1 回答 1

11

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

let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero

所以你不需要定义一个有区别的联合。

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

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

  1. 使用重载(a la 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 ...

  1. 使用内联,一个常见的技巧是重新输入:
    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

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


  1. 同时使用重载和内联:这个技巧将通过中间类型使用重载来解决这两个问题,这样您就可以获得编译时检查并且您将能够定义泛型函数:
    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)
  1. 利用一些通用约束:
    let inline retype (x: 'T) : 'U = (# "" x: 'U #)
    let inline stripUoM (x: '``Num<'M>``) =
        let _ = x * (LanguagePrimitives.GenericOne : 'Num)
        retype x :'Num

这类似于 2) 但它不需要类型注释。限制是它仅适用于数字类型,但通常这是 UoM 的用例。

于 2013-01-05T08:58:17.203 回答