F# 测量单位:从弧度到速度(米/秒)

发布于 2024-12-18 16:05:01 字数 743 浏览 2 评论 0原文

假设我已经定义了一个用于处理 3 维向量和测量单位的 F# 模块:

[<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>
    }

在某处我有一个以弧度表示的角度:

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

现在我必须声明一个 Vector3(速度),使用该角度来计算其分量。我尝试了类似的方法:

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

上面的代码无法编译,因为 Vector3 需要 X 和 Y 中的值,但 Sin 返回一个浮点数。

我该如何解决这个问题?如果可能的话,我想在测量单位之间执行转换,而不是强制转换,这样编译器就可以保证我在将角度转换为速度时做正确的事情。

有什么建议吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

寻找一个思念的角度 2024-12-25 16:05:01

这里有几个问题:
cos 需要一个无单位的值,因此您必须去掉 angle 的单位;鉴于速度需要 float 而不是 float32,您不妨直接转换为 float (这会删除单位)。

然后,您需要重新安装这些装置。在度量单位的第一个版本中,您可以通过简单地乘以适当度量的 1 来完成此操作。现在有 LanguagePrimitives.FloatWithMeasure,它更正确,但稍微更详细。

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

除此之外,10 弧度是一个有趣的角度...

(请注意,cossin 是内置的)

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)

断肠人 2024-12-25 16:05:01

好的,这是另一种方法,演示如何结合标量速度和 2d 方向来真正利用您的单位:

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

这给出了:

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

您可以通过向 Vector3 类型添加一个运算符来更进一步:

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

这意味着您然后可以这样做:

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

显然,你仍然没有真正在其中“使用”弧度,但这是预期的,无论如何弧度都是“非”单位。您可以使用它们的一种方法是如果您希望能够管理弧度度数。您可以在 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>)

这看起来不错,但不幸的是它无法编译,因为一旦元组、函数和/或,方法“ofAngle”与此类型中的另一个方法具有相同的名称和签名测量单位被删除。您可以查看这个问题了解更多详情

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

醉南桥 2024-12-25 16:05:01

如果可能的话我想在度量之间执行转换
单位,而不是强制转换,这样编译器就可以
保证我在变换角度的同时做正确的事
变成速度。

你要求的是矛盾的事情。测量单位通过使用类型推断来强制程序中的单位来帮助保证程序的正确性,因此必须显式地进行转换。

正如@Benjol 所说,您必须在有维数据和无量纲数据之间进行转换。下面的代码是一个非正式版本,我通过乘以其单位值将 float 转换为 float

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<_>
               } 

请注意,您只需为 X 指定单位,其他单位是根据 Vector3 的类型声明推断的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文