Skip to content

Instantly share code, notes, and snippets.

@Lucifier129
Last active January 18, 2022 04:38
Show Gist options
  • Save Lucifier129/d101f181e8ff45029218d4caa3149559 to your computer and use it in GitHub Desktop.
Save Lucifier129/d101f181e8ff45029218d4caa3149559 to your computer and use it in GitHub Desktop.
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