DEV Community

stereobooster
stereobooster

Posted on

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

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;
Enter fullscreen mode Exit fullscreen mode

Metric.js:

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

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);
Enter fullscreen mode Exit fullscreen mode

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│
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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"'.
Enter fullscreen mode Exit fullscreen mode

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

Oldest comments (0)