度量单位
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 与其他单位一起使用(如在分子中)。 例如,速率的单位编写为 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
。 在此示例中,会除以 1.0<degC>
以便参数可进行 printf
操作,因为 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
子命名空间中包含符号形式的 SI 单位(例如表示米的 m
),在 UnitNames
子命名空间中包含全名形式的单位(例如表示米的 meter
)。
使用泛型单位
可以编写对具有关联度量单位的数据进行运算的泛型函数。 为此,可将类型与泛型单位一起指定为类型参数,如下面的代码示例所示。
// 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