Refactor Option type to one implementation instead of two
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-07-03 17:49:15 -04:00
parent 1b3e9d9796
commit 4313b923bf

View File

@ -1,175 +1,127 @@
/**
* The sad, lonely enum that should be more tightly coupled
* to the Option type...but this isn't Rust
*/
enum OptionType {
Some = 'Some',
None = 'None',
}
const isOption = <T>(v: any): v is Option<T> => v instanceof Option;
/** /**
* Rust-style optional type * Rust-style optional type
* *
* Based on https://gist.github.com/s-panferov/575da5a7131c285c0539 * Based on https://gist.github.com/s-panferov/575da5a7131c285c0539
*/ */
export default interface Option<T> { export class Option<T> {
isSome(): boolean; /**
isNone(): boolean; * The placeholder for the 'None' value type
isSomeAnd(fn: (a: T) => boolean): boolean; */
isNoneAnd(fn: () => boolean): boolean; private static _noneInstance: Option<any> = new Option();
unwrap(): T | never;
unwrapOr(def: T): T;
unwrapOrElse(f: () => T): T;
map<U>(f: (a: T) => U): Option<U>;
mapOr<U>(def: U, f: (a: T) => U): U;
mapOrElse<U>(def: () => U, f: (a: T) => U): U;
and<U>(optb: Option<U>): Option<U>;
andThen<U>(f: (a: T) => Option<U>): Option<U>;
or(optb: Option<T>): Option<T>;
orElse(f: () => Option<T>): Option<T>;
}
class _Some<T> implements Option<T> { /**
private value: T; * Is this a 'Some' or a 'None'?
*/
private optionType: OptionType;
constructor(v: T) { /**
this.value = v; * The value for the 'Some' type
} */
private value?: T;
static wrapNull<T>(value: T): Option<T> { private constructor(v?: T | null) {
if (value == null) { if (v !== undefined && v !== null) {
return None; this.optionType = OptionType.Some;
this.value = v;
} else { } else {
return new _Some<T>(value); this.optionType = OptionType.None;
this.value = undefined;
} }
} }
map<U>(fn: (a: T) => U): Option<U> { public static get None(): Option<any> {
return new _Some(fn(this.value)); return <Option<any>> Option._noneInstance;
} }
mapOr<U>(_def: U, f: (a: T) => U): U { public static Some<X>(v: X): Option<X> {
return f(this.value); return new Option(v);
} }
mapOrElse<U>(_def: () => U, f: (a: T) => U): U { public static from<X>(v: any): Option<X> {
return f(this.value); return (isOption(v)) ? Option.from(v.unwrap()) : new Option(v);
} }
isSome(): boolean { isSome(): boolean {
return true; return this.optionType === OptionType.Some && this.value !== undefined;
} }
isNone(): boolean { isNone(): boolean {
return false; return this.optionType === OptionType.None;
} }
isSomeAnd(fn: (a: T) => boolean): boolean { isSomeAnd(fn: (a: T) => boolean): boolean {
return fn(this.value); return this.isSome() ? fn(this.unwrap()) : false;
}
isNoneAnd(_fn: () => boolean): boolean {
return false;
}
unwrap(): T {
return this.value;
}
unwrapOr(_def: T): T {
return this.value;
}
unwrapOrElse(_f: () => T): T {
return this.value;
}
and<U>(optb: Option<U>): Option<U> {
return optb;
}
andThen<U>(f: (a: T) => Option<U>): Option<U> {
return f(this.value);
}
or(_optb: Option<T>): Option<T> {
return this;
}
orElse(_f: () => Option<T>): Option<T> {
return this;
}
toString(): string {
return 'Some ' + this.value;
}
}
class _None<T> implements Option<T> {
constructor() {
}
map<U>(_fn: (a: T) => U): Option<U> {
return <Option<U>> _None._instance;
}
isSome(): boolean {
return false;
}
isNone(): boolean {
return true;
}
isSomeAnd(_fn: (a: T) => boolean): boolean {
return false;
} }
isNoneAnd(fn: () => boolean): boolean { isNoneAnd(fn: () => boolean): boolean {
return fn(); return this.isNone() ? fn() : false;
} }
unwrap(): never { map<U>(fn: (a: T) => U): Option<U> {
return this.isSome() ? new Option(fn(this.unwrap())) : Option._noneInstance;
}
mapOr<U>(def: U, f: (a: T) => U): U {
return this.isSome() ? f(this.unwrap()) : def;
}
mapOrElse<U>(def: () => U, f: (a: T) => U): U {
return this.isSome() ? f(this.unwrap()) : def();
}
unwrap(): T | never {
if (this.isSome() && this.value !== undefined) {
return this.value;
}
console.error('None.unwrap()'); console.error('None.unwrap()');
throw 'None.get'; throw 'None.get';
} }
unwrapOr(def: T): T { unwrapOr(def: T): T {
return def; return this.isSome() ? this.unwrap() : def;
} }
unwrapOrElse(f: () => T): T { unwrapOrElse(f: () => T): T {
return f(); return this.isSome() ? this.unwrap() : f();
} }
mapOr<U>(def: U, _f: (a: T) => U): U { and<U>(optb: Option<U>): Option<U> {
return def; return this.isSome() ? optb : Option._noneInstance;
} }
mapOrElse<U>(def: () => U, _f: (a: T) => U): U { andThen<U>(f: (a: T) => Option<U>): Option<U> {
return def(); return this.isSome() ? f(this.unwrap()) : Option._noneInstance;
}
and<U>(_optb: Option<U>): Option<U> {
return _None.instance<U>();
}
andThen<U>(_f: (a: T) => Option<U>): Option<U> {
return _None.instance<U>();
} }
or(optb: Option<T>): Option<T> { or(optb: Option<T>): Option<T> {
return optb; return this.isNone() ? optb : this;
} }
orElse(f: () => Option<T>): Option<T> { orElse(f: () => Option<T>): Option<T> {
return f(); return this.isNone() ? f() : this;
} }
private static _instance: Option<any> = new _None(); toString(): string {
const innerValue = (this.value !== undefined)
? JSON.stringify(this.value)
: '';
const prefix = this.optionType.valueOf();
public static instance<X>(): Option<X> { return (innerValue.length > 0) ? `${prefix} (${innerValue})` : prefix;
return <Option<X>> _None._instance;
}
public toString(): string {
return 'None';
} }
} }
export const None: Option<any> = _None.instance(); export const { Some, None } = Option;
export default Option;
export function Some<T>(value: T): Option<T> {
return _Some.wrapNull(value);
}