Sum types, the missing data structure

Sum types, the missing data structure

By Guillaume Claret

Popularized in JavaScript by some libraries like Redux, the sum types are a data structure representing values with different cases. Native in most functional languages, sum types are unfortunately not a builtin feature in JavaScript. We will recall what are the sum types and show how to use them in JavaScript.

Sum types

A sum type represents an “or” of values. Anytime we write a function which can have different kinds of arguments or results, we are using a sum type. For example, a function which returns either a number or a string message (in case of error) returns a sum type. The sum types complement the product types such as Array which are “and” of values. In JavaScript, one way to encode a sum type is to use an object with a type field. For example, the following function attempts to parse a string and returns either an error message or a number:

function safeParseInt(s) {
  const n = parseInt(s);
  return isNaN(n) ? {
    type: 'error',
    value: 'The string ' + s + ' is not a number.'
  } : {
    type: 'success',
    value: n
  };
}

We can use the sum types to represent the result of an API call, the Redux actions, the routes, the state of a form (submitting, with an error, …) or the status of asynchronous requests (pending, successful, …).

The type field helps to discriminate among the different cases. This is the difference between sum types and union types. In union types, we do not know to which case corresponds a value (unless we use introspection). Here is the same example with a union type:

function safeParseInt(s) {
  const n = parseInt(s);
  return isNaN(n) ? 'The string ' + s + ' is not a number.' : n;
}

In some senses, we can view a sum type as a union type plus a tag.

Should we prefer union types?

I believe that union types are dangerous. Indeed, if we look at the typing rules, union types are ambiguous while the typing of sums is syntax-directed. Moreover, with the notion of union comes the notion of inclusion of types, and complex questions such as the contravariance and often the need of downcasts.

Destructuring

There are no built-in ways to open the value of a sum type in JavaScript. We can use the switch statement as proposed in the Redux documentation:

const result = safeParseInt(...);
switch (result.type) {
  case 'success':
    console.log('OK', result.value);
    break;
  case 'error':
    console.log('ERROR', result.value);
}

An other way is to use an object of functions:

function match(value, cases, defaultResult) {
  return value.type in cases ? cases[value] : defaultResult();
}

const result = safeParseInt(...);
const message = match(result, {
  success: ({value}) => 'OK ' + value,
  error: ({value}) => 'ERROR ' + value
});
console.log(message);

For each possible case we define a function of the corresponding name and an optional default value.

Typing sum types

The JavaScript type-checker Flow has a good support for the sum-types, with destructuring in the form of switch statements (see disjoint unions). Remember that, if needed, we can transform a switch into an expression by using an empty anonymous function:

const result = safeParseInt(...);
const message = (() => {
  switch (result.type) {
    case 'OK':
      return 'OK ' + result.value;
    case 'ERROR':
      return 'ERROR ' + result.value;
  }
})();

This is a bit heavy, but this is type-checked! The TypeScript system does not have sum types yet, but may converge to the same solution as Flow. If you are ready to change your syntax a bit more, PureScript and Elm have builtin supports of sum types with specific syntaxes for destructuring.

OuiCar's Picture

About OuiCar

OuiCar is a community market place to find and rent cars anywhere in France. But we are not just car renters. We also like to experiment new technologies and share ideas with others.

Paris http://www.ouicar.fr

Comments