DEV Community

loading...

Pragmatic types: opaque types and how they could have saved Mars Climate Orbiter

stereobooster profile image stereobooster ・2 min read

The 'root cause' of the loss of the spacecraft was the failed translation of English units into metric units in a segment of ground-based, navigation-related mission software, as NASA has previously announced.

-- source

It sounds almost unrealistic - a software bug led to spacecraft loss. But this is true developer forgot to translate one type of units into another type of units.

How to make sure you will not add meters to miles or meters to seconds or seconds to hours or Euros to Dollars? Type systems have an answer for it - opaque types.

Flow

Imperial.js:

// @flow
export opaque type Mile = number;
export const numberToMile = (n: number): Mile => n;

Metric.js:

// @flow
export opaque type Kilometer = number;
export const numberToKilometers = (n: number): Kilometer => n;

test.sj

//@flow
import { type Kilometer } from './Metric'
import { numberToMile } from './Imperial'
export const calculateOrbit = (n: Kilometers) => {
  // do some math here
  n;
};

let m = numberToMile(123);
calculateOrbit(m);

Error:

Cannot call calculateOrbit with m bound to n because Mile [1] is incompatible with Kilometers [2].

     test.js
 [2]  4│ export const calculateOrbit = (n: Kilometer) => {
      5│   // do some math here
      6│   n;
      7│ };
      8│
      9│ let m = numberToMile(123);
     10│ calculateOrbit(m);
     11│

Note: this will work only if you keep definitions of Mile and Kilometer in separate files.

TypeScript

There is no native opaque type in TypeScript, but you can use a solution proposed by Charles Pick:

type Opaque<K, T> = T & { __TYPE__: K };

Example:

type Kilometer = Opaque<'Kilometers', number>;
type Mile = Opaque<'Mile', number>;
const numberToMile = (n: number) => n as Mile;
const calculateOrbit = (n: Kilometer) => {
  // do some math here
  n;
};

let m = numberToMile(123);
calculateOrbit(m);

Error:

Argument of type 'Opaque<"Mile", number>' is not assignable to parameter of type 'Opaque<"Kilometers", number>'.
  Type 'Opaque<"Mile", number>' is not assignable to type '{ __TYPE__: "Kilometers"; }'.
    Types of property '__TYPE__' are incompatible.
      Type '"Mile"' is not assignable to type '"Kilometers"'.

This post is part of the series. Follow me on twitter and github

Discussion (0)

pic
Editor guide