Skip to content

Instantly share code, notes, and snippets.

@OliverJAsh
Created November 29, 2017 17:35
Show Gist options
  • Save OliverJAsh/64b65038fede09664f33bcd8cb9705a5 to your computer and use it in GitHub Desktop.
Save OliverJAsh/64b65038fede09664f33bcd8cb9705a5 to your computer and use it in GitHub Desktop.
Tagged unions with unionize

Tagged unions are a way of saying "this data is any one of these values", and the tag is a property that discriminates the values. It enables some really powerful type system features, like exhaustive checking and control flow analysis.

Tagged unions also go by the name of: tagged unions, discriminated unions, algebraic data types (ADTs), and sum types. Just to confuse matters.

In many functional languages this is a really common pattern, like Haskell, Elm, PureScript, and Scala. So much so, they have their own native syntax.

For example, in Elm we can define a tagged union representing an anonymous or named user with:

// elm
type User = Anonymous | Named String

… where Anonymous and Named are the tags that differentiate the values, and String is the type of the value you'll get for Named. This also creates constructors for Anonymous and Named.

We can do the same in TypeScript, but there's a lot of boilerplate. Most often, you have to define an enum or string literals for the tags, and then constructors and types for each record in the union:

// plain TS:

// Union tag
enum UserType {
  Anonymous,
  Named,
}

// Records and constructors
type Anonymous = {
  type: UserType.Anonymous;
  value: {}
};
type Named = {
  tag: UserType.Named;
  value: {
    name: string;
  };
};
const createAnonymous = (): Anonymous => ({ type: UserType.Anonymous, value: {} });
const createNamed = ({ name }: { name: string }): Named => ({
  tag: UserType.Named,
  value: { name },
});

// Tagged union
type User = Anonymous | Named;

Unionize is a fantastic little library that abstracts away much of this boilerplate:

// with unionize:

import { unionize, ofType } from 'unionize';

const User = unionize({
  Anonymous: ofType<{}>(),
  Named: ofType<{ name: string }>(),
});
type UserUnion = typeof User._Union;

// example of using constructors:
User.Anonymous({});
User.Named({ name: 'foo' });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment