Skip to content

Instantly share code, notes, and snippets.

@frankpf
Last active July 16, 2020 14:48
Show Gist options
  • Save frankpf/cde7f792580f731dfe886ed2d91bab45 to your computer and use it in GitHub Desktop.
Save frankpf/cde7f792580f731dfe886ed2d91bab45 to your computer and use it in GitHub Desktop.
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
/* Source: https://github.com/Microsoft/TypeScript/pull/21316#issuecomment-364982638 */
type DiscriminateUnion<Union, TagKey extends keyof Union, TagValue extends Union[TagKey]> =
Union extends Record<TagKey, TagValue> ? Union : never
type MatchingFunc<A extends { kind: string }, K extends A['kind'], U> = (a: Omit<DiscriminateUnion<A, 'kind', K>, 'kind'>) => U
function match<A extends { kind: string }>(discriminant: A) {
return <U>(obj:
{ [K in A['kind']]: MatchingFunc<A, K, U> } |
(Partial<{ [K in A['kind']]: MatchingFunc<A, K, U> }> & { default(): U }),
) => {
const objAny = obj as any
const caseFn = objAny[discriminant.kind]
return caseFn
? caseFn(discriminant) as U
: objAny.default()
}
}
/* Example discriminated union */
type Shape = Square | Rectangle | Circle
const rect: Shape = { kind: 'rectangle', width: 10, height: 10 }
const square: Square = { kind: 'square', size: 20 }
const circle: Circle = { kind: 'circle', radius: 30 }
/* Usage of match */
function test(shape: Shape) {
const matchResult = match(shape)({
square({ size }) {
return `I am a square of size ${size}`
},
rectangle({ width, height }) {
return `I am a rectangle of width ${width} and height ${height}`
},
default() {
return `I am something else`
},
})
console.log(matchResult)
}
test(rect)
test(square)
test(circle)
/* Types */
interface Square {
kind: 'square'
size: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
interface Circle {
kind: 'circle'
radius: number
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment