Last active
January 18, 2022 04:38
-
-
Save Lucifier129/d101f181e8ff45029218d4caa3149559 to your computer and use it in GitHub Desktop.
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 Prettier<T> = { | |
[key in keyof T]: T[key]; | |
} | |
type Tagged<Tag extends string> = { | |
tag: Tag; | |
}; | |
type Field<Key extends string, Value> = { | |
[key in Key]: Value; | |
}; | |
type TaggedData<Tag extends string, Data> = Prettier< | |
Tagged<Tag> & Field<Tag, Data> | |
>; | |
type Visitor<T, R> = T extends Tagged<string> | |
? keyof T extends 'tag' | |
? { | |
[key in T['tag']]: () => R; | |
} | |
: T extends { | |
[key in T['tag']]: infer Data; | |
} | |
? { | |
[key in T['tag']]: (data: Data) => R; | |
} | |
: never | |
: never; | |
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends ( | |
x: infer R | |
) => any | |
? R | |
: never; | |
type Patterns<T extends Tagged<string>, R> = Prettier< | |
UnionToIntersection<Visitor<T, R>> | |
>; | |
const match = <T extends Tagged<string>>(data: T) => { | |
return { | |
case<R>(patterns: Patterns<T, R>): R { | |
if (data.tag in patterns) { | |
// @ts-ignore | |
return patterns[data.tag](data[data.tag]); | |
} | |
throw new Error(`Unexpected input: ${data}`); | |
}, | |
partial<R>(patterns: Partial<Patterns<T, R>>): R { | |
if (data.tag in patterns) { | |
// @ts-ignore | |
return patterns[data.tag](data[data.tag]); | |
} | |
throw new Error(`Unhandled branch: ${data.tag}`); | |
} | |
}; | |
}; | |
const Tagged = <Tag extends string>(tag: Tag) => { | |
return (): Tagged<Tag> => { | |
return { | |
tag, | |
}; | |
}; | |
}; | |
const TaggedData = <Tag extends string>(tag: Tag) => { | |
return <Data>(data: Data) => { | |
return { | |
tag, | |
[tag]: data, | |
} as TaggedData<Tag, Data>; | |
}; | |
}; | |
type Option<T> = TaggedData<'some', T> | Tagged<'none'>; | |
const None = Tagged('none'); | |
const Some = TaggedData('some'); | |
const show = <T>(data: Option<T>) => { | |
return match(data).case({ | |
some: (value) => `some: ${value}`, | |
none: () => 'none', | |
}); | |
}; | |
const test0: Option<number> = Some(1); | |
const test1: Option<number> = None(); | |
const test2 = show(test0); | |
const test3 = show(test1); | |
console.log({ | |
test0, | |
test1, | |
test2, | |
test3, | |
}); | |
type Test = | |
| TaggedData<'a', number> | |
| TaggedData<'b', string> | |
| TaggedData<'c', boolean> | |
| TaggedData<'d', number[]>; | |
const A = TaggedData('a'); | |
const B = TaggedData('b'); | |
const C = TaggedData('c'); | |
const D = TaggedData('d'); | |
const showTest = (data: Test) => { | |
return match(data).case({ | |
a: (value) => `a: ${value}`, | |
b: (value) => `b: ${value}`, | |
c: (value) => `c: ${value}`, | |
d: (value) => `d: ${value.join(' & ')}`, | |
}); | |
}; | |
const test4: Test = A(1); | |
const test5: Test = B('a'); | |
const test6: Test = C(true); | |
const test7: Test = D([1, 2, 3]); | |
const test8 = showTest(test4); | |
const test9 = showTest(test5); | |
const test10 = showTest(test6); | |
const test11 = showTest(test7); | |
console.log('test', { | |
test4, | |
test5, | |
test6, | |
test7, | |
test8, | |
test9, | |
test10, | |
test11, | |
}); | |
type CounterState = { | |
count: number; | |
}; | |
type CounterAction = | |
| Tagged<'incre'> | |
| Tagged<'decre'> | |
| TaggedData<'increBy', number> | |
| TaggedData<'decreBy', number>; | |
const counterReducer = (state: CounterState, action: CounterAction): CounterState => { | |
if (action.tag === 'incre') { | |
return { | |
...state, | |
count: state.count + 1 | |
} | |
} else if (action.tag === 'decre') { | |
return { | |
...state, | |
count: state.count - 1 | |
} | |
} else if (action.tag === 'increBy') { | |
return { | |
...state, | |
count: state.count + action.increBy | |
} | |
} else if (action.tag === 'decreBy') { | |
return { | |
...state, | |
count: state.count - action.decreBy | |
} | |
} | |
throw new Error(`Unexpected action: ${action}`); | |
} | |
const Incre = Tagged('incre'); | |
const Decre = Tagged('decre'); | |
const IncreBy = TaggedData('increBy'); | |
const counterState0: CounterState = { | |
count: 0 | |
} | |
const counterState1 = counterReducer(counterState0, Incre()); | |
const counterState2 = counterReducer(counterState1, Decre()); | |
const counterState3 = counterReducer(counterState2, IncreBy(2)); | |
console.log('counter', { | |
counterState0, | |
counterState1, | |
counterState2, | |
counterState3, | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment