Maßeinheiten

Gleitkommawerte und Integerwerte mit Vorzeichen in F# können zugeordnete Maßeinheiten aufweisen, die in der Regel Länge, Volumen, Masse usw. angeben. Indem Sie Mengen mit Einheiten verwenden, ermöglichen Sie, dass der Compiler überprüfen kann, ob arithmetische Beziehungen die richtigen Einheiten aufweisen, um Programmierfehler zu vermeiden.

Hinweis

Diese Beispiele zeigen die Korrektheit bei arithmetischen Berechnungen mit Maßeinheiten, das Feature kann auch zum Hinzufügen von typsicheren Anmerkungen mit null Darstellungskosten zu anderen Typen genutzt werden, mit einem Ansatz wie das FSharp.UMX-Projekt.

Syntax

[<Measure>] type unit-name [ = measure ]

Bemerkungen

In der vorherigen Syntax ist unit-name als Maßeinheit definiert. Der optionale Teil wird verwendet, um eine neue Maßeinheit in Bezug auf zuvor definierte Einheiten zu definieren. Die folgende Zeile definiert beispielsweise die Maßeinheit cm (Zentimeter).

[<Measure>] type cm

Die folgende Zeile definiert die Maßeinheit ml (Milliliter) als Kubikzentimeter (cm^3).

[<Measure>] type ml = cm^3

In der vorherigen Syntax ist measure eine Formel, die Einheiten umfasst. In Formeln mit Einheiten werden Integerexponenten (positiv und negativ) unterstützt, Leerzeichen zwischen Einheiten geben ein Produkt der beiden Einheiten an, * gibt ebenfalls ein Produkt von Einheiten an, und / gibt einen Quotienten von Einheiten an. Für eine reziproke Einheit können Sie entweder einen negativen Integerexponenten oder / als Trennung zwischen Zähler und Nenner einer Einheitenformel verwenden. Mehrere Einheiten im Nenner sollten in Klammern eingeschlossen werden. Nach / durch Leerzeichen getrennte Einheiten werden als Teil des Nenners interpretiert, aber alle Einheiten nach * werden als Teil des Zählers interpretiert.

Sie können „1“ in Einheitenausdrücken verwenden, alleinstehend zum Angeben einer dimensionslosen Menge oder gemeinsam mit anderen Einheiten, z. B. im Zähler. Beispielsweise werden die Einheiten für eine Rate als 1/s geschrieben, wobei s für Sekunden steht. Klammern werden in Einheitenformeln nicht verwendet. Sie geben in Einheitenformeln keine numerischen Konvertierungskonstanten an, können jedoch Konvertierungskonstanten mit Einheiten separat definieren und in Berechnungen mit geprüften Einheiten verwenden.

Es gibt verschiedene äquivalente Arten, Einheitenformeln zu schreiben, die dasselbe bedeuten. Daher konvertiert der Compiler Einheitenformeln in eine konsistente Form, die negative Exponenten in Kehrwerte konvertiert, Einheiten in einen einzelnen Zähler und einen Nenner gruppiert und die Einheiten im Zähler und Nenner alphabetisch ordnet.

Beispielsweise werden die Einheitenformeln kg m s^-2 und m /s s * kg beide in kg m/s^2 konvertiert.

Sie verwenden Maßeinheiten in Gleitkommaausdrücken. Die Verwendung von Gleitkommazahlen mit zugeordneten Maßeinheiten sorgt für eine weitere Ebene der Typsicherheit. Außerdem hilft sie dabei, Fehler mit nicht übereinstimmenden Einheiten zu vermeiden, die in Formeln mit schwach typisierten Gleitkommazahlen auftreten können. Wenn Sie einen Gleitkommaausdruck mit Einheiten schreiben, müssen die Einheiten im Ausdruck übereinstimmen.

Sie können Literale mit einer Einheitenformel in spitzen Klammern versehen, wie in den folgenden Beispielen gezeigt.

1.0<cm>
55.0<miles/hour>

Sie platzieren zwischen der Zahl und der eckigen Klammer kein Leerzeichen, können jedoch ein Literalsuffix wie f im folgenden Beispiel einschließen.

// The f indicates single-precision floating point.
55.0f<miles/hour>

Eine solche Anmerkung ändert den Typ des Literals vom primitiven Typ (z. B. float) in einen dimensionierten Typ wie float<cm> oder in diesem Fall float<miles/hour>. Die Einheitenanmerkung <1> gibt eine dimensionslose Menge an, deren Typ dem primitiven Typ ohne Einheitenparameter entspricht.

Der Typ einer Maßeinheit ist ein Gleitkomma- oder vorzeichenbehafteter Integertyp zusammen mit einer zusätzlichen Einheitenanmerkung, die in Klammern angegeben ist. Wenn Sie also den Typ einer Konvertierung von g (Gramm) in kg (Kilogramm) schreiben, beschreiben Sie die Typen wie folgt.

let convertg2kg (x : float<g>) = x / 1000.0<g/kg>

