patturn
v0.1.2
Published
Functional match expressions for JS
Downloads
6
Maintainers
Readme
patturn
The missing match
expression for JavaScript. Use functional pattern matching constructs familiar from languages like Rust to sidestep the limitations of switch
statements, reduce messy ternary expressions and if/else chains, and assign variables based on arbitrarily complex conditions.
Installation
yarn add patturn
or
npm install --save patturn
Match expressions
The match()
function behaves like a superpowered switch
statement that returns a value from the matched branch. It accepts a value to match against, an array of matchers, and an optional default return value.
import { match } from "patturn";
const ANSWER = 42;
const result = match(
ANSWER,
[
[0, "zilch"],
[3, "the magic number"],
[42, "the meaning of life"],
[420, "nothing to see here, officer"],
],
"no match"
);
// result: "the meaning of life"
The return types may be heterogeneous, and when using TypeScript can be inferred, or constrained as needed.
Guards and Returns
Each matcher consists of a guard and a return. Guards check if a value matches a condition, and returns specify the value to return from the match. Each can be a value, expression, array of values, function called with the value, or any combination thereof:
import { match } from "patturn";
const name = "benedict";
match(name, [
["thomas", "t-bone"],
[(n) => n.includes("ben"), `${name} cumberbatch?`],
[(n) => n.length > 8, (_n) => "too long, don't care"],
]); // returns "benedict cumberbatch?"
Note: guards use strict equality, or the boolean return value if a function.
Guard Lists
To match multiple values in a single match branch, simply pass in an array of values as the guard. This is the equivalent of the fallthrough behavior in switch
, and any matching value will immediately break with the associated return:
import { match } from "patturn";
const flavor = "strawberry";
const preference = match(
flavor,
[
[["chocolate", "vanilla"], "obviously good"],
[["mint chip", "strawberry"], "kinda okay"], // matches second guard case
["pistachio", "lowkey favorite"],
["rocky road", "too much going on"],
],
"no opinion"
);
// preference: "kinda okay"
Order matters
Ordering of matchers is important -- the first guard to pass is the one used. In the example below, both the third and fourth guards would pass, but the fourth is never run:
import { match } from "patturn";
type User = { name: string; id: number };
const me: User = { name: "rekt", id: 32 };
match<User, boolean | null>(me, [
[(u) => u.id === 1, true],
[(u) => u.name === "he-who-must-not-be-named", null],
[(u) => u.id < 1000, true],
[(u) => u.name === "rekt", false],
]); // returns `true`
Match Signature
function match<In, Out = In>(input: In, matchers: Array<MatchBranch<In, Out>>, defaultValue?: Out): Out | undefined;
type MatchBranch<In, Out> = [Guard<In>, Return<In, Out>];
type Guard<In> = In | In[] | ((input: In) => boolean);
type Return<In, Out> = Out | ((input: In) => Out);
When statements
The when()
function behaves much like match()
, but doesn't return a value. It has the added option of running lazily, stopping after the first match, or greedily and running through every match. It's also a lot like a switch
, useful for running side-effects based on complex conditions.
import { when } from "patturn";
const album = { artist: "Radiohead", title: "OK Computer", year: 1997 };
when(
album,
[
[
(a) => a.year >= 1990 && a.year <= 2000,
(_) => console.log("playing 90's music..."),
],
[(a) => a.artist === "Sisqo", () => process.exit(1)],
[(a) => a.artist === "Radiohead", () => setVolume(100)],
],
false
);
// - logs "playing 90's music..."
// - blasts volume
When Signature
function when<In>(input: In, matchers: Array<WhenBranch<In>>, lazy?: boolean): void;
type WhenBranch<In> = [Guard<In>, (input: In) => void];
type Guard<In> = In | In[] | ((input: In) => boolean);
License
MIT © Tobias Fried