Jednostki miary
Wartości liczb zmiennoprzecinkowych i liczb całkowitych ze znakiem w języku F# mogą mieć skojarzone jednostki miary, które są zwykle używane do wskazywania długości, objętości, masy itd. Korzystając z ilości z jednostkami, można włączyć kompilator w celu sprawdzenia, czy relacje arytmetyczne mają poprawne jednostki, co pomaga zapobiegać błędom programowania.
Uwaga
W tych przykładach pokazano poprawność w obliczeniach arytmetycznych obejmujących jednostki miary. Funkcja może być również używana do dodawania bezpiecznej adnotacji typu z zerowymi kosztami reprezentacji do innych typów przy użyciu podejścia takiego jak projekt FSharp.UMX .
Składnia
[<Measure>] type unit-name [ = measure ]
Uwagi
Poprzednia składnia definiuje nazwę jednostki jako jednostkę miary. Opcjonalna część służy do definiowania nowej miary pod względem wcześniej zdefiniowanych jednostek. Na przykład następujący wiersz definiuje miarę cm
(centymetr).
[<Measure>] type cm
Poniższa linia definiuje miarę ml
(mililiter) jako centymetr sześcienny (cm^3
).
[<Measure>] type ml = cm^3
W poprzedniej składni miara jest formułą obejmującą jednostki. W formułach, które obejmują jednostki, moce całkowite są obsługiwane (dodatnie i ujemne), odstępy między jednostkami wskazują również produkt dwóch jednostek *
i /
wskazuje iloraz jednostek. W przypadku jednostki wzajemnej można użyć ujemnej mocy całkowitej lub wartości /
wskazującej separację między licznikiem a mianownikiem formuły jednostkowej. Wiele jednostek w mianowniku powinno być otoczony nawiasami. Jednostki oddzielone spacjami po /
interpretowaniu jako część mianownika, ale wszystkie jednostki po *
obiekcie są interpretowane jako część licznika.
W wyrażeniach jednostkowych można użyć wartości 1, aby wskazać ilość bez wymiarów lub razem z innymi jednostkami, takimi jak w liczniku. Na przykład jednostki stawki będą zapisywane jako 1/s
, gdzie s
wskazuje sekundy. Nawiasy nie są używane w formułach jednostkowych. Nie określasz stałych konwersji liczbowej w formułach jednostkowych; można jednak definiować stałe konwersji z jednostkami oddzielnie i używać ich w obliczeniach sprawdzanych jednostkowo.
Formuły jednostkowe, które oznaczają, że to samo można napisać na różne równoważne sposoby. W związku z tym kompilator konwertuje formuły jednostek na spójną formę, która konwertuje negatywne moce na wzajemne, grupuje jednostki na pojedynczy licznik i mianownik, a także alfabetyzuje jednostki w liczniku i mianowniku.
Na przykład formuły jednostkowe kg m s^-2
i m /s s * kg
są konwertowane na kg m/s^2
.
Jednostki miary są używane w wyrażeniach zmiennoprzecinkowych. Użycie liczb zmiennoprzecinkowych wraz ze skojarzonymi jednostkami miary dodaje kolejny poziom bezpieczeństwa typu i pomaga uniknąć błędów niezgodności jednostek, które mogą wystąpić w formułach, gdy używasz słabo wpisanych liczb zmiennoprzecinkowych. Jeśli napiszesz wyrażenie zmiennoprzecinkowe używające jednostek, jednostki w wyrażeniu muszą być zgodne.
Można dodawać adnotacje do literałów za pomocą formuły jednostki w nawiasach kątowych, jak pokazano w poniższych przykładach.
1.0<cm>
55.0<miles/hour>
Nie umieszczasz odstępu między liczbą a nawiasem kątowym; można jednak uwzględnić sufiks literału, taki jak f
, jak w poniższym przykładzie.
// The f indicates single-precision floating point.
55.0f<miles/hour>
Taka adnotacja zmienia typ literału z jego typu pierwotnego (na float
przykład ) na typ wymiarowy, taki jak float<cm>
lub, w tym przypadku, float<miles/hour>
. Adnotacja jednostkowa <1>
wskazuje bezwymiarową ilość, a jego typ jest odpowiednikiem typu pierwotnego bez parametru jednostkowego.
Typ jednostki miary to zmiennoprzecinkowa lub podpisany typ całkowity wraz z dodatkową adnotacją jednostkową wskazaną w nawiasach kwadratowych. W związku z tym podczas pisania typu konwersji z g
(gramy) na kg
(kilogramy) należy opisać typy w następujący sposób.
let convertg2kg (x : float<g>) = x / 1000.0<g/kg>
Jednostki miary są używane do sprawdzania jednostek czasu kompilacji, ale nie są utrwalane w środowisku czasu wykonywania. W związku z tym nie wpływają one na wydajność.
Jednostki miary można stosować do dowolnego typu, a nie tylko typów zmiennoprzecinkowych; jednak tylko typy zmiennoprzecinkowe, typy ze znakiem całkowitym i typy dziesiętne obsługują ilości wymiarowe. W związku z tym warto używać tylko jednostek miary dla typów pierwotnych i agregacji zawierających te typy pierwotne.
Poniższy przykład ilustruje użycie jednostek miary.
// Mass, grams.
[<Measure>] type g
// Mass, kilograms.
[<Measure>] type kg
// Weight, pounds.
[<Measure>] type lb
// Distance, meters.
[<Measure>] type m
// Distance, cm
[<Measure>] type cm
// Distance, inches.
[<Measure>] type inch
// Distance, feet
[<Measure>] type ft
// Time, seconds.
[<Measure>] type s
// Force, Newtons.
[<Measure>] type N = kg m / s^2
// Pressure, bar.
[<Measure>] type bar
// Pressure, Pascals
[<Measure>] type Pa = N / m^2
// Volume, milliliters.
[<Measure>] type ml
// Volume, liters.
[<Measure>] type L
// Define conversion constants.
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>
let cmPerMeter : float<cm/m> = 100.0<cm/m>
let cmPerInch : float<cm/inch> = 2.54<cm/inch>
let mlPerCubicCentimeter : float<ml/cm^3> = 1.0<ml/cm^3>
let mlPerLiter : float<ml/L> = 1000.0<ml/L>
// Define conversion functions.
let convertGramsToKilograms (x : float<g>) = x / gramsPerKilogram
let convertCentimetersToInches (x : float<cm>) = x / cmPerInch
Poniższy przykład kodu ilustruje sposób konwertowania z bezwymiarowej liczby zmiennoprzecinkowej na wartość zmiennoprzecinkową wymiarową. Wystarczy pomnożyć wartość 1.0, stosując wymiary do wersji 1.0. Można to abstrahować do funkcji, takiej jak degreesFahrenheit
.
Ponadto po przekazaniu wartości wymiarowych do funkcji, które oczekują liczb zmiennoprzecinkowych bez wymiarów, należy anulować jednostki lub rzutować je float
przy użyciu float
operatora . W tym przykładzie argumenty printf
są dzielone według1.0<degC>
, ponieważ printf
oczekuje się ilości wymiarowych.
[<Measure>] type degC // temperature, Celsius/Centigrade
[<Measure>] type degF // temperature, Fahrenheit
let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF>
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>)
// Define conversion functions from dimensionless floating point values.
let degreesFahrenheit temp = temp * 1.0<degF>
let degreesCelsius temp = temp * 1.0<degC>
printfn "Enter a temperature in degrees Fahrenheit."
let input = System.Console.ReadLine()
let parsedOk, floatValue = System.Double.TryParse(input)
if parsedOk
then
printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
else
printfn "Error parsing input."
W poniższej przykładowej sesji przedstawiono dane wyjściowe z i dane wejściowe do tego kodu.
Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is 32.22.
Typy pierwotne obsługujące jednostki miary
Następujące typy lub aliasy skrótów typów obsługują adnotacje typu unit-of-measure:
Alias języka F# | Typ CLR |
---|---|
float32 /single |
System.Single |
float /double |
System.Double |
decimal |
System.Decimal |
sbyte /int8 |
System.SByte |
int16 |
System.Int16 |
int /int32 |
System.Int32 |
int64 |
System.Int64 |
byte /uint8 |
System.Byte |
uint16 |
System.UInt16 |
uint /uint32 |
System.UInt32 |
uint64 |
System.UIn64 |
nativeint |
System.IntPtr |
unativeint |
System.UIntPtr |
Można na przykład dodać adnotację do niepodpisanej liczby całkowitej w następujący sposób:
[<Measure>]
type days
let better_age = 3u<days>
Dodanie niepodpisanych typów liczb całkowitych do tej funkcji jest udokumentowane w F# RFC FS-1091.
Wstępnie zdefiniowane jednostki miary
Biblioteka jednostek jest dostępna w FSharp.Data.UnitSystems.SI
przestrzeni nazw. Obejmuje ona jednostki SI w postaci symboli (na m
przykład dla miernika) w UnitSymbols
przestrzeni nazw podrzędnych i w pełnej nazwie (na przykład meter
dla miernika) w przestrzeni nazw podrzędnych UnitNames
.
Korzystanie z jednostek ogólnych
Możesz napisać funkcje ogólne, które działają na danych, które mają skojarzona jednostka miary. W tym celu należy określić typ razem z jednostką ogólną jako parametr typu, jak pokazano w poniższym przykładzie kodu.
// Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s
let genericSumUnits ( x : float<'u>) (y: float<'u>) = x + y
let v1 = 3.1<m/s>
let v2 = 2.7<m/s>
let x1 = 1.2<m>
let t1 = 1.0<s>
// OK: a function that has unit consistency checking.
let result1 = genericSumUnits v1 v2
// Error reported: mismatched units.
// Uncomment to see error.
// let result2 = genericSumUnits v1 x1
Tworzenie typów kolekcji przy użyciu jednostek ogólnych
Poniższy kod pokazuje, jak utworzyć typ agregujący składający się z poszczególnych wartości zmiennoprzecinkowych, które mają jednostki ogólne. Dzięki temu można utworzyć pojedynczy typ, który działa z różnymi jednostkami. Ponadto jednostki ogólne zachowują bezpieczeństwo typu, zapewniając, że typ ogólny, który ma jeden zestaw jednostek, jest innym typem niż ten sam typ ogólny z innym zestawem jednostek. Podstawą tej techniki jest to, że Measure
atrybut można zastosować do parametru typu.
// Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s
// Define a vector together with a measure type parameter.
// Note the attribute applied to the type parameter.
type vector3D<[<Measure>] 'u> = { x : float<'u>; y : float<'u>; z : float<'u>}
// Create instances that have two different measures.
// Create a position vector.
let xvec : vector3D<m> = { x = 0.0<m>; y = 0.0<m>; z = 0.0<m> }
// Create a velocity vector.
let v1vec : vector3D<m/s> = { x = 1.0<m/s>; y = -1.0<m/s>; z = 0.0<m/s> }
Jednostki w czasie wykonywania
Jednostki miary są używane do sprawdzania typów statycznych. Gdy wartości zmiennoprzecinkowe są kompilowane, jednostki miary zostaną wyeliminowane, więc jednostki zostaną utracone w czasie wykonywania. W związku z tym każda próba zaimplementowania funkcji, która zależy od sprawdzania jednostek w czasie wykonywania, nie jest możliwa. Na przykład zaimplementowanie ToString
funkcji w celu wydrukowania jednostek nie jest możliwe.
Konwersje
Aby przekonwertować typ zawierający jednostki (na przykład ) na typ, float<'u>
który nie ma jednostek, można użyć standardowej funkcji konwersji. Na przykład można użyć float
polecenia , aby przekonwertować na float
wartość, która nie ma jednostek, jak pokazano w poniższym kodzie.
[<Measure>]
type cm
let length = 12.0<cm>
let x = float length
Aby przekonwertować wartość bezjednostki na wartość zawierającą jednostki, można pomnożyć wartość 1 lub 1,0, która jest oznaczona za pomocą odpowiednich jednostek. Jednak w przypadku pisania warstw współdziałania istnieją również pewne jawne funkcje, których można użyć do konwertowania wartości bezjednostki na wartości z jednostkami. Znajdują się one w module FSharp.Core.LanguagePrimitives . Aby na przykład przekonwertować element z bezjednostki float
na float<cm>
element , użyj metody FloatWithMeasure, jak pokazano w poniższym kodzie.
open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x