Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Last active June 7, 2024 16:41
Show Gist options
  • Save CMCDragonkai/e6b44699877644dda65081033fc20c89 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/e6b44699877644dda65081033fc20c89 to your computer and use it in GitHub Desktop.
ADTs in TypeScript/JavaScript

ADTs in TypeScript/JavaScript

Here are the different ways of creating an algebraic datatype or a tagged value.

type Type = 'A' | 'B';
type Value = any;

type ADTTuple = [Type, Value];
type ADTNamed = { type: Type } & {};
type ADTObject = { type: Type, value: Value };
type ADTKey = {
  [K in Type]: {[P in K]: Value}
}[Type];

function matchTuple(x: ADTTuple) {
  const [type, value] = x;
  if (type === 'A') {
    // ... value
  } else if (type === 'B') {
    // ... value
  }
}

function matchNamed(x: ADTNamed) {
  const { type, ...value } = x;
  if (type === 'A') {
    // ... value
  } else if (type === 'B') {
    // ... value
  }
}

function matchObject(x: ADTObject) {
  const { type, value } = x;
  if (type === 'A') {
    // ... value
  } else if (type === 'B') {
    // ... value
  }
}

function matchKey(x: ADTKey) {
  if ('A' in x) {
    // ... x['A']
  } else if ('B' in x) {
    // ... x['B']
  }
}

The ADTNamed is the most inflexible due to 2 reasons:

  • Name clashes because type is a special keyword now.
  • It limits the value to an object type, it cannot be just a number or other primitive as the intersection won't work.

I prefer the ADTTuple, ADTObject and ADTKey forms.

The ADTTuple is nice for simple internal data structures. It's easy to destructure and work with. It is however not very descriptive, nor self-documenting.

The ADTObject and ADTKey are very similar. The main difference is that the ADTKey is more succinct.

This can be useful for wrappers that may result in repeated usage like value.value that occurs with ADTObject.

An example of a good use of ADTKey is JSON-RPC 2.0:

{
  "jsonrpc": "2.0",
  "id": null,
  "result": null
}
{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": -32600, 
    "message": "..."
  }
}

The usage of the key result vs error is what determines the 2 types of messages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment