100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Units of Measure

See how F#'s units of measure attach compile-time-only physical units to numeric types, catching dimensional-analysis bugs before runtime.

Type SystemAdvanced10 min readJul 10, 2026
Analogies

Why Units of Measure Exist

Units of measure (UoM) let you attach a compile-time-only tag like <meter>, <second>, or <kg> to a numeric type, so 3.0<meter> and 3.0<second> are both float values under the hood but are distinct, incompatible types to the compiler. This directly targets the class of bug where a value's numeric type-checks perfectly (it's just a float) but its physical meaning is wrong -- famously, NASA's Mars Climate Orbiter was lost in 1999 because one team's software produced thrust data in pound-force-seconds while another expected newton-seconds, and nothing in either codebase's types caught the mismatch.

🏏

Cricket analogy: Units of measure are like distinguishing a bowling figure in overs from one in balls bowled -- both are just numbers, but mixing up '10 overs' with '10 balls' in a run-rate calculation gives a nonsensical result, the same category of silent bug UoM prevents by tagging 10.0<over> differently from 10.0<ball>.

Declaring and Attaching Units

You declare a unit with [<Measure>] type m (for meters) and [<Measure>] type s (for seconds), then attach them to literals with angle-bracket syntax: let distance = 100.0<m> and let time = 9.58<s>. Units compose: dividing 100.0<m> / 9.58<s> produces a value of type float<m/s> automatically, and the compiler derives compound units like m/s or m/s^2 from arithmetic rather than requiring you to declare them separately, though you can also declare a named compound unit like [<Measure>] type N = kg * m / s^2 for readability.

🏏

Cricket analogy: Declaring [<Measure>] type over and attaching it as 10.0<over> is like the scoring convention that treats overs as a distinct unit from balls in official records; compound derivation, like runs-per-over, mirrors how 100.0<run> / 10.0<over> yields a run/over type automatically without the compiler needing a separate 'strike rate unit' declared upfront.

fsharp
[<Measure>] type m
[<Measure>] type s

let distance = 100.0<m>
let time = 9.58<s>
let speed = distance / time   // float<m/s>

[<Measure>] type kg
[<Measure>] type N = kg * m / s^2

let force (mass: float<kg>) (accel: float<m/s^2>) : float<N> =
    mass * accel

Arithmetic and Unit Inference

Addition and subtraction require identical units -- 5.0<m> + 3.0<m> type-checks and yields 8.0<m>, but 5.0<m> + 3.0<s> is a compile error -- while multiplication and division freely combine or cancel units, so 10.0<m> * 3.0<s> produces 30.0<m s> and 10.0<m> / 10.0<m> produces a dimensionless 1.0 (with no unit at all). This means the compiler will reject an accidentally-transposed formula, such as computing kinetic energy as mass * velocity instead of 0.5 * mass * velocity * velocity, purely because the resulting unit doesn't match the <kg m^2/s^2> you declared the function to return.

🏏

Cricket analogy: The rule that 5.0<over> + 3.0<over> works but 5.0<over> + 3.0<ball> doesn't mirrors how a scorecard can sum two spells' overs together directly, but can't meaningfully add 'overs bowled' to 'balls faced' without first converting, the same dimensional discipline UoM enforces on addition.

fsharp
[<Measure>] type ft

let metersToFeet (m: float<m>) : float<ft> =
    m * 3.28084<ft/m>

let trackLength = 100.0<m>
let trackLengthInFeet = metersToFeet trackLength  // 328.084<ft>

// Stripping the unit back to a plain float:
let raw = trackLength / 1.0<m>   // 100.0 : float

You can write generic functions over units of measure too: let square (x: float<'u>) : float<'u ^ 2> = x * x works for float<m>, float<s>, or any other unit, deriving float<m^2> or float<s^2> automatically at each call site -- units of measure participate in generic type inference just like ordinary type parameters.

Converting Between Units and Stripping Measures

Converting between units, such as meters to feet, requires an explicit conversion factor with its own unit -- let metersToFeet (m: float<m>) : float<ft> = m * 3.28084<ft/m> -- because the compiler has no built-in knowledge that meters and feet measure the same underlying dimension; you must supply the relationship yourself. To strip a unit entirely and get back a plain float, you divide by a quantity of the same unit (5.0<m> / 1.0<m> gives 5.0), and there's no automatic 'unwrap' operation because doing so silently would defeat the entire purpose of tracking units in the first place.

🏏

Cricket analogy: Converting metersToFeet with an explicit conversion factor is like converting a bowler's speed reading from kilometers per hour to miles per hour on a broadcast graphic -- the two numbers describe the same physical pace, but the broadcaster must apply a known conversion factor (0.621371) rather than assume the systems interoperate automatically.

Units of measure are entirely erased at runtime -- 3.0<m> compiles down to the exact same IL as a plain 3.0, so there is zero runtime overhead, but also zero runtime checking. If you use obj, reflection, or serialize a value and lose the static type (e.g., round-tripping through JSON as a plain float), nothing prevents you from reattaching the wrong unit on deserialization.

  • Units of measure attach a compile-time-only tag like <m> or <s> to numeric types.
  • Declared with [<Measure>] type m, then attached to literals as 3.0<m>.
  • Addition and subtraction require identical units; multiplication and division freely combine or cancel units.
  • Compound units like <m/s> or <N> are derived automatically from arithmetic, or can be named explicitly.
  • Converting between units (e.g., meters to feet) requires an explicit, manually supplied conversion factor.
  • Units are completely erased at runtime -- no performance cost, but also no runtime validation.
  • Units of measure catch physically nonsensical formulas at compile time, like the class of bug that doomed the Mars Climate Orbiter.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#FStudyNotes#UnitsOfMeasure#Units#Measure#Exist#Declaring#StudyNotes#SkillVeris#ExamPrep