Skip to content

Instantly share code, notes, and snippets.

@rhietala
Last active September 17, 2019 20:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rhietala/68cea10df8b81348df77177afdef9527 to your computer and use it in GitHub Desktop.
Save rhietala/68cea10df8b81348df77177afdef9527 to your computer and use it in GitHub Desktop.
poor man's pattern matching (of values, not types) with lodash cond
import {
cond,
constant,
defaultTo,
fromPairs,
isMatch,
isUndefined,
keys,
map,
omitBy,
values,
zip
} from 'lodash/fp';
/**
* Transform array to an object with array indexes as keys.
*
* So that lodash `isMatch` can be used on arrays.
*
* @param xs array, e.g. ['foo', 'bar', 'baz']
* @return object, e.g. { '0': 'foo', '1': 'bar', '2': 'baz' }
*/
const toIndexedObj = <T>(xs: T[]): { [key: string]: T } =>
fromPairs(zip(keys(xs), values(xs)));
/**
* Filter key-value pairs from object if value is undefined.
*
* For example:
* filterUndefineds({ a: 1, b: undefined, c: 2 })
* => { a: 1, c: 2 }
*
* @param x object with possibly undefined values
* @return object with undefined values removed
*/
const filterUndefineds = omitBy(isUndefined);
/**
* Transform simpler CondPair data structure to LodashCondPair,
* which can be given to lodash `cond`.
*/
type LodashCondPair<O> = [(val: any) => boolean, (val: any) => O];
// prettier-ignore
const transformCondPairs = <O>([matches, value]: [any, O]): LodashCondPair<O> =>
[
(x) => isMatch(filterUndefineds(toIndexedObj(matches)), toIndexedObj(x)),
constant(value)
];
/**
* Simple value pattern matching with lodash
*
* @param xs inputs that are compared to conditions
* @param def default value that is returned when none of the conditions match
* @param pairs condition pairs so that first element is the condition that xs
* is matched against, and second element is the value that is
* returned on successful match. Condition's dimension should be
* the same as xs's, but is not enforced by typescript.
* `undefined` can be used in the condition to match any xs value.
* @return matching condition's value or default if none match
*
* For example:
* match<string, string, number, { b: number; c?: number }, number>(
* ['a', 'b', 3, { b: 2, c: 3 }],
* 321,
* [['a', 'a', undefined, undefined], 1],
* [['a', 'd', undefined, undefined], 2],
* [[undefined, 'b', undefined, { b: 2 }], 3]
* )
* => 3
*
* Matching is done using lodash `isMatch`, which means that for objects,
* partial match is enough (for input { b: 2, c: 3 }, { b: 2 } is a match)
*/
// prettier-ignore
interface Match {
<I1, O>(x: [I1], def: O, ...pairs: Array<[[I1 | undefined], O]>): O;
<I1, I2, O>(x: [I1, I2], def: O, ...pairs: Array<[[I1 | undefined, I2 | undefined], O]>): O;
<I1, I2, I3, O>(x: [I1, I2, I3], def: O, ...pairs: Array<[[I1 | undefined, I2 | undefined, I3 | undefined], O]>): O;
<I1, I2, I3, I4, O>(x: [I1, I2, I3, I4], def: O, ...pairs: Array<[[I1 | undefined, I2 | undefined, I3 | undefined, I4 | undefined], O]>): O;
<I1, I2, I3, I4, I5, O>(x: [I1, I2, I3, I4, I5], def: O, ...pairs: Array<[[I1 | undefined, I2 | undefined, I3 | undefined, I4 | undefined, I5 | undefined], O]>): O;
<I1, I2, I3, I4, I5, I6, O>(x: [I1, I2, I3, I4, I5, I6], def: O, ...pairs: Array<[[I1 | undefined, I2 | undefined, I3 | undefined, I4 | undefined, I5 | undefined, I6 | undefined], O]>): O;
}
const match: Match = (xs: any, def: any, ...pairs: any): any =>
defaultTo(def, cond(map(transformCondPairs, pairs))(xs));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment