F# Измерение единицы: от радиан до скорости в M/s

StackOverflow https://stackoverflow.com/questions/8308414

  •  25-10-2019
  •  | 
  •  

Вопрос

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top