具有度量单位的重载功能

时间:2015-06-18 02:43:29

标签: .net generics f#

TL; DR 如何编写一个“重载”函数,该函数可以处理单位具有相同单位的单独类型的所有数值类型({{1} },float32<m> -> Vector2<m>)?

我有一个很好的小Vector2类,但是我想允许将度量单位附加到它上面,所以我基本上就这样定义了它(但是功能比这里要多得多):

int<kg> -> Vector2<kg>

我已经能够毫不费力地编写重载算术运算符(例如type Vector2<[<Measure>] 'u>(x: float32<'u>, y: float32<'u>) = member this.X = x member this.Y = y ),但我一直在努力编写一个可以工作的构造函数运算符(称为static member (+) ...)包含所有数字类型和单位(允许我写@@而不是1<m> @@ 2.5<m>。在我将单位附加到此课程之前,这很简单:

new Vector2<m>(1.<m>, 2.<m>)

到目前为止,我已经编写了一个帮助类来处理这个问题:

let inline (@@) x y = new Vector2(float32 x, float32 y)

但似乎无法编写适当的类型约束来处理此调用案例。例如,像这样的东西不起作用

type OverloadedOperators =
    static member CreateVector2 (x: float32<'u>, y: float32<'u>) = new Vector2<'u>(x, y)
    static member CreateVector2 (x: float<'u>, y: float<'u>) = new Vector2<'u>(x |> float32 |> Float32WithMeasure<'u>, y |> float32 |> Float32WithMeasure<'u>)
    static member CreateVector2 (x: int<'u>, y: int<'u>) = new Vector2<'u>(x |> float32 |> Float32WithMeasure<'u>, y |> float32 |> Float32WithMeasure<'u>)

因为let inline (@@) (x: 'u when 'u : (static member CreateVector2 : 'u * 'u -> Vector2<'v>)) y = OverloadedOperators.CreateVector2 (x, y) The type 'u is not compatible with float32<'v>等。 我不知道怎么写这最后一小块。我想要的甚至可能吗?或者我应该忍受不断在任何地方使用The type 'u is not compatible with int<'v>构造函数?

编辑感谢@JohnPalmer,我已经非常接近了,基于他的答案,我已经提出了最后一点:Vector2。现在最后一步是“取消元组”这个函数,让它成为一个中缀运算符,但这似乎是不可能的,因为它甚至不知道它总是一个元组。我想我可能已经尽可能地用F#来到这里,但如果我错了,请纠正我!与此同时,我将使用let inline vec2 p = convert *** p,这比 lot 更好,而不是每次调用构造函数时都必须内联所有杂乱的单元类型转换。

3 个答案:

答案 0 :(得分:1)

这是一个有点hacky的解决方案,它是根据我们在这种情况下使用的标准技巧构建的:

open LanguagePrimitives
type Vector2<[<Measure>] 'u>(x: float32<'u>, y: float32<'u>) =
    member this.X = x
    member this.Y = y
type OverloadedOperators() =
    static member CreateVector2 (x: float32<'u>, y: float32<'u>) = new Vector2<'u>(x, y)
    static member CreateVector2 (x: float<'u>, y: float<'u>) = new Vector2<'u>(x |> float32 |> Float32WithMeasure<'u>, y |> float32 |> Float32WithMeasure<'u>)
    static member CreateVector2 (x: int<'u>, y: int<'u>) = new Vector2<'u>(x |> float32 |> Float32WithMeasure<'u>, y |> float32 |> Float32WithMeasure<'u>)
    static member ( *** ) (T,(x:float32<'u>,y)) = OverloadedOperators.CreateVector2(x,y)
    static member ( *** ) (T,(x:float<'u>,y)) = OverloadedOperators.CreateVector2(x,y)
    static member ( *** ) (T,(x:int<'u>,y)) = OverloadedOperators.CreateVector2(x,y)
let convert  =OverloadedOperators() 

(* testing *)
[<Measure>]
type m

convert *** (1<m>,1<m>)
convert *** (1.0<m>,1.0<m>)

它使用两个运算符并且过于冗长,但与您想要的相当接近

答案 1 :(得分:0)

let inline (@@) (x: float32<'u>) (y: float32<'u>) = new Vector2<'u>(x, y)怎么样?没有这个诀窍?它至少对我有用。

答案 2 :(得分:0)

从@ JohnPalmer的回答开始,我能够走到最后一英里;只是对我没想到的类型签名的简单更改。这是第一部分:

type OverloadedOperators() =
    static member CreateVector2 (_, x: float32<'u>, y: float32<'u>) = new Vector2<'u>(x, y)
    static member CreateVector2 (_, x: float<'u>, y: float<'u>) = new Vector2<'u>(x |> float32 |> Float32WithMeasure<'u>, y |> float32 |> Float32WithMeasure<'u>)
    static member CreateVector2 (_, x: int<'u>, y: int<'u>) = new Vector2<'u>(x |> float32 |> Float32WithMeasure<'u>, y |> float32 |> Float32WithMeasure<'u>)

let ops = new OverloadedOperators()

给运营商签名:

let inline (@@) (x: ^a) (y: ^b) = ((^T or ^a or ^b) : (static member CreateVector2 : ^T * ^a * ^b -> Vector2<'u>) (ops, x, y))

所以我们拥有它:成功的运算符重载适用于任何数字类型度量单位。