Last active
October 1, 2019 14:01
-
-
Save tstodter/2ea1671170b6927c0ed3b6c483e328dd to your computer and use it in GitHub Desktop.
Pattern matching for sum types/algebraic data types in Typescript
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
export type UnionType = {kind: string}; | |
export type UnionMemberByKind<U, K> = Extract<U, { kind: K }> | |
export type UnionMatchObj<U extends UnionType, Ret> = { | |
[K in U['kind']]: (unionMember: UnionMemberByKind<U, K>) => Ret | |
}; | |
export type Merge<M extends {}, N extends {}> = { | |
[P in Exclude<keyof M, keyof N>]: M[P] | |
} & N; | |
export const match = <U extends UnionType, RetT>( | |
fObj: UnionMatchObj<U, RetT> | |
) => ( | |
unionVal: U | |
) => ( | |
fObj[unionVal.kind as U['kind']](unionVal as any) | |
); | |
export const matcher = | |
<U extends UnionType>() => | |
<RetT>(fObj: UnionMatchObj<U, RetT>) => ( | |
match<U, RetT>(fObj) | |
); | |
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> | |
export const makeFactory = <T extends UnionType>(kind: T['kind']) => | |
(init: PartialBy<T, 'kind'>): T => ({ | |
...init, | |
kind | |
} as T); | |
/* ///////////////////////////////////// */ | |
// For example... | |
/* ///////////////////////////////////// */ | |
type Move = { | |
kind: 'move'; | |
direction: string; | |
player: string; | |
}; | |
type Attack = { | |
kind: 'attack'; | |
target: string; | |
player: string; | |
}; | |
type LeaveDungeon = { | |
kind: 'leaveDungeon'; | |
player: string; | |
}; | |
type DungeonEvent = Move | Attack | LeaveDungeon; | |
const Move = makeFactory<Move>('move'); | |
const Attack = makeFactory<Attack>('attack'); | |
const LeaveDungeon = makeFactory<LeaveDungeon>('leaveDungeon'); | |
const toString = match<DungeonEvent, string>({ | |
move: ({direction, player}) => `${player} moving ${direction}`, | |
attack: ({target, player}) => `${player} is attacking ${target}`, | |
leaveDungeon: ({player}) => `${player} has left the dungeon` | |
}); | |
const newEvent = Move({direction: 'east', player: 'Zardul'}); | |
console.log(toString(newEvent)); | |
const secondEvent = LeaveDungeon({player: 'Zardul'}); | |
console.log(toString(secondEvent)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment