对于如何编写isValidUnitValue
函数的具体问题,答案是:
let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero
所以你不需要定义一个有区别的联合。
关于最初的问题,是否可以定义一个既通用数据类型又通用度量单位的函数,就像dropUnit
简短的回答是否定的一样。如果存在这样的函数,它将具有类似的签名, 'a<'b> -> 'a
并且为了表示它,类型系统应该实现更高的种类。
但是有一些使用重载和内联的技巧:
- 使用重载(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 ...
- 使用内联,一个常见的技巧是重新输入:
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
哪个编译但会在运行时失败。
- 同时使用重载和内联:这个技巧将通过中间类型使用重载来解决这两个问题,这样您就可以获得编译时检查并且您将能够定义泛型函数:
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)
- 利用一些通用约束:
let inline retype (x: 'T) : 'U = (# "" x: 'U #)
let inline stripUoM (x: '``Num<'M>``) =
let _ = x * (LanguagePrimitives.GenericOne : 'Num)
retype x :'Num
这类似于 2) 但它不需要类型注释。限制是它仅适用于数字类型,但通常这是 UoM 的用例。