MatchesWith
A simple pattern matching library
yarn add @baconsfoster/matches-with
// or
npm install --save @baconsfoster/matches-with
Overview
Provides:
-
matchesWith
: the pattern matching function -
MatchesWith
: A class with both a static and instance member assigned the pattern matching function -
Any
: A default-case predicate -
isInstanceOf
: A function that returns a predicate for a given class constructor -
isTypeOf
: A function that returns a predicate for a given string based on thetypeof
operator
Examples:
class ErrorA{}
class ErrorB{}
const isErrorA = isInstanceOf(ErrorA);
const isErrorB = isInstanceOf(ErrorB);
const isNumber = isTypeOf('number');
const isBoolean = isTypeOf('boolean');
try {
await someFunctionThatCouldThrowManyDifferentErrors();
await someOtherFunctionThatMightThrowAStringOrNumber();
} catch (e) {
matchesWith(e, [
[isErrorA, () => console.log('e is of type ErrorA')],
[isErrorB, () => console.log('e is of type ErrorB')],
[isNumber, () => console.log(`e is a number: ${e})`],
[isBoolean, () => console.log('who throws booleans?')],
[Any, () => console.log('this is the generic fallback handler')]
]);
}
Note that the order of predicates matters:
matchesWith(val, [
[Any, () => console.log('`Any` matches anything, so the rest aren\'t tested')],
[somethingElse, () => console.log('can never be reached')]
])
Note that matchesWith
returns the value of invoking the matched clause. If no cases are matched, NO_MATCH is returned instead.
import { matchesWith, isTypeof, NO_MATCH } from '@baconsfoster/matchesWith';
const isString = isTypeOf('string');
const x = matchesWith(true, [
[isString, () => 'a string was matched']
]);
x === NO_MATCH; // true
Inheriting and Mixing In matching
MatchesWith
is a class that can be utilized for inheritance or mixing in. Examples:
class BaseClass extends MatchesWith {}
BaseClass.matchesWith(someVal, [
// ... predicate clauses
]);
const test = new BaseClass();
test.matchesWith([
// predicate clauses tested against the 'test' variable (i.e. self)
]);
This is mostly handy where you are operating on a sum type- say a value that is one of several child classes. For example:
class BaseClass extends MatchesWith{}
class ChildA extends BaseClass{}
class ChildB extends BaseClass{}
function returnChildAOrB() { /* returns either an instance of ChildA or ChildB */}
const test = returnChildAOrB();
test.matchesWith(test, [
[isChildA, () => console.log('got child a')],
[isChildB, () => console.log('got child b')]
]);
TypeScript Caveats and Using as a Mixin
Rather than inheriting from MatchesWith, which may not be practical, you can use implements
in TypeScript
to treat the class's public members as an interface; from there, you just copy them over. Example:
class B implements MatchesWith {
static matchesWith = MatchesWith.matchesWith;
matchesWith = MatchesWith.prototype.matchesWith;
}
This way, you can get both a parent from a different inheritance chain while "mixing in" the MatchesWith behavior, if so desired.