F# 中的通用单位

Generic units in F#

在 F# 中编写泛型函数时,我可以使用 LanguagePrimitives 模块中定义的成员,例如在此函数中,它只是递增一个数字

let inline increment (x : 'a) =
    x + LanguagePrimitives.GenericOne

我想知道是否有任何类似于使用度量单位的东西。特别是:是否可以编写一个函数,该函数采用通用参数并将其转换为具有单位的相同类型的数字。类似于:

let inline toNumberWithUnit<[<Measure>] 'u> x =
    x * GenericOne<'u> //that won't work

这将有一个类型:'a -> 'a<'u>。可能吗?

该签名在 .NET 中是非法的,因此唯一的方法是使用具有静态约束的 F# 内联功能。

那么你可以这样定义一个函数:

[<Measure>]
type M = class end

let x = LanguagePrimitives.FloatWithMeasure<M> 2. 

type T<[<Measure>]'M>() =
    static member ($) (T, x) = LanguagePrimitives.FloatWithMeasure<'M> x
    static member ($) (T, x) = LanguagePrimitives.Float32WithMeasure<'M> x
    static member ($) (T, x) = LanguagePrimitives.Int32WithMeasure<'M> x
    // more overloads

let inline NumberWithMeasure x = T() $ x

let a: float<M>   = NumberWithMeasure 2.
let b: float32<M> = NumberWithMeasure 2.0f
let c: int<M>     = NumberWithMeasure 2

处理泛型数字和度量单位时的主要问题是,您最终会得到那些具有泛型类型和类型参数(higher kind)的签名,而目前还没有.NET 支持。

更新

一段时间后,我 运行 也遇到了这种情况,发现这个答案恰好来自我:)

在尝试使用不同的度量单位后,我意识到它不起作用,因为类型推断不会泛化到度量单位,发布的示例有效,因为类型推断见证了 [=12= 的使用] 在 M.

上测量然后特化函数

然而,这里有一种方法可以使它工作,通过显式使用上面定义的 $ 运算符:

[<Measure>] type km
[<Measure>] type miles

type WithMeasure<[<Measure>]'M>() =
    static member ($) (x, T) = LanguagePrimitives.FloatWithMeasure<'M> x
    static member ($) (x, T) = LanguagePrimitives.Float32WithMeasure<'M> x
    static member ($) (x, T) = LanguagePrimitives.Int32WithMeasure<'M> x
    static member ($) (x, T) = LanguagePrimitives.DecimalWithMeasure<'M> x
    static member ($) (x, T) = LanguagePrimitives.Int16WithMeasure<'M> x
    static member ($) (x, T) = LanguagePrimitives.Int64WithMeasure<'M> x
    static member ($) (x, T) = LanguagePrimitives.SByteWithMeasure<'M> x
    // no more overloads


let a: float<km>      = 2.   $WithMeasure()
let b: float32<miles> = 2.0f $WithMeasure()

可能有创建泛型函数或泛型常量的方法,但目前 F# 的当前版本似乎无法实现。

我会在 F# 4.1 准备就绪后试用它。