Единица измерения 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
,