Overloaded function with units

TL; DR How do I write an "overloaded" function that can handle all numeric types with ones and a separate type with the same device ( float32<m> -> Vector2<m>

, int<kg> -> Vector2<kg>

)?

I have a small Vector2 class, but I want the units to be tied to it, so I essentially defined it like this (but with much more functionality than here):

type Vector2<[<Measure>] 'u>(x: float32<'u>, y: float32<'u>) =
    member this.X = x
    member this.Y = y

      

I also managed to write overloaded arithmetic operators (like, static member (+) ...

), but I was really trying to write a constructor operator (called @@

) that can work with all numeric types as well as units (allowing me to write 1<m> @@ 2.5<m>

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

. Before I hooked up units to this class there was very simple:

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

      

So far, I've written a helper class to handle this:

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

      

but it seems impossible to write an appropriate type constraint to handle this call case. For example, something like this doesn't work

let inline (@@) (x: 'u when 'u : (static member CreateVector2 : 'u * 'u -> Vector2<'v>)) y = OverloadedOperators.CreateVector2 (x, y)

      

because The type 'u is not compatible with float32<'v>

, The type 'u is not compatible with int<'v>

etc. I don't know how to write this last piece. Do I want this to be possible? Or should I just endure having to use the constructor all the time Vector2

?

EDIT I got very close thanks @JohnPalmer, and I came up with this last bit, leaning on his answer let inline vec2 p = convert *** p

. Now, the last step would be to "disable" this function to allow it to become an infix operator, but that seems impossible since it doesn't even know its always tuple in the first place. I think that maybe I came with F # here, but correct me if I'm wrong! At the same time, I'll agree vec2 (10.<m>, 20.<m>)

that it's much better than doing all the messy string casts every time I call the constructor.

+3


source to share


3 answers


I was able to walk the last mile starting with @JohnPalmer's answer; just a simple change to the type signature that I hadn't thought of. Here's the first part:

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

      

And the operator's signature:



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

      

So we have this: a successful operator overload that works on any numeric type and unit.

0


source


Here is one weak solution, based on the standard trick we use in this case:

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

      



It uses two operators and is overly verbose, but close enough to what you want

+1


source


How about let inline (@@) (x: float32<'u>) (y: float32<'u>) = new Vector2<'u>(x, y)

- isn't that a trick? It was at least for me.

0


source







All Articles