@proem/variant

proem package for handling algebraic data types

Usage no npm install needed!

<script type="module">
  import proemVariant from 'https://cdn.skypack.dev/@proem/variant';
</script>

README

@proem/variant

@proem/variant provides functions for processing Variant types (also known as discriminated unions, sum types, algebraic data types, ...).

Usage

Defining variants can be done with interfaces:

// Variant is a combination of multiple tagged types
type Op = Add | Sub | Increment | Decrement | Div

interface Add {
  type: 'add'
  left: number
  right: number
}

interface Increment {
  type: 'increment'
  value: number
}

interface Decrement {
  type: 'decrement'
  value: number
}

interface Sub {
  type: 'sub'
  left: number
  right: number
}

interface Div {
  type: 'div'
  left: number
  right: number
}

variant.map

variant.map takes an object with a variant handling function for each case, and returns a function that takes a variant and returns a new value.

import variant from '@proem/variant'

// map returns a function that takes a variant and returns a result based on your config
const calculate = variant.map<Op, number>({
  add: ({ left, right }) => left + right,
  sub: ({ left, right }) => left - right,
  increment: ({ value }) => value + 1,
  decrement: ({ value }) => value - 1,
  div: ({ left, right }) => {
    if (right === 0) {
      throw Error('DIVIDE BY ZERO')
    }
    return left / right
  },
})

const result = calculate({ type: 'add', left: 2, right: 3 }) // === 5

Providing an optional default case as a second parameter allows handlying partial cases:

import variant from '@proem/variant'

// map returns a function that takes a variant and returns a result based on your config
const calculate: (op: Op) => number = variant.map<Op, number>(
  {
    add: ({ left, right }) => left + right,
    sub: ({ left, right }) => left - right,
  },
  op => 0,
)

const result = calculate({ type: 'increment', value: 1 }) // === 0

variant.reducer

variant.reducer takes an object with a variant handling function for each case, and returns a reducer function that takes a state and variant and returns a new state.

import variant from '@proem/variant'
import array from '@proem/array'

type StatefulOp =
  | { type: 'add'; right: number }
  | { type: 'sub'; right: number }
  | { type: 'increment' }

const calcReducer = variant.reducer<number, StatefulOp>({
  add: (result, { right }) => result + right,
  sub: (result, { right }) => result - right,
  increment: result => result + 1,
})

const ops: StatefulOp[] = [
  { type: 'add', right: 4 },
  { type: 'sub', right: 2 },
  { type: 'increment' },
]

const result = array.reduce(ops, 0, calcReducer) // === 3

variant.oneOf

variant.oneOf is a type guard that can refine the type of a Variant to a more specific one.

function checkOp(op: Op) {
  if (variant.oneOf(op, ['div', 'sub'])) {
    const divOrSub: Div | Sub = op
    // type inference knowns that op is either Div or Sub
  } else {
    // type inference knowns that op is not Div or Sub
    const or: Add | Sub | Increment | Decrement = op
  }
}