Единица измерения F #: от радианов до скорости в м / с - PullRequest
3 голосов
/ 29 ноября 2011

допустим, я определил модуль 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 ]

4 голосов
/ 29 ноября 2011

Несколько проблем здесь: 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 встроены)

1 голос
/ 29 ноября 2011

Хорошо, вот еще один дубль, демонстрирующий, как вы можете объединить скалярную скорость и 2-мерное направление, чтобы реально использовать ваши единицы:

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

1 голос
/ 29 ноября 2011

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

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

Как сказал @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.

...