Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloaded function with units of measure

Tags:

.net

generics

f#

TL;DR How can I write an "overloaded" function that can handle all numeric types with units and a separate type with that same unit (float32<m> -> Vector2<m>, int<kg> -> Vector2<kg>)?

I have a nice little Vector2 class, but I want to allow units of measure to be attached to it, so I've essentially defined it like this (but with a lot more functionality than included here):

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

I've been able to write overloaded arithmetic operators without much difficulty as well (e.g. static member (+) ...), but I've been really struggling 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 attached units to this class, it was really easy:

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

So far I've written a helper class to deal with 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 the 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'm not sure how to write this last little piece. Is what I want even possible? Or should I just put up with constantly having to use the Vector2 constructor everywhere?

EDIT I've gotten really close thanks to @JohnPalmer and I've come up with this last little bit, building on his answer: let inline vec2 p = convert *** p. Now the last step would be to "un-tuple" this function to allow it to become an infix operator, but this seems to be impossible since it doesn't even really know its always a tuple in the first place. I think I may have come as far as I can with F# here, but correct me if I'm wrong! In the mean time I'll settle with vec2 (10.<m>, 20.<m>), which is a lot better than having to do all of the messy unit type casts inline every single time I call the constructor.

like image 226
Jwosty Avatar asked Nov 17 '25 18:11

Jwosty


1 Answers

Here is one slightly hacky solution that builds from the standard trick we use in this sort of 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 is reasonably close to what you want

like image 96
John Palmer Avatar answered Nov 19 '25 09:11

John Palmer