loading...
Cover image for Sum Types in JavaScript

Sum Types in JavaScript

moosch profile image Ryan Whitlie Updated on ・3 min read

Write You Some FP Maths

In this series I'll be looking at some mathematical concepts and functional programming stuff in JavaScript to help solidify the concepts and just generally gain experience and muscle memory. I also hope it's useful to readers.

FP FTW!

Sum Types

A Sum type is also known as a tagged union, variant, variant record, choice type, discriminated union, disjoint union, or coproduct. more

You may have heard of union types from languages like TypeScript, but sum types are a bit different (actually quite different but lets not get into that now). A union type uses something called type refinement to get defined types and depending on the language will fail to compile if you create an instance where an unexpected type is used.

In the case of sum types pattern matching is used to do the same thing as type refinement, and can also be used to handle a null or unexpected type case. Both solve similar issues of avoiding runtime errors with a type mismatch or an undefined/null value.

Sum types can be thought of as a datatype that can be one of a number of types (known as products) that have their own definition or shape. Each of these types has a tag that is used to identify the type in pattern matching. Except this is JavaScript so we can't actually do pattern matching. We can either do a series of if statements, or a switch statement. A switch looks cleaner, so let's make that easier by adding a tag to the product types.

For example, if we define some money types:

const Coin = (int, qty, unit) => ({
  tag: 'Coin',
  value: int * (unit === 'cent' ? 0.01 : 1.0) * qty,
});

const Note = (int, qty) => ({
  tag: 'Note',
  value: int * qty,
});

Then the sum type would be Money:

// Money :: Coin | Note
const Money = {
  'Coin': Coin,
  'Note': Note,
}

We can assign some Money to a variable and pass it into a pattern matching (simple switch thanks to the tag) function that will give us the value based on the Money type (Coin or Note).

function value(m) {
  switch(m.tag) {
    case 'Coin':
    case 'Note':
      return `Value: $${m.value}`;
    default:
      return `Value: $0`;
  }
}

const coins = Money.Coin(10, 5, 'cent');
value(coins); // => Value: $0.5

const cash = Money.Note(6, 20);
value(cash); // => Value: $120

There you go, you've successfully built your own sum type complete with products. Well done 👍

What next...?

Well this is all very good, but wouldn't it be amazing if we could define these sum types without having to define the tag property on every product? Say maybe input a bunch of identifier tags and their corresponding data structures when we define the sum type?
The answer is yes, yes it would. So lets do that!

In this example we're going to borrow from the Elixir language and their awesome pattern matching over function arguments. But still using the money example.

const defineProducts = (fields) =>
  fields.reduce(
    (acc, T) => ({
      // This order means first come first called
      [T.length]: T,
      ...acc,
    }),
    {},
  );

const SumType = (types, failure) => {
  const products = defineProducts(types);

  return (...args) => {
    const len = String(args.length);
    if (products[len]) {
      return products[len](...args);
    }

    return failure(...args);
  }
};

defineProducts generates an object with the type's parameter number as the key and the type (product) as the value. The SumType function takes an array of types and a type (in this case an inline function) if no match can be found.

Now to define the sum type:

const Money = SumType(
  [Coin, Note],
  (_) => {
    throw new TypeError('Invalid data structure provided');
  }
);

Now time to give it a go:

const coins = Money(10, 5, 'cent');
`Value: $${coins.value}`; // => Value: $0.5

const cash = Money(6, 20);
`Value: $${cash.value}`; // => Value: $120

With this structure there's no need to call Money.Coin or Money.Note because the parameters supplied are matching the type.

Conclusion

Sum types can come in super useful, even in JavaScript, and can be configured in lots of different ways for difference use cases. Luckily some smart folks have come up with libraries to generate them easily.
Check out Daggy or sum-type.

If you have any questions of feedback feel free to comment 🙂

Happy coding λ

Posted on by:

Discussion

markdown guide