type ResultInner<T, E> = | { type: 'OK', value: T } | { type: 'ERROR', error: E }; interface MatchHandlers<T, E, R> { ok: (value: T) => R, err: (error: E) => R, } class Result<T, E> { inner: ResultInner<T, E>; constructor(inner: ResultInner<T, E>) { this.inner = inner; } public static newOk<T, E>(value: T): Result<T, E> { const inner: ResultInner<T, E> = { type: 'OK', value, }; return new Result<T, E>(inner); } public static newErr<T, E>(error: E): Result<T, E> { const inner: ResultInner<T, E> = { type: 'ERROR', error, }; return new Result<T, E>(inner); } match<R>(handlers: MatchHandlers<T, E, R>): R { switch (this.inner.type) { case 'OK': { return handlers.ok(this.inner.value); } case 'ERROR': { return handlers.err(this.inner.error); } default: throw new Error("Result.match() is supposed to be exhaustive"); } } mapOk<T2>(transform: (val: T) => T2): Result<T2, E> { return this.match({ ok: (val) => Result.newOk(transform(val)), err: (e) => Result.newErr(e), }); } isOk(): boolean { return this.match({ ok: (_) => true, err: (_) => false, }); } isErr(): boolean { return !this.isOk(); } err(): E | null { return this.match({ ok: (_) => null, err: (e) => e, }); } ok(): T | null { return this.match({ ok: (val) => val, err: (_) => null, }); } unwrap(): T { return this.match({ ok: (val) => val, err: (e) => { throw new Error(`Cannot unwrap Result with an error: ${e}`); }, }); } }