A startup’s gene analysis samples nearly melted because someone confused Fahrenheit and Celsius in their monitoring system. A Mars orbiter was lost because of mixed metric and imperial units. Medication dosing errors have killed patients due to milligrams versus micrograms confusion. These aren’t edge cases - they’re symptoms of a fundamental problem in how we build mission-critical systems:
Most languages approach types as an afterthought rather than a first line of defense.
The Near-Disaster That Should Never Have Been Possible
Consider a gene analysis startup that nearly lost years of irreplaceable samples due to a simple type confusion. A developer accidentally configured their freezer monitoring system to read temperatures in Fahrenheit while leaving alarm thresholds in Celsius. When 32°F (0°C) triggered alerts, they were dismissed as a configuration error.
The real crisis came over the weekend when an electrical contractor tripped every circuit breaker in the building and left without telling anyone. The monitoring system - disabled due to the earlier “false” alarms - failed to notify anyone of the power loss. By pure luck, the samples survived. But this near-catastrophe exposed a fundamental flaw: their solution allowed temperature values to exist without their units, creating a disaster waiting to happen.
This wasn’t a failure of testing, monitoring, or procedures. It was a failure of types. The system allowed temperature values to exist without their units of measure, creating a ticking time bomb that eventually exploded.
F# Units of Measure: Your First Line of Defense
F# offers a unique solution that other languages simply lack: units of measure with zero runtime cost. Here’s how that freezer monitoring system should have been written:
[<Measure>] type celsius
[<Measure>] type fahrenheit
[<Measure>] type kelvin
// Temperature conversion functions that preserve units
let celsiusToFahrenheit (temp: float<celsius>) : float<fahrenheit> =
temp * 9.0<fahrenheit/celsius> / 5.0 + 32.0<fahrenheit>
let fahrenheitToCelsius (temp: float<fahrenheit>) : float<celsius> =
(temp - 32.0<fahrenheit>) * 5.0<celsius/fahrenheit> / 9.0
// This would NEVER compile - catching the error at build time
type FreezerMonitor = {
CurrentTemp: float<celsius>
AlarmThreshold: float<celsius>
}
// Attempting to mix units causes compile-time error
let checkTemperature (monitor: FreezerMonitor) (reading: float<fahrenheit>) =
// COMPILE ERROR: Expected float<celsius> but got float<fahrenheit>
if reading > monitor.AlarmThreshold then
Alert.Critical "Temperature exceeded threshold"
The beauty of F#’s approach is that these unit checks disappear entirely at runtime. The compiled code is identical to using plain floats, but the type system ensures you can never mix units incorrectly.
Beyond Numbers: UMX for Domain Safety
The Fidelity Framework extends this concept beyond numeric types using F# UMX (Units of Measure eXtensions). This brings the same compile-time safety to any type in your domain:
// Define domain-specific units for a medical system
type [<Measure>] patient
type [<Measure>] practitioner
type [<Measure>] prescription
// Type-safe identifiers prevent catastrophic mix-ups
type PatientId = Guid<patient>
type PractitionerId = Guid<practitioner>
type PrescriptionId = Guid<prescription>
// This function signature makes invalid states unrepresentable
let prescribeMedication
(doctor: PractitionerId)
(patient: PatientId)
(medication: Medication) : PrescriptionId =
// Cannot accidentally swap patient and doctor IDs
Prescription.create doctor patient medication
// Attempting to mix identifiers causes compile error
let wrongOrder = prescribeMedication patientId doctorId medication
// COMPILE ERROR: Expected Guid<practitioner> but got Guid<patient>
Real-World Applications in Systems Programming
The Fidelity Framework leverages units of measure throughout its stack for systems-level safety:
Memory Management with Units
[<Measure>] type byte
[<Measure>] type kb = byte * 1024
[<Measure>] type mb = kb * 1024
[<Measure>] type gb = mb * 1024
// Network protocols with bandwidth units
[<Measure>] type second
[<Measure>] type mbps = mb / second
type NetworkBuffer = {
Capacity: int<mb>
CurrentUsage: int<byte>
Bandwidth: float<mbps>
}
// Compile-time prevention of buffer overflows
let allocateBuffer (size: int<mb>) (data: byte[]) =
let dataSize = data.Length * 1<byte>
if dataSize > size * 1<mb/byte> then
// Unit conversion is explicit and checked
Error "Data exceeds buffer capacity"
else
Ok (Buffer.create size)
Hardware Interface Safety
// GPIO pin safety for embedded systems
type [<Measure>] input
type [<Measure>] output
type [<Measure>] pwm
type Pin<[<Measure>] 'mode> = int<'mode>
// Functions that only accept correctly configured pins
let readDigital (pin: Pin<input>) : bool =
GPIO.read (int pin)
let writeDigital (pin: Pin<output>) (value: bool) : unit =
GPIO.write (int pin) value
let setPWMDutyCycle (pin: Pin<pwm>) (duty: float<percent>) : unit =
GPIO.setPWM (int pin) (float duty)
// Compile error if you try to write to an input pin
let inputPin = 7<input>
writeDigital inputPin true // COMPILE ERROR!
Protocol-Level Type Safety
// Network protocol units for Modbus (like the freezer example)
type [<Measure>] holding_register
type [<Measure>] input_register
type [<Measure>] coil
type [<Measure>] discrete_input
// Modbus addresses with their register types
type ModbusAddress<[<Measure>] 'register> = uint16<'register>
// The freezer monitoring system, done right
type FreezerSensor = {
TempRegister: ModbusAddress<input_register>
DoorStatus: ModbusAddress<discrete_input>
SetPoint: ModbusAddress<holding_register>
}
// Read operations that ensure correct register access
let readTemperature (sensor: FreezerSensor) : float<celsius> =
let rawValue = Modbus.readInputRegister sensor.TempRegister
float rawValue * 0.1<celsius> // Sensor reports in 0.1°C increments
// Cannot accidentally read a holding register as input
let wrong = Modbus.readInputRegister sensor.SetPoint
// ERROR: Expected uint16<input_register> but got uint16<holding_register>
Memory Safety Without the Performance Tax
One of the most challenging type safety problems in .NET is the “byref restriction” - the inability to store or return references to memory, which forces defensive copying that can devastate performance in data-intensive applications. The Fidelity Framework transforms this limitation into an advantage by combining UMX with BAREWire’s capability-based memory management:
// Traditional .NET - forced to copy due to byref restrictions
let updateLargeData (data: float[]) =
// Can't return a reference, must copy entire array
let copy = Array.copy data
for i = 0 to copy.Length - 1 do
copy.[i] <- copy.[i] * 2.0
copy // Expensive copy operation
// Fidelity approach - type-safe capabilities with zero copying
[<Measure>] type readonly
[<Measure>] type readwrite
[<Measure>] type exclusive
type MemoryCapability<'T, [<Measure>] 'access> =
| Capability of BAREWire.Buffer<'T> * AccessToken<'access>
// Functions can only accept appropriately typed capabilities
let readData (cap: MemoryCapability<float, readonly>) =
match cap with
| Capability(buffer, _) -> buffer.Read(0)
let modifyData (cap: MemoryCapability<float, readwrite>) =
match cap with
| Capability(buffer, _) ->
for i = 0 to buffer.Length - 1 do
buffer.[i] <- buffer.[i] * 2.0
// Compile error prevents accidental mutation
let badFunction (cap: MemoryCapability<float, readonly>) =
modifyData cap // ERROR: Expected readwrite but got readonly
This design prevents both type confusion AND performance degradation. Where .NET’s byref restrictions force copying at every boundary, BAREWire capabilities can be safely passed through async boundaries, stored in data structures, and shared across processes - all while maintaining type safety through UMX:
// This works in Fidelity but is impossible in standard .NET
let asyncProcessWithoutCopying (cap: MemoryCapability<float[], readwrite>) = async {
// Capability can be captured in async - no byref restriction!
do! Async.Sleep(100)
// Direct memory access after await - no defensive copying
modifyData cap
// Can even return capabilities from async
return cap
}
// Share memory across process boundaries with type safety
let shareAcrossProcesses (localCap: MemoryCapability<SensorData, exclusive>) =
// Convert exclusive access to shared read-only for other processes
let sharedCap = BAREWire.downgrade<exclusive, readonly> localCap
// Type system ensures other processes can only read
IPC.share "sensor-data" sharedCap
The integration of UMX with memory capabilities addresses multiple critical requirements:
- Type safety: Prevents accidental writes to read-only memory at compile time
- Memory safety: Makes use-after-free errors impossible through capability tracking
- Performance: Eliminates defensive copying throughout the system
- Composability: Capabilities work seamlessly with async, can be stored, and passed between processes
The Zero-Cost Abstraction Promise
What makes F#’s units of measure exceptional is that they provide iron-clad safety with literally zero runtime cost. The compiler erases all unit information after types have been marshaled to the lowest level of compilation:
// F# source with units - type safe at compile time
let calculatePower (voltage: float<volt>) (current: float<ampere>) : float<watt> =
voltage * current
; LLVM IR after optimization - all names become numbers
define double @calculatePower(double %0, double %1) #0 {
%2 = fmul fast double %0, %1
ret double %2
}
; x86-64 assembly - pure machine operations
calculatePower:
mulsd xmm0, xmm1 ; multiply two doubles in SSE registers
ret ; return result in xmm0
At the assembly level, there’s no trace of voltage, current, watts, or even parameter positions - just two floating-point values in registers being multiplied. The type safety that prevents unit confusion exists only at compile time, protecting us during development but vanishing completely in the deployed code.
This is the holy grail of systems programming: abstractions that make your code safer without making it slower.
Learning from Near-Disasters
The freezer monitoring incident teaches us several critical lessons:
- Types are your specification: The type system should encode your domain knowledge
- Make illegal states unrepresentable: If mixing units is wrong, make it impossible
- Zero-cost abstractions exist: Safety doesn’t require runtime overhead
- Catch errors at compile time: This constructive constraint during design saves time and money, and eliminates entire categories of operational risk
Integration with the Fidelity Framework
The Fidelity Framework builds on F#’s units of measure to provide comprehensive safety across the stack:
// BAREWire protocol with type-safe channels
type [<Measure>] sensor_data
type [<Measure>] control_command
type [<Measure>] telemetry
type Channel<[<Measure>] 'msgType> = {
Endpoint: BAREWire.Endpoint
Protocol: Protocol<'msgType>
}
// Channels can only connect compatible types
let connectChannels
(sender: Channel<'a>)
(receiver: Channel<'a>) : Connection<'a> =
BAREWire.connect sender receiver
// Type error if you try to connect incompatible channels
let sensorChan = createChannel<sensor_data> "tcp://sensor:5555"
let controlChan = createChannel<control_command> "tcp://control:5556"
let invalid = connectChannels sensorChan controlChan
// COMPILE ERROR: Cannot connect Channel<sensor_data> to Channel<control_command>
Beyond Safety: Types as Documentation
Well-designed units of measure serve as living documentation:
// The types tell the whole story
type TurbineMonitoring = {
RotationSpeed: float<rpm>
Temperature: float<celsius>
Pressure: float<pascal>
Efficiency: float<percent>
PowerOutput: float<megawatt>
Runtime: TimeSpan<hour>
}
// Function signatures become self-documenting
let calculateMaintenance
(runtime: TimeSpan<hour>)
(avgTemp: float<celsius>)
(peakPressure: float<pascal>) : TimeSpan<hour> =
// Complex calculation with guaranteed unit correctness
let tempFactor = (avgTemp - 85.0<celsius>) / 10.0<celsius>
let pressureFactor = peakPressure / 100_000.0<pascal>
let interval = 2000.0<hour> / (1.0 + abs tempFactor + pressureFactor)
interval - runtime
The Path Forward
As we build increasingly critical systems - from medical devices to autonomous vehicles to financial infrastructure - we can’t afford to treat types as optional. The gene analysis startup nearly lost irreplaceable samples because their monitoring system allowed temperature units to be confused. What will your organization risk before prioritizing a type-safe and memory-safe system?
The Fidelity Framework demonstrates that type safety doesn’t require sacrificing performance. By leveraging F#’s units of measure and extending them through UMX, we can build systems that are simultaneously:
- Safer: Entire classes of errors become impossible
- Faster: Zero runtime overhead from type checking
- Clearer: Types in source code document intent and constraints
- Maintainable: Refactoring becomes fearless with smart compiler support
Don’t Let Lazy Software Fail Your Business
That company’s freezer monitoring system nearly failed catastrophically not because of a bug in the traditional sense, but because the monitoring software allowed a dangerous state to exist. The DNA samples survived by luck alone - but luck is not a strategy. With F#’s units of measure, that error would have been caught at compile time, long before any samples were ever at risk.
In the Fidelity Framework, we’re applying this lesson at every level - from low-level memory management to high-level protocol design. Because when you’re building critical systems, “it works for my happy path use case” isn’t good enough. The clear and present signal is that future systems need guarantees that only a strong type system can provide. And only F# and the Fidelity framework can provide that at no additional runtime cost.
The next time you’re tempted to use a raw float
for temperature, or a plain string
for an identifier, remember: types aren’t just academic exercises. They’re your first, best, and cheapest line of defense against catastrophic failure.
Your reputation and your business may depend on it.