Skip to content

Instantly share code, notes, and snippets.

@mrange
Last active October 7, 2025 19:56
Show Gist options
  • Select an option

  • Save mrange/258e318a000dd25363185f636f257cb9 to your computer and use it in GitHub Desktop.

Select an option

Save mrange/258e318a000dd25363185f636f257cb9 to your computer and use it in GitHub Desktop.
F# measures
open LanguagePrimitives
// Units of measure are *compile-time* type tags on numeric types
// They prevent mixing incompatible dimensions (meters vs seconds) and have *no runtime cost*.
type [<Measure>] m // meter (length)
type [<Measure>] kg // kilogram (mass)
type [<Measure>] s // second (time)
type [<Measure>] A // ampere (electric current)
type [<Measure>] K // kelvin (thermodynamic temperature)
type [<Measure>] mol // mole (amount of substance)
type [<Measure>] cd // candela (luminous intensity)
// Tagging helpers: take a plain float and return a unitized float.
// FloatWithMeasure adds the unit at compile time.
let length v : float<m> = FloatWithMeasure v
let mass v : float<kg> = FloatWithMeasure v
let time v : float<s> = FloatWithMeasure v
let current v : float<A> = FloatWithMeasure v
let temperature v : float<K> = FloatWithMeasure v
let mole v : float<mol> = FloatWithMeasure v
let luminosity v : float<cd> = FloatWithMeasure v
// Derived unit example: acceleration = m / s^2.
let acceleration v : float<m/s^2> = FloatWithMeasure v
// Example usage: types carry the physics.
let someAcceleration : float<m/s^2> = acceleration 10.
let someForce : float<kg*m/s^2> = mass 80. * someAcceleration // F = m * a
// Temperature measures
type [<Measure>] C // Celsius
type [<Measure>] F // Fahrenheit
type [<Measure>] R // Rankine
// Generic affine converter between units: v_T = m*v_F + a
// - m has unit 'T/'F (scale factor)
// - a has unit 'T' (offset)
type Converter<[<Measure>] 'F, [<Measure>] 'T>(m : float<'T/'F>, a : float<'T>) =
class
member x.M = m
member x.A = a
// Enforces 'F -> 'T at compile time
member x.Convert (v : float<'F>) : float<'T> = m * v + a
// Compose: ('F -> 'T) then ('T -> 'Q) gives ('F -> 'Q)
// (m2*(m1*v + a1) + a2) = (m2*m1)*v + (m2*a1 + a2)
member x.Compose<[<Measure>] 'Q> (c : Converter<'T,'Q>) : Converter<'F,'Q> =
Converter<'F,'Q>(c.M * x.M, c.M * x.A + c.A)
// Inverse: v_F = (v_T - a) / m => slope 1/m, offset -a/m
member x.Invert () : Converter<'T,'F> =
Converter<'T,'F>(1.0 / x.M, -x.A / x.M)
end
// Helper: construct a pair of converters from plain floats (scale, offset).
// We attach the intended units using FloatWithMeasure.
let makeConverter<[<Measure>] 'F, [<Measure>] 'T> m a =
let c = Converter<'F,'T>(FloatWithMeasure m, FloatWithMeasure a)
c, c.Invert()
// Canonical Kelvin-based conversions (source of truth):
let KtoC, CtoK = makeConverter<K, C> (1. ) (-273.15)
let KtoF, FtoK = makeConverter<K, F> (9. / 5.) (-459.67)
let KtoR, RtoK = makeConverter<K, R> (9. / 5.) ( 0. )
// Example round-trip via units:
let c : float<C> = KtoC.Convert 100.<K>
let k : float<K> = CtoK.Convert c
// Build C -> F by composition: (C -> K) then (K -> F)
let CtoF : Converter<C, F> = CtoK.Compose KtoF
// Type-safe conversion
let f : float<F> = CtoF.Convert 100.<C>
printfn "100 C in F: %f" f
// Even handles fractional units
let f2 : float<F^(1/2)>= sqrt f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment