Skip to content

Commit 820b1ea

Browse files
committed
added Enums and Maybe/Option::or/and
1 parent 47c6748 commit 820b1ea

File tree

5 files changed

+210
-8
lines changed

5 files changed

+210
-8
lines changed

src/enum.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Maybe } from "./maybe";
2+
import { Option } from "./option"
3+
4+
export type EnumInstance<T> = T extends TranslateTypeToStatic<infer U> ? (TranslateTypeToInstance<U> & InstanceType<T>) : never;
5+
6+
export const Default = Symbol("Default");
7+
8+
interface BaseInstance<T extends { [key: string]: { new(...args: any): any } }> {
9+
when<K extends keyof T, RT>(object: (...args: ConstructorParameters<T[K]>) => TranslateTypeToWellDefined<T, K>, handler: (instance: InstanceType<T[K]>) => RT): RT extends Promise<infer U> ? Maybe<U> : Option<RT>;
10+
asyncWhen<K extends keyof T, RT>(object: (...args: ConstructorParameters<T[K]>) => TranslateTypeToWellDefined<T, K>, handler: (instance: InstanceType<T[K]>) => Promise<RT>): Maybe<RT>;
11+
match<K extends keyof T, H extends ({
12+
[key in keyof T]?: (instance: InstanceType<T[key]>) => any
13+
} & Record<typeof Default, () => any> | {
14+
[key in keyof T]: (instance: InstanceType<T[key]>) => any
15+
})>(handlers: H): ReturnType<Exclude<H[keyof H], undefined>>;
16+
}
17+
18+
type TranslateTypeToStatic<T extends { [key: string]: { new(...args: any): any } }> = {
19+
[K in keyof T]: <V extends { new(...args: any): any }>(this: V, ...args: ConstructorParameters<T[K]>) => TranslateTypeToWellDefined<T, K> & InstanceType<V>;
20+
} & X<T>;
21+
22+
interface X<T extends { [key: string]: { new(...args: any): any } }> {
23+
new(current: keyof T, value: InstanceType<T[keyof T]>): TranslateTypeToInstance<T>;
24+
}
25+
26+
type TranslateTypeToInstance<T extends { [key: string]: { new(...args: any): any } }> = BaseInstance<T> & {
27+
[K in keyof T as `is${Capitalize<string & K>}`]: (a: TranslateTypeToInstance<T>) => a is TranslateTypeToWellDefined<T, K>;
28+
}
29+
30+
type TranslateTypeToWellDefined<T extends { [key: string]: { new(...args: any): any } }, K extends keyof T> = TranslateTypeToInstance<T> & {
31+
[key in K]: InstanceType<T[key]>;
32+
}
33+
34+
export function Enum<T extends { [key: string]: { new (...args: any): any }}>(classObject: T) {
35+
const base = class<K extends keyof T> {
36+
[Symbol.toStringTag]() { return name }
37+
38+
constructor(
39+
public current: K,
40+
public value: InstanceType<T[K]>,
41+
) {}
42+
43+
match<RT>(handlers: { [key in keyof T]?: (instance: InstanceType<T[key]>) => RT } & (Record<typeof Default, () => RT> | {})): RT {
44+
const handler = handlers[this.current];
45+
46+
if (handler) {
47+
return handler(this.value);
48+
}
49+
50+
if (handlers[Default as any] !== undefined) {
51+
return (handlers[Default as any] as any)();
52+
}
53+
54+
throw new Error(`PANIC! No handler for ${String(this.current)}!`);
55+
}
56+
};
57+
58+
const baseAny = base as any;
59+
60+
const keys = Object.keys(classObject) as (keyof T)[];
61+
62+
const map: any = {};
63+
64+
for (const key of keys) {
65+
classObject[key].prototype[Symbol.toStringTag] = () => key;
66+
67+
const stringKey = String(key);
68+
const capitalized = stringKey.charAt(0).toUpperCase() + stringKey.slice(1);
69+
70+
baseAny.prototype[`is${capitalized}`] = function (a: TranslateTypeToInstance<T>): a is TranslateTypeToWellDefined<T, keyof T> {
71+
return (a as any).current === key;
72+
}
73+
74+
Object.defineProperty(baseAny.prototype, stringKey, {
75+
get() {
76+
if (this.current !== key) {
77+
throw new Error(`Tried to access ${String(key)} when current is ${this.current}`);
78+
}
79+
80+
return this.value;
81+
}
82+
});
83+
84+
map[key] = baseAny[key] = function (...args: any[]) {
85+
return new this(key, new classObject[key](...args));
86+
}
87+
}
88+
89+
baseAny.prototype.when = function when<K1 extends T[keyof T], RT>(object: K1, handler: (instance: InstanceType<K1>) => RT): Option < RT > {
90+
if(map[this.current] as any === object as any) {
91+
return Option.some(handler(this.value as unknown as InstanceType<K1>));
92+
}
93+
94+
return Option.none();
95+
}
96+
97+
baseAny.prototype.asyncWhen = function asyncWhen<K1 extends T[keyof T], RT>(object: K1, handler: (instance: InstanceType<K1>) => Promise<RT>): Maybe<RT> {
98+
if(map[this.current] as any === object as any) {
99+
return Maybe.new<RT>(m => {
100+
handler(this.value as unknown as InstanceType<K1>).then(m.fullfill).catch(m.unfullfill);
101+
});
102+
}
103+
104+
return Maybe.Unfullfilled();
105+
}
106+
107+
return baseAny as TranslateTypeToStatic<T>;
108+
}

