Skip to content

Latest commit

 

History

History
139 lines (106 loc) · 5.46 KB

File metadata and controls

139 lines (106 loc) · 5.46 KB

Item 64: Consider Brands for Nominal Typing

Things to Remember

  • With nominal typing, a value has a type because you say it has a type, not because it has the same shape as that type.
  • Consider attaching brands to distinguish primitive and object types that are semantically distinct but structurally identical.
  • Be familiar with the various techniques for branding: properties on object types, string-based enums, private fields, and unique symbols.## Code Samples
interface Vector2D {
  x: number;
  y: number;
}
function calculateNorm(p: Vector2D) {
  return Math.sqrt(p.x ** 2 + p.y ** 2);
}

calculateNorm({x: 3, y: 4});  // OK, result is 5
const vec3D = {x: 3, y: 4, z: 1};
calculateNorm(vec3D);  // OK! result is also 5

💻 playground


interface Vector2D {
  type: '2d';
  x: number;
  y: number;
}

💻 playground


type AbsolutePath = string & {_brand: 'abs'};
function listAbsolutePath(path: AbsolutePath) {
  // ...
}
function isAbsolutePath(path: string): path is AbsolutePath {
  return path.startsWith('/');
}

💻 playground


function f(path: string) {
  if (isAbsolutePath(path)) {
    listAbsolutePath(path);
  }
  listAbsolutePath(path);
  //               ~~~~ Argument of type 'string' is not assignable to
  //                    parameter of type 'AbsolutePath'
}

💻 playground


type Meters = number & {_brand: 'meters'};
type Seconds = number & {_brand: 'seconds'};

const meters = (m: number) => m as Meters;
const seconds = (s: number) => s as Seconds;

const oneKm = meters(1000);
//    ^? const oneKm: Meters
const oneMin = seconds(60);
//    ^? const oneMin: Seconds

💻 playground


const tenKm = oneKm * 10;
//    ^? const tenKm: number
const v = oneKm / oneMin;
//    ^? const v: number

💻 playground


declare const brand: unique symbol;
export type Meters = number & {[brand]: 'meters'};

💻 playground


function binarySearch<T>(xs: T[], x: T): boolean {
  let low = 0, high = xs.length - 1;
  while (high >= low) {
    const mid = low + Math.floor((high - low) / 2);
    const v = xs[mid];
    if (v === x) return true;
    [low, high] = x > v ? [mid + 1, high] : [low, mid - 1];
  }
  return false;
}

💻 playground


type SortedList<T> = T[] & {_brand: 'sorted'};

function isSorted<T>(xs: T[]): xs is SortedList<T> {
  for (let i = 0; i < xs.length - 1; i++) {
    if (xs[i] > xs[i + 1]) {
      return false;
    }
  }
  return true;
}

function binarySearch<T>(xs: SortedList<T>, x: T): boolean {
  // ...
}

💻 playground