Skip to content
Please note that GitHub no longer supports Internet Explorer.

We recommend upgrading to the latest Microsoft Edge, Google Chrome, or Firefox.

Learn more
Statically checked exhaustive matching for TypeScript
TypeScript
Branch: master
Clone or download
Cannot retrieve the latest commit at this time.
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci Add .circleci/config.yml Oct 22, 2018
.vscode Ignore .rts2_cache_* directories Dec 4, 2018
src Accept also numbers and symbols as kind keys and values Oct 23, 2018
test Accept also numbers and symbols as kind keys and values Oct 23, 2018
.gitignore Ignore .rts2_cache_* directories Dec 4, 2018
.npmignore
LICENSE Initial commit Oct 20, 2018
README.md Update README.md Dec 4, 2018
package.json v0.0.6 Dec 4, 2018
tsconfig.json Use microbundle for building Nov 23, 2018
yarn.lock Update deps Dec 4, 2018

README.md

matchkin CircleCI

matchkin is a TypeScript library for creating value matchers that can be statically proven to be exhaustive. Exhaustive in this context means that all possible values get handled (as long as we don't deliberately escape the sweet embrace of the type system).

For example this article outlines a good way to achieve pretty much the same thing with switch statements. The switch statements can get statically checked for unexpected values, so you're protected from matching against "hambugre" instead of "hamburger". However matchkin does offer some extra niceties:

  • Never forget the default block. If the type checker sees that all possibilities aren't explicitly covered then the default fallback is required. Note that e.g. TSLint can be configured to require default blocks in all switch statements. However...
  • No gratuitous default blocks. If the type system in convinced that you're handling all cases then the default fallback is forbidden.
  • Ensure that this all can be checked statically. Moving up one meta level. Matchers are created by calling createMatcher(...). Those calls also get statically checked to make sure that the above checks can be performed for the resulting matcher.

Of course for these pros there are cons:

  • Some more ceremony when creating a matching function using createMatcher. Though hopefully that's an one-time investment.
  • The library currently supports discriminating types by literal string, number and symbol types only.
  • Hurrah, yet another library to include to your project!

Here's an animated GIF that demonstrates the features:

An animated GIF showcasing the features

Installation

$ yarn add matchkin

Usage

First import the function createMatcher.

import { createMatcher } from "matchkin";

Assume we have these two types:

class Cat {
  readonly species = "cat";
}
class Dog {
  readonly species = "dog";
}

To create a matcher we can do this:

const match = createMatcher("species", {
  cat: (c: Cat) => "kitty",
  dog: (d: Dog) => "doggy"
});

Here the value "species" is the name of the property that match can use to discriminate between values of type Cat | Dog. The type of this argument has to be a single string literal. Luckily for us its type is "species" as well.

The second argument is an object, its properties listing all the possible species match must handle. The property values are functions that take one argument: a matched instance. Their types have to be consistent with the corresponding property name - for example dog: (c: Cat) => c is an error, because the "species" property of Cats is "cat", not "dog".

In the end createMatcher returns a function we here call match, which we can use in two ways. First we can explicitly list all possibilities:

const pet: Cat | Dog = ...

// Returns either "Bad kitty" or "Good doggy"
match(pet, {
  cat: name => `Bad ${name}`,
  dog: name => `Good ${name}`
});

Or we can list just some of the possibilities and provide a fallback for the non-listed ones:

// Returns either "Good doggy" or "Some non-dog"
match(
  pet,
  {
    dog: name => `Good ${name}`
  },
  () => "Some non-dog"
);

Either all cases must be explicitly handled or there has to be a default fallback (but not both at the same time). Also extra cases, such as pony: name => ..., are not allowed.

License

This library is licensed under the MIT license. See LICENSE.

You can’t perform that action at this time.