Refactor Option type to one implementation instead of two
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
This commit is contained in:
parent
1b3e9d9796
commit
4313b923bf
@ -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);
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user