Last active
October 7, 2025 19:56
-
-
Save mrange/258e318a000dd25363185f636f257cb9 to your computer and use it in GitHub Desktop.
F# measures
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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