Enheter
Flyttals- och signerade heltalsvärden i F# kan ha associerade måttenheter, som vanligtvis används för att ange längd, volym, massa och så vidare. Genom att använda kvantiteter med enheter gör du det möjligt för kompilatorn att kontrollera att aritmetiska relationer har rätt enheter, vilket hjälper till att förhindra programmeringsfel.
Kommentar
De här exemplen visar korrekthet i aritmetiska beräkningar som involverar måttenheter. Funktionen kan också användas för att lägga till typsäker anteckning med noll representationskostnader till andra typer, med tillvägagångssätt som FSharp.UMX-projekt .
Syntax
[<Measure>] type unit-name [ = measure ]
Kommentarer
Den tidigare syntaxen definierar enhetsnamn som en måttenhet. Den valfria delen används för att definiera ett nytt mått i termer av tidigare definierade enheter. Följande rad definierar till exempel måttet cm
(centimeter).
[<Measure>] type cm
Följande linje definierar måttet ml
(milliliter) som en kubikcentimeter (cm^3
).
[<Measure>] type ml = cm^3
I den tidigare syntaxen är mått en formel som omfattar enheter. I formler som omfattar enheter stöds integrerade krafter (positiva och negativa), blanksteg mellan enheter indikerar en produkt av de två enheterna, *
anger också en produkt av enheter och /
anger en kvot för enheter. För en ömsesidig enhet kan du antingen använda en negativ heltalskraft eller en /
som indikerar en separation mellan täljaren och nämnaren för en enhetsformel. Flera enheter i nämnaren ska omges av parenteser. Enheter avgränsade med blanksteg efter en /
tolkas som en del av nämnaren, men alla enheter som följer a *
tolkas som en del av täljaren.
Du kan använda 1 i enhetsuttryck, antingen ensam för att ange en dimensionslös kvantitet eller tillsammans med andra enheter, till exempel i täljaren. Till exempel skrivs enheterna för en hastighet som 1/s
, där s
anger sekunder. Parenteser används inte i enhetsformler. Du anger inte numeriska konverteringskonstanter i enhetsformler. Du kan dock definiera konverteringskonstanter med enheter separat och använda dem i enhetskontrollerade beräkningar.
Enhetsformler som betyder samma sak kan skrivas på olika motsvarande sätt. Kompilatorn konverterar därför enhetsformler till en konsekvent form, som konverterar negativa krafter till reciprokaler, grupperar enheter till en enda täljare och en nämnare och alfabetiserar enheterna i täljaren och nämnaren.
Till exempel konverteras kg m s^-2
enhetsformler och m /s s * kg
båda till kg m/s^2
.
Du använder måttenheter i flyttalsuttryck. Att använda flyttalsnummer tillsammans med associerade måttenheter ger en annan nivå av typsäkerhet och hjälper till att undvika fel i enhetsmatchning som kan uppstå i formler när du använder svagt typinskrivna flyttalsnummer. Om du skriver ett flyttalsuttryck som använder enheter måste enheterna i uttrycket matcha.
Du kan kommentera literaler med en enhetsformel inom vinkelparenteser, som du ser i följande exempel.
1.0<cm>
55.0<miles/hour>
Du placerar inte ett blanksteg mellan talet och vinkelparentesen. Du kan dock inkludera ett literalsuffix som f
, som i följande exempel.
// The f indicates single-precision floating point.
55.0f<miles/hour>
En sådan anteckning ändrar typen av literal från dess primitiva typ (till exempel float
) till en dimensionerad typ, till exempel float<cm>
eller, i det här fallet, float<miles/hour>
. En enhetsanteckning av <1>
anger en dimensionslös kvantitet och dess typ motsvarar den primitiva typen utan en enhetsparameter.
Typen av en måttenhet är en flyttal eller signerad integraltyp tillsammans med en extra enhetsanteckning som anges inom hakparenteser. När du skriver typen av konvertering från g
(gram) till kg
(kg) beskriver du därför typerna på följande sätt.
let convertg2kg (x : float<g>) = x / 1000.0<g/kg>
Måttenheter används för kompileringstidsenhetskontroll men sparas inte i körningsmiljön. Därför påverkar de inte prestanda.
Måttenheter kan tillämpas på alla typer, inte bara flyttalstyper. Men endast flyttalstyper, signerade integraltyper och decimaltyper stöder dimensionerade kvantiteter. Därför är det bara meningsfullt att använda måttenheter på de primitiva typerna och på aggregeringar som innehåller dessa primitiva typer.
I följande exempel visas hur måttenheter används.
// 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
I följande kodexempel visas hur du konverterar från ett dimensionslöst flyttalnummer till ett dimensionsvärde för flyttal. Du multiplicerar bara med 1,0 och tillämpar dimensionerna på 1.0. Du kan abstrahera detta till en funktion som degreesFahrenheit
.
När du skickar dimensionsvärden till funktioner som förväntar sig dimensionslösa flyttalsnummer måste du också avbryta enheterna eller gjutna till float
med hjälp av operatorn float
. I det här exemplet dividerar du med 1.0<degC>
för argumenten till printf
eftersom printf
förväntar sig dimensionslösa kvantiteter.
[<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."
I följande exempelsession visas utdata från och indata till den här koden.
Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is 32.22.
Primitiva typer som stöder måttenheter
Följande typer eller typförkortningsalias stöder måttenhetsanteckningar:
F#-alias | CLR-typ |
---|---|
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 |
Du kan till exempel kommentera ett osignerat heltal på följande sätt:
[<Measure>]
type days
let better_age = 3u<days>
Tillägg av osignerade heltalstyper till den här funktionen dokumenteras i F# RFC FS-1091.
Fördefinierade måttenheter
Ett enhetsbibliotek är tillgängligt i FSharp.Data.UnitSystems.SI
namnområdet. Den innehåller SI-enheter i både deras symbolform (t.ex m
. för mätare) i UnitSymbols
undernamnområdet och i deras fullständiga namn (t.ex meter
. för mätare) i UnitNames
undernamnet.
Använda allmänna enheter
Du kan skriva allmänna funktioner som fungerar på data som har en associerad måttenhet. Det gör du genom att ange en typ tillsammans med en allmän enhet som en typparameter, som du ser i följande kodexempel.
// 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
Skapa samlingstyper med allmänna enheter
Följande kod visar hur du skapar en aggregeringstyp som består av enskilda flyttalsvärden som har enheter som är generiska. På så sätt kan en enskild typ skapas som fungerar med en mängd olika enheter. Dessutom bevarar generiska enheter typsäkerhet genom att säkerställa att en allmän typ som har en uppsättning enheter är en annan typ än samma generiska typ med en annan uppsättning enheter. Grunden för den här tekniken är att Measure
attributet kan tillämpas på typparametern.
// 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> }
Enheter vid körning
Måttenheter används för kontroll av statiska typer. När flyttalsvärden kompileras elimineras måttenheterna, så enheterna går förlorade vid körning. Därför är alla försök att implementera funktioner som är beroende av att kontrollera enheterna vid körning inte möjligt. Det går till exempel inte att implementera en ToString
funktion för att skriva ut enheterna.
Omvandlingar
Om du vill konvertera en typ som har enheter (till exempel float<'u>
) till en typ som inte har enheter kan du använda standardkonverteringsfunktionen. Du kan till exempel använda float
för att konvertera till ett float
värde som inte har enheter, som du ser i följande kod.
[<Measure>]
type cm
let length = 12.0<cm>
let x = float length
Om du vill konvertera ett enhetslöst värde till ett värde som har enheter kan du multiplicera med ett värde på 1 eller 1,0 som kommenteras med lämpliga enheter. Men för att skriva samverkanslager finns det också några explicita funktioner som du kan använda för att konvertera enhetslösa värden till värden med enheter. Dessa finns i modulen FSharp.Core.LanguagePrimitives . Om du till exempel vill konvertera från en enhetslös float
till en float<cm>
använder du FloatWithMeasure, som du ser i följande kod.
open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x