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 准备就绪后试用它。
在 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 准备就绪后试用它。