Last active
January 27, 2022 05:32
-
-
Save Willmo36/6295b5b0ddec3277ee3cf0684a0e89b3 to your computer and use it in GitHub Desktop.
TypeScript - Generic pattern matching
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
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; | |
type Union<T extends string> = Record<T, string>; | |
type MatchHandlers<T extends string, P extends Union<T>, R> = P extends any ? Record<P[T], (p: P) => R> : never; | |
type DefaultedOrFullHandlers<T extends string, P extends Union<T>, R> = | |
| (Partial<MatchHandlers<T, P, R>> & { otherwise: () => R }) | |
| UnionToIntersection<MatchHandlers<T, P, R>>; | |
/** | |
* Create a match function based on the given tag property. | |
* For TS unions, we must first know the tag property | |
* @param tagProp - The tag of the union to switch on | |
* | |
* @example | |
* | |
const match = makeMatcher('type'); | |
type A = { type: 'A'; foo: number }; | |
type B = { type: 'B'; bar: string }; | |
type C = { type: 'C'; baz: Date }; | |
type Promo = A | B | C; | |
// Ensure all cases are handled | |
const full = match<Promo, string>({ | |
A: (a) => a.foo.toString(), | |
B: (b) => b.bar, | |
C: (c) => c.baz.toDateString(), | |
}); | |
const full_result = full({ type: 'A', foo: 123 }); | |
// Partial cases with fallback | |
const defaulted = match<Promo, string>({ | |
otherwise: () => 'hello', | |
}); | |
const defaulted_result = defaulted({ type: 'B', bar: 'runs otherwise case' }); | |
//Partial cases with no fallback will not compile | |
const missingCase_no_compile = match<Promo, string>({ | |
A: () => 'A', | |
B: () => 'B', | |
}); | |
*/ | |
export function makeMatcher<T extends string>(tagProp: T) { | |
return function match<P extends Union<T>, R>(handlers: DefaultedOrFullHandlers<T, P, R>) { | |
return (p: P): R => { | |
const h = handlers as MatchHandlers<T, P, R> & Record<'otherwise', () => R>; | |
const key: P[T] = p[tagProp]; | |
const handler = h[key] ?? h.otherwise; | |
return handler(p); | |
}; | |
}; | |
} | |
export const matchTag = makeMatcher('tag'); | |
export const matchType = makeMatcher('type'); | |
export const matchFpts = makeMatcher('_tag'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment