带有度量单位的重载函数

Overloaded function with units of measure

TL;DR 我如何编写一个 "overloaded" 函数来处理所有带有单位 的数字类型使用相同的单位输入 (float32<m> -> Vector2<m>, int<kg> -> Vector2<kg>)?

我有一个漂亮的小 Vector2 class,但我想允许将度量单位附加到它,所以我基本上是这样定义它的(但功能比这里包含的要多得多):

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>)。在我将单位附加到这个 class 之前,它真的很容易:

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

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

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>等 我不确定如何写这最后的一小段。我想要的有可能吗?或者我应该忍受不断地在任何地方使用 Vector2 构造函数?

编辑 感谢@JohnPalmer,我已经非常接近了,我在他的回答的基础上提出了最后一点:let inline vec2 p = convert *** p。现在最后一步是 "un-tuple" 这个函数,让它成为一个中缀运算符,但这似乎是不可能的,因为它一开始甚至不知道它总是一个元组。我想我可能已经尽可能地使用 F#,但如果我错了请纠正我!与此同时,我将选择 vec2 (10.<m>, 20.<m>),这比我每次调用构造函数时都必须内联执行所有混乱的单元类型转换要好 很多

let inline (@@) (x: float32<'u>) (y: float32<'u>) = new Vector2<'u>(x, y) 怎么样 - 不是这样吗?至少对我有用。

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

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>)

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

我能够从@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))

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