F# Измерение единицы: от радиан до скорости в M/s
Вопрос
Допустим, я определил модуль 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 возвращает поплавок.
Как я могу решить эту проблему? Если это возможно, я хотел бы выполнить преобразование между единицами измерения, а не с актерским составом, таким образом, что компилятор может гарантировать, что я поступаю правильно, преобразуя угол в скорость.
Любое предложение?
Решение
Несколько проблем здесь: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
Очевидно, что вы все еще не «используете» свои радианы там, но это в некотором роде ожидается, радианы в любом случае являются своего рода «не» подразделениями. Один из способов, которым ты мог Используйте их, если вы хотите иметь возможность управлять радианами а также градусы. Вы можете добавить двух других статических участников в свой вектор3:
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
.