測量單位
F# 中的浮點數和帶正負號的整數值可以具備相關聯的測量單位,其通常可用來表示長度、容量、質量等等。 透過使用帶有單位的數量,您可以讓編譯器確認算術關係具有正確的單位,這有助於防止程式設計錯誤。
注意
這些示例演示了涉及度量單位的算術計算的正確性,還可以利用該功能使用諸如 FSharp.UMX 專案的方式在不產生表示成本的情況下將型別安全註釋新增到其他型別。
語法
[<Measure>] type unit-name [ = measure ]
備註
上一個語法會將 unit-name 定義為測量單位。 選用的部分可用於根據先前定義的單位來定義新量值。 例如,下一行會定義量值 cm
(公分)。
[<Measure>] type cm
下一行會將量值 ml
(毫升) 定義為立方公分 (cm^3
)。
[<Measure>] type ml = cm^3
在上一個語法中,measure 是涉及單位的公式。 在涉及單位的公式中,支援整數乘冪 (正數和負數),單位之間的空格表示這兩個單位的乘積,*
也代表單位的乘積,而 /
代表單位的商數。 若單位為倒數,您可以使用整數負乘冪或 /
,來表示單位公式中分子與分母之間的分隔。 若分母中有多個單位,就應該使用括弧括住。 /
之後以空格分隔的單位會解譯為分母的一部分,但 *
之後的所有單位都會解譯為分子的一部分。
您可以在單位運算式中單獨使用 1 來表示無維度數量,也可以與其他單位一起使用,例如在分子中。 例如,速率的單位會寫入為 1/s
,其中 s
代表秒數。 單位公式中不會使用括弧。 您不會在單位公式中指定數值轉換常數;不過,您可以分別使用單位來定義轉換常數,並在檢查單位的計算中使用它們。
表示可以各種對等方式撰寫相同內容的單位公式。 因此,編譯器會將單位公式轉換為一致的形式,其會將負乘冪轉換為倒數、將單位分組為單一分子和分母,並依字母順序排列分子和分母中的單位。
例如,單位公式 kg m s^-2
和 m /s s * kg
都會轉換為 kg m/s^2
。
您可以在浮點數運算式中使用測量單位。 將浮點數與相關聯的測量單位一起使用,可新增另一個層級的型別安全,並協助避免當您使用弱式類型浮點數時公式中可能發生的單位不符錯誤。 如果您撰寫使用單位的浮點數運算式,運算式中的單位必須相符。
您可以使用角括弧中的單位公式來標註常值,如下列範例所示。
1.0<cm>
55.0<miles/hour>
您不可在數字與角括弧之間放置空格;不過,可包含常值尾碼 (例如 f
),如下列範例所示。
// The f indicates single-precision floating point.
55.0f<miles/hour>
這類註釋會將常值的類型從基本類型 (例如 float
) 變更為維度類型 (例如 float<cm>
,或者,在此案例中為 float<miles/hour>
)。 <1>
的單位註釋指出無維度數量,且其類型相當於不具單位參數的基本類型。
測量單位的類型是浮點數或帶正負號的整數類型,以及以括弧表示的額外單位註釋。 因此,當您撰寫從 g
(公克) 轉換為 kg
(公斤) 的類型時,可以如下方式描述類型。
let convertg2kg (x : float<g>) = x / 1000.0<g/kg>
測量單位可用於編譯時間單位檢查,但不會保存於執行階段環境中。 因此,它們不會影響效能。
測量單位可以套用至任何類型,而不只是浮點數類型;不過,只有浮點數類型、帶正負號整數類型及十進位類型支援維度數量。 因此,只有在基本類型和包含這些基本類型的彙總上使用測量單位才有意義。
下列範例說明如何使用測量單位。
// 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
下列程式碼範例說明如何從無維度浮點數轉換為維度浮點數值。 您只需乘以 1.0,將維度套用至 1.0。 您可以將此作業抽象化為類似 degreesFahrenheit
的函式。
此外,當您將維度值傳遞至需要無維度浮點數的函式時,必須使用 float
運算子來取消單位或轉換為 float
。 在此範例中,您會將 printf
的引數除以 1.0<degC>
,因為 printf
需要無維度的數量。
[<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."
下列範例工作階段顯示此程式碼的輸出和輸入。
Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is 32.22.
支援測量單位的基本類型
下列類型或類型縮寫別名支援測量單位註釋:
F# 別名 | 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 |
例如,您可以標註不帶正負號的整數,如下所示:
[<Measure>]
type days
let better_age = 3u<days>
F# RFC FS-1091 \(英文\) 中記載了如何將不帶正負號的整數類型新增至此功能。
預先定義的測量單位
FSharp.Data.UnitSystems.SI
命名空間中有一個單位庫。 它會在 UnitSymbols
子命名空間中以其符號形式 (例如適用於公尺的 m
),以及在 UnitNames
子命名空間中以其完整名稱 (例如適用於公尺的 meter
),包含 SI 單位。
使用泛型單位
您可以撰寫泛型函式,以對具有相關聯測量單位的資料進行運算。 您可以藉由將類型與泛型單位一起指定為類型參數來完成此作業,如下列程式碼範例所示。
// 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
使用泛型單位建立集合類型
下列程式碼示範如何建立彙總類型,此類型由具有泛型單位的個別浮點數值組成。 這可建立單一類型,以搭配各種單位運作。 此外,泛型單位會藉由確保具有一組單位的泛型型別與具有一組不同單位的相同泛型類型不同,來保護型別安全。 此技術的基礎是 Measure
屬性可以套用至類型參數。
// 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> }
執行階段的單位
測量單位可用於靜態類型檢查。 編譯浮點數值時,會排除測量單位,所以單位會在執行階段遺失。 因此,無法嘗試實作取決於在執行階段檢查單位的功能。 例如,無法實作 ToString
函式來列印單位。
轉換
若要將具有單位的類型 (例如 float<'u>
) 轉換為沒有單位的類型,您可以使用標準轉換函式。 例如,您可以使用 float
轉換為沒有單位的 float
值,如下列程式碼所示。
[<Measure>]
type cm
let length = 12.0<cm>
let x = float length
若要將無單位的值轉換為具有單位的值,您可以乘以 1 或 1.0 值 (以適當的單位進行標註)。 不過,為了撰寫互通性層,也有一些明確的函式可用來將無單位的值轉換為具有單位的值。 這些均位於 FSharp.Core.LanguagePrimitives 模組中。 例如,若要從無單位的 float
轉換為 float<cm>
,請使用 FloatWithMeasure,如下列程式碼所示。
open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x