|
| 1 | +(** |
| 2 | +# Units of Measure |
| 3 | +
|
| 4 | +One of F#'s most powerful features for scientific and engineering code is |
| 5 | +**units of measure** - compile-time dimensional analysis that prevents |
| 6 | +unit-related bugs. |
| 7 | +
|
| 8 | +## The Problem |
| 9 | +
|
| 10 | +Unit errors are a classic source of bugs. The famous Mars Climate Orbiter |
| 11 | +was lost because one team used metric units while another used imperial. |
| 12 | +Python can't catch these errors: |
| 13 | +
|
| 14 | +```python |
| 15 | +# Python - no protection |
| 16 | +distance = 100 # meters? feet? who knows! |
| 17 | +time = 9.58 # seconds? minutes? |
| 18 | +speed = distance / time # ??? |
| 19 | +``` |
| 20 | +
|
| 21 | +## F# Units of Measure |
| 22 | +
|
| 23 | +F# lets you annotate numeric types with units that are checked at compile time: |
| 24 | +*) |
| 25 | + |
| 26 | +(*** hide ***) |
| 27 | +module UnitsOfMeasure |
| 28 | + |
| 29 | +(** |
| 30 | +*) |
| 31 | + |
| 32 | +[<Measure>] type m // meters |
| 33 | +[<Measure>] type s // seconds |
| 34 | +[<Measure>] type kg // kilograms |
| 35 | + |
| 36 | +(** |
| 37 | +Now we can define values with units: |
| 38 | +*) |
| 39 | + |
| 40 | +let distance = 100.0<m> |
| 41 | +let time = 9.58<s> |
| 42 | +let speed = distance / time // Automatically inferred as float<m/s> |
| 43 | + |
| 44 | +(** |
| 45 | +The compiler tracks units through all operations. Division of meters by |
| 46 | +seconds gives meters-per-second. This is all checked at compile time! |
| 47 | +
|
| 48 | +## Preventing Errors |
| 49 | +
|
| 50 | +Try to add incompatible units and the compiler stops you: |
| 51 | +
|
| 52 | +```fsharp |
| 53 | +let distance = 100.0<m> |
| 54 | +let mass = 50.0<kg> |
| 55 | +
|
| 56 | +// This won't compile! |
| 57 | +// let nonsense = distance + mass |
| 58 | +// Error: The unit of measure 'm' does not match 'kg' |
| 59 | +``` |
| 60 | +
|
| 61 | +## Derived Units |
| 62 | +
|
| 63 | +You can define derived units based on existing ones: |
| 64 | +*) |
| 65 | + |
| 66 | +[<Measure>] type N = kg * m / s^2 // Newton |
| 67 | +[<Measure>] type J = N * m // Joule |
| 68 | + |
| 69 | +let force = 10.0<N> |
| 70 | +let displacement = 5.0<m> |
| 71 | +let work = force * displacement // Inferred as float<J> |
| 72 | + |
| 73 | +(** |
| 74 | +## Real-World Example: Physics Simulation |
| 75 | +
|
| 76 | +Here's a practical example computing kinetic energy: |
| 77 | +*) |
| 78 | + |
| 79 | +let kineticEnergy (mass: float<kg>) (velocity: float<m/s>) : float<J> = |
| 80 | + 0.5 * mass * velocity * velocity |
| 81 | + |
| 82 | +let carMass = 1500.0<kg> |
| 83 | +let carSpeed = 30.0<m/s> |
| 84 | +let energy = kineticEnergy carMass carSpeed |
| 85 | + |
| 86 | +(** |
| 87 | +The function signature clearly documents what units are expected and returned. |
| 88 | +The compiler ensures you can't accidentally pass velocity where mass is expected. |
| 89 | +
|
| 90 | +## Unit Conversions |
| 91 | +
|
| 92 | +Define conversion functions with explicit unit transformations: |
| 93 | +*) |
| 94 | + |
| 95 | +[<Measure>] type km |
| 96 | +[<Measure>] type h |
| 97 | + |
| 98 | +let metersToKm (d: float<m>) : float<km> = d / 1000.0<m/km> |
| 99 | +let secondsToHours (t: float<s>) : float<h> = t / 3600.0<s/h> |
| 100 | + |
| 101 | +let marathonDistance = 42195.0<m> |
| 102 | +let marathonKm = metersToKm marathonDistance // 42.195<km> |
| 103 | + |
| 104 | +(** |
| 105 | +## Generated Python |
| 106 | +
|
| 107 | +When compiled to Python, units are erased (they're purely a compile-time |
| 108 | +feature), but your code is guaranteed to be unit-safe: |
| 109 | +
|
| 110 | +```python |
| 111 | +def kinetic_energy(mass: float, velocity: float) -> float: |
| 112 | + return 0.5 * mass * velocity * velocity |
| 113 | +
|
| 114 | +car_mass: float = 1500.0 |
| 115 | +car_speed: float = 30.0 |
| 116 | +energy: float = kinetic_energy(car_mass, car_speed) |
| 117 | +``` |
| 118 | +
|
| 119 | +The Python code is clean and efficient. All the unit checking happened |
| 120 | +at compile time in F#, so there's no runtime overhead. |
| 121 | +
|
| 122 | +## Why This Matters for Python |
| 123 | +
|
| 124 | +Python is widely used in scientific computing, but lacks compile-time |
| 125 | +unit checking. With Fable.Python, you can: |
| 126 | +
|
| 127 | +1. **Write unit-safe code** in F# with full dimensional analysis |
| 128 | +2. **Catch unit errors at compile time** before they become runtime bugs |
| 129 | +3. **Generate clean Python** that integrates with NumPy, SciPy, etc. |
| 130 | +4. **Document intent** - function signatures show expected units |
| 131 | +
|
| 132 | +This is especially valuable for physics simulations, financial calculations, |
| 133 | +engineering applications, and any domain where mixing up units could be costly. |
| 134 | +*) |
0 commit comments