Question

let's say I've defined an F# module for handling 3-dimensional vectors and measure unit:

[<Measure>]
type m
[<Measure>]
type s
[<Measure>]
type v = m/s
[<Measure>]
type rad

type Vector3<[<Measure>] 'a> =
    {
    X : float<'a>
    Y : float<'a>
    Z : float<'a>
    }

And somewhere I have an angle expressed in radians:

let angle:float32<rad> = 0.5f<rad>

Now I have to declare a Vector3 (velocity) using the angle to calculate its components. I tried something like :

let velocity : Vector3<m/s> = { X = Math.Cos(angle);Y = Math.Sin(angle);Z = 0.0<m/s>} //don't compile

The code above doesn't compile because Vector3 is expecting a value in X and Y but Sin returns a float.

How can I solve this problem? If it possible I would like to perform a conversion between measure units, instead of a cast, in such a way that the compiler can guarantee that I'm doing the right thing while transforming an angle into a velocity.

Any suggestion?

Was it helpful?

Solution

Several problems here: cos is expecting a unitless value, so you have to take the units off angle; given that velocity is expecting float and not float32, you might as well just convert directly to float (which removes the units).

Then, you need to put the units back on. In first versions of units of measure, you could do this by simply multiplying by 1 in the appropriate measure. Now there is LanguagePrimitives.FloatWithMeasure, which is more correct, but slightly more verbose.

let velocity =
 {
     X = angle |> float |> cos |> LanguagePrimitives.FloatWithMeasure<m/s> ;
     Y = angle |> float |> sin |> LanguagePrimitives.FloatWithMeasure<m/s> ;
     Z = 0.0<m/s> ;
 }

That aside, 10 radians is a funny angle...

(Note that cos and sin are built-in)

OTHER TIPS

OK, here's another take, demonstrating how you could combine a scalar speed and a 2d-direction to really make use of your units:

let vvector (velocity:float<'u>) (xydirection:float<rad>) =
    { 
        X = cos (float xydirection) * velocity;
        Y = sin (float xydirection) * velocity;
        Z = 0.<_>
    }

Which gives:

> vvector 15.3<m/s> angle<rad>;;
val it : Vector3<m/s> = {X = 13.4270132;
                         Y = 7.335210741;
                         Z = 0.0;}

You could take it a step further by adding an operator to your Vector3 type:

static member (*) (v1: Vector3<'u>, n: float<'v>) =
    { X = v1.X * n; Y = v1.Y * n; Z = v1.Z * n }

Which would mean you could then do this:

let vvector2 (velocity:float<'u>) (xydirection:float<rad>) =
    { X = cos(float xydirection); Y = sin(float xydirection); Z = 0. } * velocity

Obviously, you're still not really 'using' your radians in there, but that's sort of expected, radians are sort of 'non' units anyway. One way that you could make use of them is if you wanted to be able to manage radians and degrees. You could add two other static members on your Vector3:

static member ofAngle (ang:float<rad>) = 
    { X = cos(float ang); Y = sin(float ang); Z = 0.0 }
static member ofAngle (ang:float<degree>) = 
    Vector3<'u>.ofAngle(ang * 2.<rad> * System.Math.PI / 360.<degree>)

That looks nice, but unfortunately it won't compile because The method 'ofAngle' has the same name and signature as another method in this type once tuples, functions and/or units of measure are erased. You can check out this question for more details

If it possible I would like to perform a conversion between measure units, instead of a cast, in such a way that the compiler can guarantee that I'm doing the right thing while transforming an angle into a velocity.

You ask for contradictory things. Unit of Measure helps to guarantee correctness of programs by using type inference to enforce units in the programs, so conversion has to be done explicitly.

As @Benjol said, you have to convert between dimensional and dimensionless data. Below code is a informal version where I convert from float to float<m/s> by multiplying with its unit value:

let angle:float32<rad> = 0.5f<rad>

let velocity = {
                X = sin (float angle) * 1.0<m/s> ; 
                Y = cos (float angle) * 1.0<_> ; 
                Z = 0.0<_>
               } 

Note that you only have to specify unit for X, other units are inferred based on type declaration of Vector3.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top