Floating-point, signed integer and decimal values can carry a unit of measure that the compiler checks and then erases: adding metres to seconds is a type error, and the units cost nothing at run time. A unit is a type with the attribute [<Measure>].
Defining units
[<Measure>] type m // a base unit
[<Measure>] type s
[<Measure>] type kg
[<Measure>] type N = kg m / s^2 // a derived unit: kg m/s^2
[<Measure>] type km
[<Measure>] type ml = cm^3
[<Measure>] type Hz = 1 / s
= defines it from others.A unit formula uses the names of units, * or a space for a product, / for a quotient, ^ and an integer (positive or negative) for a power, 1 for the dimensionless unit, and parentheses. Equivalent formulas are converted to one form, so kg m s^-2 and m /s s * kg are both kg m/s^2.
Using units
let distance = 100.0<m>
let time = 9.58<s>
let speed = distance / time // float<m/s>
let singles = 55.0f<km/s> // a suffix may come before the angle bracket
let force : float<N> = 2.0<kg> * 9.8<m/s^2> // checked: kg m/s^2 is N
let ratio = 3.0<m> / 2.0<m> // float<1>, written float
let plain : float = float distance // the float operator removes the unit
let tagged = 5.0 * 1.0<m> // multiply by a unit literal to add one
let convert (x: float<g>) = x / 1000.0<g/kg> // float<kg>
<.- The type is written
float<m/s>,int<kg>,decimal<usd>; a dimensionless quantity isfloat<1>, which is the same asfloat. - Addition and subtraction need equal units; multiplication and division combine them;
sqrthalves the powers; comparison needs equal units. - To pass a dimensioned value to a function that wants a plain number, cancel the units (
x / 1.0<m>) or use thefloatoperator. To add a unit to a plain number, multiply by1.0<unit>or useLanguagePrimitives.FloatWithMeasure. - Units are not kept at run time: a
float<m>is aSystem.Double, and reflection cannot see the unit. A unit can be applied to other types (string<kg>) only as a type annotation trick, as in libraries that tag identifiers; arithmetic works only on floating-point, signed integer and decimal types.
Generic units
let inline square (x: float<'u>) = x * x // float<'u^2>
let sumAll (xs: float<'u> list) = List.sum xs // 'u is a unit variable
type Vector<[<Measure>] 'u> = { X: float<'u>; Y: float<'u> }
let v : Vector<m> = { X = 1.0<m>; Y = 2.0<m> }
[<Measure>].The library supplies the SI units in FSharp.Data.UnitSystems.SI.UnitNames (metre, second, kilogram and the rest) and FSharp.Data.UnitSystems.SI.UnitSymbols (m, s, kg, N and the rest). Since F# 6 more numeric types accept unit annotations, and printf format strings can work with units (%f accepts a float<m>).
In the lexer and the viewer
A unit annotation on a literal is a < straight after the number, up to the matching >. In a type, the angle brackets after a name hold the unit. The viewer colours the unit names as types and the number as a number; the brackets and the /, * and ^ signs stay plain.
Not described
Known, not yet covered
- Reference: the specification’s chapter on units of measure (equivalence of formulas, unit inference, measure parameters in classes and signatures,
MeasureAnnotatedAbbreviation) was not read; the contents of the SI modules were not listed; the rules for printing units are not given. - Reference: F# does not convert between related units (
kmandm) by itself: a conversion factor is a value such as1000.0<m/km>. That is from the way units work, not from a page of the documentation read.
See also
- Types – the type syntax
- Lexical structure – how the number and the angle bracket are read