Last active
September 17, 2019 20:32
-
-
Save rhietala/68cea10df8b81348df77177afdef9527 to your computer and use it in GitHub Desktop.
poor man's pattern matching (of values, not types) with lodash cond
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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