Maßeinheiten werden für die Überprüfung der Einheit beim Kompilieren verwendet, aber nicht in der Laufzeitumgebung beibehalten. Daher wirken sie sich nicht auf die Leistung aus.

Maßeinheiten können auf jeden Typ angewandt werden, nicht nur auf Gleitkommatypen. Allerdings unterstützen nur Gleitkommatypen, Integertypen mit Vorzeichen und Dezimaltypen dimensionierte Mengen. Daher ist es nur sinnvoll, Maßeinheiten für die primitiven Typen zu verwenden sowie für Aggregate, die diese primitiven Typen enthalten.

Im folgenden Beispiel wird die Verwendung von Maßeinheiten veranschaulicht.

// 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

Im folgenden Codebeispiel wird die Konvertierung von einer dimensionslosen Gleitkommazahl in einen dimensionierten Gleitkommawert veranschaulicht. Sie multiplizieren einfach mit 1,0 und wenden die Dimensionen auf 1,0 an. Dies können Sie in eine Funktion wie degreesFahrenheit abstrahieren.

Wenn Sie dimensionierte Werte an Funktionen übergeben, die dimensionslose Gleitkommazahlen erwarten, müssen Sie außerdem die Einheiten kürzen oder mithilfe des float-Operators in float umwandeln. In diesem Beispiel dividieren Sie die Argumente für printf durch 1.0<degC> da printf dimensionslose Mengen erwartet.

[<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."

Die folgende Beispielsitzung zeigt die Ausgaben und Eingaben für diesen Code.

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.

Primitive Typen mit Unterstützung von Maßeinheiten

Die folgenden Typen oder Typabkürzungsaliase unterstützen Anmerkungen zu Maßeinheiten:

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

Sie können z. B. eine ganze Zahl ohne Vorzeichen wie folgt mit einer Anmerkung versehen:

[<Measure>]
type days

let better_age = 3u<days>

Das Hinzufügen von Integertypen ohne Vorzeichen zu diesem Feature ist in F#-RFC FS-1091 dokumentiert.

Vordefinierte Maßeinheiten

Im Namespace FSharp.Data.UnitSystems.SI ist eine Einheitenbibliothek verfügbar. Sie enthält SI-Einheiten sowohl in Symbolform (z. B. m für Meter) im Unternamespace UnitSymbols als auch mit vollständigem Namen (z. B. meter für Meter) im Unternamespace UnitNames.

Verwenden von generischen Einheiten

Sie können generische Funktionen schreiben, die mit Daten mit zugeordneter Maßeinheit funktionieren. Hierzu geben Sie einen Typ zusammen mit einer generischen Einheit als Typparameter an, wie im folgenden Codebeispiel gezeigt.

// 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

Erstellen von Auflistungstypen mit generischen Einheiten

Der folgende Code zeigt, wie Sie einen Aggregattyp erstellen, der aus einzelnen Gleitkommawerten mit generischen Einheiten besteht. Dadurch kann ein einzelner Typ erstellt werden, der mit einer Vielzahl von Einheiten funktioniert. Darüber hinaus behalten generische Einheiten die Typsicherheit bei, indem sie sicherstellen, dass ein generischer Typ mit bestimmten Einheiten ein anderer Typ ist als derselbe generische Typ mit anderen Einheiten. Dieses Verfahren basiert darauf, dass das Measure-Attribut auf den Typparameter angewandt werden kann.

 // 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> }

Einheiten zur Laufzeit

Maßeinheiten werden für die statische Typüberprüfung verwendet. Beim Kompilieren von Gleitkommawerten werden die Maßeinheiten entfernt, sodass sie zur Laufzeit verloren gehen. Daher ist es nicht möglich, Funktionen zu implementieren, die von der Überprüfung der Einheiten zur Laufzeit abhängen. Beispielsweise ist das Implementieren einer ToString-Funktion zum Drucken der Einheiten nicht möglich.

Konvertierungen

Um einen Typ mit Einheiten (z. B. float<'u>) in einen Typ ohne Einheiten zu konvertieren, können Sie die Standardkonvertierungsfunktion verwenden. Sie können beispielsweise mit float in einen float-Wert ohne Einheiten konvertieren, wie im folgenden Code gezeigt.

[<Measure>]
type cm
let length = 12.0<cm>
let x = float length

Um einen Wert ohne Einheit in einen Wert mit Einheiten zu konvertieren, können Sie mit dem Wert 1 oder 1,0 multiplizieren, der die entsprechenden Einheiten als Anmerkung aufweist. Zum Schreiben von Interoperabilitätsebenen gibt es jedoch auch einige explizite Funktionen, mit denen Sie Werte ohne Einheiten in Werte mit Einheiten konvertieren können. Diese befinden sich im Modul FSharp.Core.LanguagePrimitives. Um beispielsweise von einem float-Wert ohne Einheit in einen float<cm>-Wert zu konvertieren, verwenden Sie FloatWithMeasure, wie im folgenden Code gezeigt.

open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x

Weitere Informationen