2024-07-03 16:13:29 -04:00
|
|
|
/**
|
2024-07-03 17:49:15 -04:00
|
|
|
* The sad, lonely enum that should be more tightly coupled
|
|
|
|
* to the Option type...but this isn't Rust
|
2024-07-03 16:13:29 -04:00
|
|
|
*/
|
2024-07-03 17:49:15 -04:00
|
|
|
enum OptionType {
|
|
|
|
Some = 'Some',
|
|
|
|
None = 'None',
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 19:09:04 -04:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Typeguards to handle Some/None difference
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
const isOption = <T>(v: any): v is Option<T> => v instanceof Option;
|
2024-07-03 16:13:29 -04:00
|
|
|
|
2024-07-03 19:09:04 -04:00
|
|
|
class OptionInnerNone<T> {
|
|
|
|
public type: OptionType = OptionType.None;
|
|
|
|
}
|
|
|
|
|
|
|
|
class OptionInnerSome<T> {
|
|
|
|
public type: OptionType = OptionType.Some;
|
|
|
|
|
|
|
|
constructor(public value: T) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
type OptionInnerType<T> = OptionInnerNone<T> | OptionInnerSome<T>;
|
|
|
|
|
|
|
|
const isSome = <T>(v: OptionInnerType<T>): v is OptionInnerSome<T> =>
|
|
|
|
'value' in v && v.type === OptionType.Some;
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
/**
|
|
|
|
* Rust-style optional type
|
|
|
|
*
|
|
|
|
* Based on https://gist.github.com/s-panferov/575da5a7131c285c0539
|
|
|
|
*/
|
|
|
|
export class Option<T> {
|
|
|
|
/**
|
|
|
|
* The placeholder for the 'None' value type
|
|
|
|
*/
|
2024-07-03 19:09:04 -04:00
|
|
|
private static _None: Option<any> = new Option(null);
|
2024-07-03 17:49:15 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Is this a 'Some' or a 'None'?
|
|
|
|
*/
|
2024-07-03 19:09:04 -04:00
|
|
|
private readonly inner: OptionInnerType<T>;
|
2024-07-03 17:49:15 -04:00
|
|
|
|
2024-07-03 19:09:04 -04:00
|
|
|
private constructor(v?: T) {
|
|
|
|
this.inner = (v !== undefined && v !== null)
|
|
|
|
? new OptionInnerSome(v)
|
|
|
|
: new OptionInnerNone();
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
public static get None(): Option<any> {
|
2024-07-03 19:09:04 -04:00
|
|
|
return Option._None;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 19:09:04 -04:00
|
|
|
public static Some<X>(v: any): Option<X> {
|
|
|
|
return Option.from(v);
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 19:09:04 -04:00
|
|
|
public static from<X>(v?: any): Option<X> {
|
2024-07-03 17:49:15 -04:00
|
|
|
return (isOption(v)) ? Option.from(v.unwrap()) : new Option(v);
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
isSome(): boolean {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner);
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
isNone(): boolean {
|
2024-07-03 19:09:04 -04:00
|
|
|
return !this.isSome();
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
isSomeAnd(fn: (a: T) => boolean): boolean {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? fn(this.inner.value) : false;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
isNoneAnd(fn: () => boolean): boolean {
|
|
|
|
return this.isNone() ? fn() : false;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
map<U>(fn: (a: T) => U): Option<U> {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? Option.from(fn(this.inner.value)) : Option.None;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
mapOr<U>(def: U, f: (a: T) => U): U {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? f(this.inner.value) : def;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
mapOrElse<U>(def: () => U, f: (a: T) => U): U {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? f(this.inner.value) : def();
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
unwrap(): T | never {
|
2024-07-03 19:09:04 -04:00
|
|
|
if (isSome(this.inner)) {
|
|
|
|
return this.inner.value;
|
2024-07-03 17:49:15 -04:00
|
|
|
}
|
2024-07-03 16:13:29 -04:00
|
|
|
|
|
|
|
console.error('None.unwrap()');
|
|
|
|
throw 'None.get';
|
|
|
|
}
|
|
|
|
|
|
|
|
unwrapOr(def: T): T {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? this.inner.value : def;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
unwrapOrElse(f: () => T): T {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? this.inner.value : f();
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
and<U>(optb: Option<U>): Option<U> {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? optb : Option.None;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
andThen<U>(f: (a: T) => Option<U>): Option<U> {
|
2024-07-03 19:09:04 -04:00
|
|
|
return isSome(this.inner) ? f(this.inner.value) : Option.None;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
or(optb: Option<T>): Option<T> {
|
2024-07-03 17:49:15 -04:00
|
|
|
return this.isNone() ? optb : this;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
orElse(f: () => Option<T>): Option<T> {
|
2024-07-03 17:49:15 -04:00
|
|
|
return this.isNone() ? f() : this;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
toString(): string {
|
2024-07-03 19:09:04 -04:00
|
|
|
const innerValue = (isSome(this.inner))
|
|
|
|
? JSON.stringify(this.inner.value)
|
2024-07-03 17:49:15 -04:00
|
|
|
: '';
|
2024-07-03 19:09:04 -04:00
|
|
|
const prefix = this.inner.type.valueOf();
|
2024-07-03 16:13:29 -04:00
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
return (innerValue.length > 0) ? `${prefix} (${innerValue})` : prefix;
|
2024-07-03 16:13:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-03 17:49:15 -04:00
|
|
|
export const { Some, None } = Option;
|
|
|
|
export default Option;
|