Единица измерения F#: от радианов до скорости в м / с

Допустим, я определил модуль 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 возвращает число с плавающей запятой.

Как я могу решить эту проблему? Если это возможно, я бы хотел выполнить преобразование между единицами измерения, а не приведением, таким образом, чтобы компилятор мог гарантировать, что я поступаю правильно при преобразовании угла в скорость.

Любое предложение?

3 ответа

Решение

Несколько проблем здесь: 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 радиан это забавный угол...

(Обратите внимание, что cos а также sin встроены)

Хорошо, вот еще один дубль, демонстрирующий, как вы можете объединить скалярную скорость и 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>)

Это выглядит красиво, но, к сожалению, не скомпилируется, потому что 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. Вы можете проверить этот вопрос для более подробной информации

Если это возможно, я бы хотел выполнить преобразование между единицами измерения, а не приведением, таким образом, чтобы компилятор мог гарантировать, что я поступаю правильно при преобразовании угла в скорость.

Вы просите противоречивых вещей. Единица измерения помогает гарантировать правильность программ, используя вывод типов для принудительного применения единиц измерения в программах, поэтому преобразование должно выполняться явно.

Как сказал @Benjol, вы должны конвертировать между размерными и безразмерными данными. Ниже код является неофициальной версией, где я конвертирую из float в float<m/s> умножением на единицу стоимости:

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,

Другие вопросы по тегам