src/future.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,11 @@ export class Future<T, E> {
212212
});
213213
}
214214

215-
public match(): Matcher {
215+
public match() {
216216
return Matcher.new(evaluator => this.map(evaluator))
217217
}
218218

219-
public flatMatch(): Matcher {
219+
public flatMatch() {
220220
return Matcher.new(evaluator => this.flatMap(evaluator))
221221
}
222222

src/match.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Future } from "./future";
33

44
const MatchFailed = Symbol('MatchFailed');
55

6-
export class Matcher {
6+
export class Matcher<V = never> {
77
static new(terminationHandler: (evaluator: (data: any) => any) => any) {
88
return new Matcher(terminationHandler);
99
}
@@ -28,7 +28,11 @@ export class Matcher {
2828
return this.terminationHandler(this.evaluate.bind(this));
2929
}
3030

31-
instanceof<T, R>(type: new (...args: any[]) => T, matcher: (x: T) => R): Matcher {
31+
public end() {
32+
return this.otherwise(undefined);
33+
}
34+
35+
instanceof<T, R>(type: new (...args: any[]) => T, matcher: (x: T) => R): Matcher<V | R> {
3236
this.checks.push((data: any) => {
3337
if (data instanceof type) {
3438
return matcher(data);
@@ -40,7 +44,7 @@ export class Matcher {
4044
return this;
4145
}
4246

43-
is<T, R>(value: T, matcher: (x: T) => R): Matcher {
47+
is<T, R>(value: T, matcher: (x: T) => R): Matcher<V | R> {
4448
this.checks.push((data: any) => {
4549
if (data === value) {
4650
return matcher(data);
@@ -52,7 +56,7 @@ export class Matcher {
5256
return this;
5357
}
5458

55-
not<T, R>(value: T, matcher: (x: T) => R): Matcher {
59+
not<T, R>(value: T, matcher: (x: T) => R): Matcher<V | R> {
5660
this.checks.push((data: any) => {
5761
if (data !== value) {
5862
return matcher(data);
@@ -64,15 +68,63 @@ export class Matcher {
6468
return this;
6569
}
6670

67-
otherwise<R>(constant: R): Matcher {
71+
gt<T, R>(value: T, matcher: (x: T) => R): Matcher<V | R> {
72+
this.checks.push((data: any) => {
73+
if (data > value) {
74+
return matcher(data);
75+
}
76+
77+
return MatchFailed;
78+
});
79+
80+
return this;
81+
}
82+
83+
gte<T, R>(value: T, matcher: (x: T) => R): Matcher<V | R> {
84+
this.checks.push((data: any) => {
85+
if (data >= value) {
86+
return matcher(data);
87+
}
88+
89+
return MatchFailed;
90+
});
91+
92+
return this;
93+
}
94+
95+
lt<T, R>(value: T, matcher: (x: T) => R): Matcher<V | R> {
96+
this.checks.push((data: any) => {
97+
if (data < value) {
98+
return matcher(data);
99+
}
100+
101+
return MatchFailed;
102+
});
103+
104+
return this;
105+
}
106+
107+
lte<T, R>(value: T, matcher: (x: T) => R): Matcher<V | R> {
108+
this.checks.push((data: any) => {
109+
if (data <= value) {
110+
return matcher(data);
111+
}
112+
113+
return MatchFailed;
114+
});
115+
116+
return this;
117+
}
118+
119+
otherwise<R>(constant: R): V | R {
68120
this.checks.push((data: any) => {
69121
return constant;
70122
});
71123

72124
return this.terminate();
73125
}
74126

75-
else<R>(generator: () => R): Matcher {
127+
else<R>(generator: () => R): V | R {
76128
this.checks.push((data: any) => {
77129
return generator();
78130
});

src/maybe.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,32 @@ export class Maybe<T> {
148148
});
149149
});
150150
}
151+
152+
or(maybe: Maybe<T>): Maybe<T> {
153+
if (this.isFullfilled())
154+
return this;
155+
156+
return Maybe.new((maybe) => {
157+
this.then(value => {
158+
if (value.isNone())
159+
maybe.unfullfill();
160+
else
161+
maybe.fullfill(value.unwrap());
162+
});
163+
});
164+
}
165+
166+
and(maybe: Maybe<T>): Maybe<T> {
167+
if (this.isUnfullfilled())
168+
return this;
169+
170+
return Maybe.new((maybe) => {
171+
this.then(value => {
172+
if (value.isNone())
173+
maybe.unfullfill();
174+
else
175+
maybe.fullfill(value.unwrap());
176+
});
177+
});
178+
}
151179
}

src/option.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,18 @@ export class Option<T> {
6868

6969
return fn(this.value as T);
7070
}
71+
72+
or(maybe: Option<T>): Option<T> {
73+
if (this.isSome())
74+
return this;
75+
76+
return maybe;
77+
}
78+
79+
and(maybe: Option<T>): Option<T> {
80+
if (this.isNone())
81+
return this;
82+
83+
return maybe;
84+
}
7185
}

0 commit comments

Comments
 (0)