From dbcaa682493db22888ee522a7d854d6f2420598e Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 15 Apr 2024 19:41:20 +0200 Subject: [PATCH 01/23] Add iterator test --- test/result.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/result.test.ts b/test/result.test.ts index 57e0e70..356edee 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -495,3 +495,18 @@ describe.concurrent("match", () => { expect(output).toEqual(0); }); }); + +describe("iterator", () => { + it("works with regular yield", () => { + function* gen() { + yield Ok(1); + yield Err("error"); + return Ok(2); + } + + const result = gen(); + expect(result.next().value.unwrap()).toEqual(1); + expect(result.next().value.unwrapErr()).toEqual("error"); + expect(result.next().value.unwrap()).toEqual(2); + }); +}); From 287203e64575b85388d12fb48f5c1589f9c54b91 Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 17 Apr 2024 16:27:12 +0200 Subject: [PATCH 02/23] Try isOk func --- src/result.ts | 20 ++++++++++---------- test/result.test.ts | 33 +++++++++------------------------ 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/result.ts b/src/result.ts index f0d0029..a0fdc09 100644 --- a/src/result.ts +++ b/src/result.ts @@ -27,20 +27,20 @@ export class ResultImpl { throw new Panic(message, {cause: this.value}); } - get isOk(): boolean { + isOk(): this is Ok { return this[variant]; } - get isErr(): boolean { + isErr(): this is Err { return !this[variant]; } get value(): T | undefined { - return this.isOk ? (this[value] as T) : undefined; + return this.isOk() ? (this[value] as T) : undefined; } get error(): E | undefined { - return this.isErr ? (this[value] as E) : undefined; + return this.isErr() ? (this[value] as E) : undefined; } *[Symbol.iterator](): Iterator, T, any> { @@ -68,11 +68,11 @@ export class ResultImpl { * ``` */ match(pattern: ResultMatch): A | B { - return this.isOk ? pattern.Ok(this.value as T) : pattern.Err(this.error as E); + return this.isOk() ? pattern.Ok(this.value as T) : pattern.Err(this.error as E); } matchAsync(pattern: ResultMatchAsync): Promise { - return this.isOk ? pattern.Ok(this.value as T) : pattern.Err(this.error as E); + return this.isOk() ? pattern.Ok(this.value as T) : pattern.Err(this.error as E); } /** @@ -632,10 +632,10 @@ export class ResultImpl { } export interface Ok extends ResultImpl { - readonly isOk: true; - readonly isErr: false; readonly value: T; readonly error: undefined; + isOk(): this is Ok; + isErr(): this is Err; unwrap(): T; unwrapErr(): never; expect(message: string): T; @@ -652,10 +652,10 @@ export function Ok(value?: T): Ok { } export interface Err extends ResultImpl { - readonly isOk: false; - readonly isErr: true; readonly value: undefined; readonly error: E; + isOk(): this is Ok; + isErr(): this is Err; unwrap(): never; unwrapErr(): E; expect(message: string): never; diff --git a/test/result.test.ts b/test/result.test.ts index 356edee..8dc5627 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -17,8 +17,8 @@ describe.concurrent("core", () => { expect(r.isErr).toEqual(false); expect(r.value).toEqual(42); - expectTypeOf(r.isOk).toEqualTypeOf(); - expectTypeOf(r.isErr).toEqualTypeOf(); + // expectTypeOf(r.isOk).toEqualTypeOf<() => true>(); + // expectTypeOf(r.isErr).toEqualTypeOf<() => false>(); expectTypeOf(r.value).toEqualTypeOf(); expectTypeOf(r.error).toEqualTypeOf(); @@ -36,8 +36,8 @@ describe.concurrent("core", () => { expect(r.isErr).toEqual(true); expect(r.error).toEqual("error"); - expectTypeOf(r.isOk).toEqualTypeOf(); - expectTypeOf(r.isErr).toEqualTypeOf(); + // expectTypeOf(r.isOk).toEqualTypeOf<() => false>(); + // expectTypeOf(r.isErr).toEqualTypeOf<() => true>(); expectTypeOf(r.value).toEqualTypeOf(); expectTypeOf(r.error).toEqualTypeOf(); @@ -52,9 +52,9 @@ describe.concurrent("core", () => { const r = TestOk(42); expectTypeOf(r.value).toEqualTypeOf(); expectTypeOf(r.error).toEqualTypeOf(); - if (r.isOk) { - expectTypeOf(r.isOk).toEqualTypeOf(); - expectTypeOf(r.isErr).toEqualTypeOf(); + if (r.isOk()) { + // expectTypeOf(r.isOk).toEqualTypeOf<() => true>(); + // expectTypeOf(r.isErr).toEqualTypeOf<() => false>(); expectTypeOf(r.value).toEqualTypeOf(); expectTypeOf(r.error).toEqualTypeOf(); @@ -64,8 +64,8 @@ describe.concurrent("core", () => { expectTypeOf(r.expect).toEqualTypeOf<(msg: string) => number>(); expectTypeOf(r.expectErr).toEqualTypeOf<(msg: string) => never>(); } else { - expectTypeOf(r.isOk).toEqualTypeOf(); - expectTypeOf(r.isErr).toEqualTypeOf(); + // expectTypeOf(r.isOk).toEqualTypeOf<() => false>(); + // expectTypeOf(r.isErr).toEqualTypeOf<() => true>(); expectTypeOf(r.value).toEqualTypeOf(); expectTypeOf(r.error).toEqualTypeOf(); @@ -495,18 +495,3 @@ describe.concurrent("match", () => { expect(output).toEqual(0); }); }); - -describe("iterator", () => { - it("works with regular yield", () => { - function* gen() { - yield Ok(1); - yield Err("error"); - return Ok(2); - } - - const result = gen(); - expect(result.next().value.unwrap()).toEqual(1); - expect(result.next().value.unwrapErr()).toEqual("error"); - expect(result.next().value.unwrap()).toEqual(2); - }); -}); From 9e6e122a5a47d3874100502ce959df2589a58f0d Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 17 Apr 2024 22:34:22 +0200 Subject: [PATCH 03/23] Update functions --- src/result.ts | 50 ++++++++++++++++++++++----------------------- test/result.test.ts | 44 ++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/result.ts b/src/result.ts index a0fdc09..67719d7 100644 --- a/src/result.ts +++ b/src/result.ts @@ -18,29 +18,13 @@ export class ResultImpl { private readonly [variant]: boolean; private readonly [value]: T | E; - constructor(ok: boolean, x: T | E) { - this[variant] = ok; + constructor(v: boolean, x: T | E) { + this[variant] = v; this[value] = x; } private unwrapFailed(message: string): never { - throw new Panic(message, {cause: this.value}); - } - - isOk(): this is Ok { - return this[variant]; - } - - isErr(): this is Err { - return !this[variant]; - } - - get value(): T | undefined { - return this.isOk() ? (this[value] as T) : undefined; - } - - get error(): E | undefined { - return this.isErr() ? (this[value] as E) : undefined; + throw new Panic(message, {cause: this[value]}); } *[Symbol.iterator](): Iterator, T, any> { @@ -68,11 +52,27 @@ export class ResultImpl { * ``` */ match(pattern: ResultMatch): A | B { - return this.isOk() ? pattern.Ok(this.value as T) : pattern.Err(this.error as E); + return this[variant] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); } matchAsync(pattern: ResultMatchAsync): Promise { - return this.isOk() ? pattern.Ok(this.value as T) : pattern.Err(this.error as E); + return this[variant] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); + } + + value(): T | undefined { + return this.ok().unwrapOr(undefined); + } + + error(): E | undefined { + return this.err().unwrapOr(undefined); + } + + isOk(): this is Ok { + return this[variant]; + } + + isErr(): this is Err { + return !this[variant]; } /** @@ -632,10 +632,10 @@ export class ResultImpl { } export interface Ok extends ResultImpl { - readonly value: T; - readonly error: undefined; isOk(): this is Ok; isErr(): this is Err; + value(): T; + error(): undefined; unwrap(): T; unwrapErr(): never; expect(message: string): T; @@ -652,10 +652,10 @@ export function Ok(value?: T): Ok { } export interface Err extends ResultImpl { - readonly value: undefined; - readonly error: E; isOk(): this is Ok; isErr(): this is Err; + value(): undefined; + error(): E; unwrap(): never; unwrapErr(): E; expect(message: string): never; diff --git a/test/result.test.ts b/test/result.test.ts index 8dc5627..67b07b0 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -13,14 +13,14 @@ describe.concurrent("core", () => { it("returns an Ok result", () => { const r = Ok(42); - expect(r.isOk).toEqual(true); - expect(r.isErr).toEqual(false); - expect(r.value).toEqual(42); + expect(r.isOk()).toEqual(true); + expect(r.isErr()).toEqual(false); - // expectTypeOf(r.isOk).toEqualTypeOf<() => true>(); - // expectTypeOf(r.isErr).toEqualTypeOf<() => false>(); - expectTypeOf(r.value).toEqualTypeOf(); - expectTypeOf(r.error).toEqualTypeOf(); + expect(r.value()).toEqual(42); + expectTypeOf(r.value).toEqualTypeOf<() => number>(); + + expect(r.error()).toEqual(undefined); + expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -32,14 +32,14 @@ describe.concurrent("core", () => { it("returns an Err result", () => { const r = Err("error"); - expect(r.isOk).toEqual(false); - expect(r.isErr).toEqual(true); - expect(r.error).toEqual("error"); + expect(r.isOk()).toEqual(false); + expect(r.isErr()).toEqual(true); + + expect(r.value()).toEqual(undefined); + expectTypeOf(r.value).toEqualTypeOf<() => undefined>(); - // expectTypeOf(r.isOk).toEqualTypeOf<() => false>(); - // expectTypeOf(r.isErr).toEqualTypeOf<() => true>(); - expectTypeOf(r.value).toEqualTypeOf(); - expectTypeOf(r.error).toEqualTypeOf(); + expect(r.error()).toEqual("error"); + expectTypeOf(r.error).toEqualTypeOf<() => string>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); @@ -50,13 +50,11 @@ describe.concurrent("core", () => { it("works as discriminated union", () => { const r = TestOk(42); - expectTypeOf(r.value).toEqualTypeOf(); - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.value()).toEqualTypeOf(); + expectTypeOf(r.error()).toEqualTypeOf(); if (r.isOk()) { - // expectTypeOf(r.isOk).toEqualTypeOf<() => true>(); - // expectTypeOf(r.isErr).toEqualTypeOf<() => false>(); - expectTypeOf(r.value).toEqualTypeOf(); - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.value).toEqualTypeOf<() => number>(); + expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -64,10 +62,8 @@ describe.concurrent("core", () => { expectTypeOf(r.expect).toEqualTypeOf<(msg: string) => number>(); expectTypeOf(r.expectErr).toEqualTypeOf<(msg: string) => never>(); } else { - // expectTypeOf(r.isOk).toEqualTypeOf<() => false>(); - // expectTypeOf(r.isErr).toEqualTypeOf<() => true>(); - expectTypeOf(r.value).toEqualTypeOf(); - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.value).toEqualTypeOf<() => undefined>(); + expectTypeOf(r.error).toEqualTypeOf<() => string>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); From 3231058fea7939f38548934d5b86a66bea1f3690 Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 17 Apr 2024 22:42:24 +0200 Subject: [PATCH 04/23] Update tests --- test/result.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/result.test.ts b/test/result.test.ts index 67b07b0..38afc06 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -52,9 +52,10 @@ describe.concurrent("core", () => { const r = TestOk(42); expectTypeOf(r.value()).toEqualTypeOf(); expectTypeOf(r.error()).toEqualTypeOf(); + if (r.isOk()) { - expectTypeOf(r.value).toEqualTypeOf<() => number>(); - expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); + expectTypeOf(r.value()).toEqualTypeOf(); + expectTypeOf(r.error()).toEqualTypeOf(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -62,8 +63,8 @@ describe.concurrent("core", () => { expectTypeOf(r.expect).toEqualTypeOf<(msg: string) => number>(); expectTypeOf(r.expectErr).toEqualTypeOf<(msg: string) => never>(); } else { - expectTypeOf(r.value).toEqualTypeOf<() => undefined>(); - expectTypeOf(r.error).toEqualTypeOf<() => string>(); + expectTypeOf(r.value()).toEqualTypeOf(); + expectTypeOf(r.error()).toEqualTypeOf(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); From bb35d2d54ae28f131d72b53dd21ad12e8a8613ba Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 17 Apr 2024 22:56:41 +0200 Subject: [PATCH 05/23] Fix types --- scripts/benchmark.ts | 4 ++-- src/result.ts | 8 ++++++-- src/run.ts | 4 ++-- test/fn.test.ts | 10 +++++----- test/guard.test.ts | 4 ---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/benchmark.ts b/scripts/benchmark.ts index 40f553b..7c1c8eb 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark.ts @@ -9,14 +9,14 @@ function formatTime(ms: number) { const a = asyncFn(async () => { const one = await getOne(); - if (one.isErr) { + if (one.isErr()) { return one; } const rand = Math.random(); if (Math.random() < 0.5) { return Err("error"); } - return Ok(rand + one.value); + return Ok(rand + one.value()); }); const b = asyncGenFn(async function* () { diff --git a/src/result.ts b/src/result.ts index 67719d7..2358655 100644 --- a/src/result.ts +++ b/src/result.ts @@ -15,8 +15,8 @@ export type ResultMatchAsync = { }; export class ResultImpl { - private readonly [variant]: boolean; - private readonly [value]: T | E; + readonly [variant]: boolean; + readonly [value]: T | E; constructor(v: boolean, x: T | E) { this[variant] = v; @@ -632,6 +632,8 @@ export class ResultImpl { } export interface Ok extends ResultImpl { + [variant]: true; + [value]: T; isOk(): this is Ok; isErr(): this is Err; value(): T; @@ -652,6 +654,8 @@ export function Ok(value?: T): Ok { } export interface Err extends ResultImpl { + [variant]: false; + [value]: E; isOk(): this is Ok; isErr(): this is Err; value(): undefined; diff --git a/src/run.ts b/src/run.ts index 5e6e088..8462850 100644 --- a/src/run.ts +++ b/src/run.ts @@ -11,7 +11,7 @@ function _run, U>( while (!done) { const iter = gen.next(returnResult.unwrap()); if (iter.value instanceof ResultImpl) { - if (iter.value.isErr) { + if (iter.value.isErr()) { done = true; gen.return?.(iter.value as any); } @@ -68,7 +68,7 @@ function _runAsync | Result, U>( if (iter.done) { return result; } - if (result.isErr) { + if (result.isErr()) { gen.return?.(iter.value as any); return result; } diff --git a/test/fn.test.ts b/test/fn.test.ts index b393257..09b05d2 100644 --- a/test/fn.test.ts +++ b/test/fn.test.ts @@ -82,7 +82,7 @@ describe.concurrent("fn", () => { }; const wrapped = fn(() => { const r = foo(); - if (r.isErr) { + if (r.isErr()) { return r; } return Ok("foo"); @@ -103,10 +103,10 @@ describe.concurrent("fn", () => { const wrapped = fn(() => { const r = foo(); - if (r.isErr) { + if (r.isErr()) { return r; } - return Ok(r.value); + return Ok(r.value()); }); expectTypeOf(wrapped).returns.toEqualTypeOf>(); }); @@ -129,7 +129,7 @@ describe.concurrent("fn", () => { bar = fn(() => { const ye = foo(); - if (ye.isOk) { + if (ye.isOk()) { return Ok(ye); } return Err(true); @@ -225,7 +225,7 @@ describe.concurrent("asyncFn", () => { }); const wrapped = asyncFn(async () => { const r = await foo(); - if (r.isErr) { + if (r.isErr()) { return r; } return Ok(true); diff --git a/test/guard.test.ts b/test/guard.test.ts index 65ab5c9..6392cd3 100644 --- a/test/guard.test.ts +++ b/test/guard.test.ts @@ -6,7 +6,6 @@ describe.concurrent("guard", () => { const fn = (x: number, y: number) => x + y; const wrappedFn = guard(fn); const result = wrappedFn(40, 2); - expect(result.isOk).toEqual(true); expect(result.unwrap()).toEqual(42); }); @@ -17,7 +16,6 @@ describe.concurrent("guard", () => { }; const wrappedFn = guard(fn); const result = wrappedFn(); - expect(result.isOk).toEqual(false); expect(result.unwrapErr()).toEqual(error); }); @@ -41,7 +39,6 @@ describe.concurrent("guardAsync", () => { const fn = async (x: number, y: number) => Promise.resolve(x + y); const wrappedFn = guardAsync(fn); const result = await wrappedFn(40, 2); - expect(result.isOk).toEqual(true); expect(result.unwrap()).toEqual(42); }); @@ -52,7 +49,6 @@ describe.concurrent("guardAsync", () => { }; const wrappedFn = guardAsync(fn); const result = await wrappedFn(); - expect(result.isOk).toEqual(false); expect(result.unwrapErr()).toEqual(error); }); From 907918e611b63de54d32437b54ebb4781fab841b Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 19 Apr 2024 22:14:37 +0200 Subject: [PATCH 06/23] Rename variant --- src/common.ts | 2 +- src/option.ts | 10 +++++----- src/result.ts | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/common.ts b/src/common.ts index 32caa2c..c8cffce 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,2 +1,2 @@ -export const variant = Symbol("variant"); +export const kind = Symbol("kind"); export const value = Symbol("value"); diff --git a/src/option.ts b/src/option.ts index 3f2cee6..e9defe2 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,6 +1,6 @@ import {AsyncOption} from "./async_option"; import {AsyncResult} from "./async_result"; -import {variant, value} from "./common"; +import {kind, value} from "./common"; import {Panic} from "./error"; import {Err, Ok, type Result} from "./result"; import {inspectSymbol} from "./util_internal"; @@ -16,11 +16,11 @@ export type OptionMatchAsync = { }; export class OptionImpl { - private readonly [variant]: boolean; + private readonly [kind]: boolean; private readonly [value]: T | undefined; constructor(some: boolean, x: T) { - this[variant] = some; + this[kind] = some; this[value] = x; } @@ -29,11 +29,11 @@ export class OptionImpl { } get isSome(): boolean { - return this[variant]; + return this[kind]; } get isNone(): boolean { - return !this[variant]; + return !this[kind]; } get value(): T | undefined { diff --git a/src/result.ts b/src/result.ts index 2358655..cbbc851 100644 --- a/src/result.ts +++ b/src/result.ts @@ -2,7 +2,7 @@ import {Panic} from "./error"; import {inspectSymbol} from "./util_internal"; import {Option, Some, None} from "./option"; import {AsyncResult} from "./async_result"; -import {variant, value} from "./common"; +import {kind, value} from "./common"; export type ResultMatch = { Ok: (value: T) => A; @@ -15,11 +15,11 @@ export type ResultMatchAsync = { }; export class ResultImpl { - readonly [variant]: boolean; + readonly [kind]: boolean; readonly [value]: T | E; - constructor(v: boolean, x: T | E) { - this[variant] = v; + constructor(k: boolean, x: T | E) { + this[kind] = k; this[value] = x; } @@ -52,11 +52,11 @@ export class ResultImpl { * ``` */ match(pattern: ResultMatch): A | B { - return this[variant] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); + return this[kind] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); } matchAsync(pattern: ResultMatchAsync): Promise { - return this[variant] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); + return this[kind] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); } value(): T | undefined { @@ -68,11 +68,11 @@ export class ResultImpl { } isOk(): this is Ok { - return this[variant]; + return this[kind]; } isErr(): this is Err { - return !this[variant]; + return !this[kind]; } /** @@ -632,7 +632,7 @@ export class ResultImpl { } export interface Ok extends ResultImpl { - [variant]: true; + [kind]: true; [value]: T; isOk(): this is Ok; isErr(): this is Err; @@ -654,7 +654,7 @@ export function Ok(value?: T): Ok { } export interface Err extends ResultImpl { - [variant]: false; + [kind]: false; [value]: E; isOk(): this is Ok; isErr(): this is Err; From 6e4b48bde6449cbdc5225fa3a6c66f2ae1373c4f Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 22 Apr 2024 14:02:05 +0200 Subject: [PATCH 07/23] Add discriminated value and error --- src/option.ts | 2 +- src/result.ts | 54 ++++++++++++++++------------------- src/{common.ts => symbols.ts} | 0 test/result.test.ts | 33 +++++++++++---------- 4 files changed, 44 insertions(+), 45 deletions(-) rename src/{common.ts => symbols.ts} (100%) diff --git a/src/option.ts b/src/option.ts index e9defe2..a8b6550 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,6 +1,6 @@ import {AsyncOption} from "./async_option"; import {AsyncResult} from "./async_result"; -import {kind, value} from "./common"; +import {kind, value} from "./symbols"; import {Panic} from "./error"; import {Err, Ok, type Result} from "./result"; import {inspectSymbol} from "./util_internal"; diff --git a/src/result.ts b/src/result.ts index cbbc851..4383b2b 100644 --- a/src/result.ts +++ b/src/result.ts @@ -2,7 +2,7 @@ import {Panic} from "./error"; import {inspectSymbol} from "./util_internal"; import {Option, Some, None} from "./option"; import {AsyncResult} from "./async_result"; -import {kind, value} from "./common"; +import * as symbols from "./symbols"; export type ResultMatch = { Ok: (value: T) => A; @@ -15,16 +15,16 @@ export type ResultMatchAsync = { }; export class ResultImpl { - readonly [kind]: boolean; - readonly [value]: T | E; + readonly [symbols.kind]: boolean; + readonly [symbols.value]: T | E; - constructor(k: boolean, x: T | E) { - this[kind] = k; - this[value] = x; + constructor(k: boolean, v: T | E) { + this[symbols.kind] = k; + this[symbols.value] = v; } private unwrapFailed(message: string): never { - throw new Panic(message, {cause: this[value]}); + throw new Panic(message, {cause: this[symbols.value]}); } *[Symbol.iterator](): Iterator, T, any> { @@ -52,27 +52,23 @@ export class ResultImpl { * ``` */ match(pattern: ResultMatch): A | B { - return this[kind] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); + return this[symbols.kind] + ? pattern.Ok(this[symbols.value] as T) + : pattern.Err(this[symbols.value] as E); } matchAsync(pattern: ResultMatchAsync): Promise { - return this[kind] ? pattern.Ok(this[value] as T) : pattern.Err(this[value] as E); - } - - value(): T | undefined { - return this.ok().unwrapOr(undefined); - } - - error(): E | undefined { - return this.err().unwrapOr(undefined); + return this[symbols.kind] + ? pattern.Ok(this[symbols.value] as T) + : pattern.Err(this[symbols.value] as E); } isOk(): this is Ok { - return this[kind]; + return this[symbols.kind]; } isErr(): this is Err { - return !this[kind]; + return !this[symbols.kind]; } /** @@ -632,12 +628,10 @@ export class ResultImpl { } export interface Ok extends ResultImpl { - [kind]: true; - [value]: T; + [symbols.kind]: true; + value: T; isOk(): this is Ok; isErr(): this is Err; - value(): T; - error(): undefined; unwrap(): T; unwrapErr(): never; expect(message: string): T; @@ -650,16 +644,16 @@ export interface Ok extends ResultImpl { export function Ok(): Ok; export function Ok(value: T): Ok; export function Ok(value?: T): Ok { - return new ResultImpl(true, value as T) as Ok; + const ok = new ResultImpl(true, value as T) as Ok; + ok.value = ok[symbols.value]; + return ok; } export interface Err extends ResultImpl { - [kind]: false; - [value]: E; + [symbols.kind]: false; + error: E; isOk(): this is Ok; isErr(): this is Err; - value(): undefined; - error(): E; unwrap(): never; unwrapErr(): E; expect(message: string): never; @@ -672,7 +666,9 @@ export interface Err extends ResultImpl { export function Err(): Err; export function Err(value: E): Err; export function Err(value?: E): Err { - return new ResultImpl(false, value as E) as Err; + const err = new ResultImpl(false, value as E) as Err; + err.error = err[symbols.value]; + return err; } /** diff --git a/src/common.ts b/src/symbols.ts similarity index 100% rename from src/common.ts rename to src/symbols.ts diff --git a/test/result.test.ts b/test/result.test.ts index 38afc06..20708d8 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -16,11 +16,11 @@ describe.concurrent("core", () => { expect(r.isOk()).toEqual(true); expect(r.isErr()).toEqual(false); - expect(r.value()).toEqual(42); - expectTypeOf(r.value).toEqualTypeOf<() => number>(); + expect(r.value).toEqual(42); + expectTypeOf(r.value).toEqualTypeOf(); - expect(r.error()).toEqual(undefined); - expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); + // @ts-expect-error + expectTypeOf(r.error).toEqualTypeOf(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -35,11 +35,11 @@ describe.concurrent("core", () => { expect(r.isOk()).toEqual(false); expect(r.isErr()).toEqual(true); - expect(r.value()).toEqual(undefined); - expectTypeOf(r.value).toEqualTypeOf<() => undefined>(); + // @ts-expect-error + expectTypeOf(r.value).toEqualTypeOf(); - expect(r.error()).toEqual("error"); - expectTypeOf(r.error).toEqualTypeOf<() => string>(); + expect(r.error).toEqual("error"); + expectTypeOf(r.error).toEqualTypeOf(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); @@ -50,12 +50,14 @@ describe.concurrent("core", () => { it("works as discriminated union", () => { const r = TestOk(42); - expectTypeOf(r.value()).toEqualTypeOf(); - expectTypeOf(r.error()).toEqualTypeOf(); - + // @ts-expect-error + expectTypeOf(r.value).toEqualTypeOf(); + // @ts-expect-error + expectTypeOf(r.error).toEqualTypeOf(); if (r.isOk()) { - expectTypeOf(r.value()).toEqualTypeOf(); - expectTypeOf(r.error()).toEqualTypeOf(); + expectTypeOf(r.value).toEqualTypeOf(); + // @ts-expect-error + expectTypeOf(r.error).toEqualTypeOf(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -63,8 +65,9 @@ describe.concurrent("core", () => { expectTypeOf(r.expect).toEqualTypeOf<(msg: string) => number>(); expectTypeOf(r.expectErr).toEqualTypeOf<(msg: string) => never>(); } else { - expectTypeOf(r.value()).toEqualTypeOf(); - expectTypeOf(r.error()).toEqualTypeOf(); + // @ts-expect-error + expectTypeOf(r.value).toEqualTypeOf(); + expectTypeOf(r.error).toEqualTypeOf(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); From 8fd5a53f933908b02a958149f4fdeb9cd0854f73 Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 24 Apr 2024 09:55:18 +0200 Subject: [PATCH 08/23] Add isOkAnd, isErrAnd --- src/result.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/result.ts b/src/result.ts index 4383b2b..34084d7 100644 --- a/src/result.ts +++ b/src/result.ts @@ -63,14 +63,38 @@ export class ResultImpl { : pattern.Err(this[symbols.value] as E); } + /** + * Returns `true` if the result is `Ok`. + */ isOk(): this is Ok { return this[symbols.kind]; } + /** + * Returns `true` if the result is `Ok` and the value satisfies the predicate. + * + * Maybe not as useful as using `result.isOk() && f(result.value)`, because it doesn't narrow the type, but it's here for completeness. + */ + isOkAnd(f: (value: T) => boolean): this is Ok { + return this[symbols.kind] && f(this[symbols.value] as T); + } + + /** + * Returns `true` if the result is `Err`. + */ isErr(): this is Err { return !this[symbols.kind]; } + /** + * Returns `true` if the result is `Err` and the error satisfies the predicate. + * + * Maybe not as useful as using `result.isErr() && f(result.error)`, because it doesn't narrow the type, but it's here for completeness. + */ + isErrAnd(f: (error: E) => boolean): this is Err { + return !this[symbols.kind] && f(this[symbols.value] as E); + } + /** * Converts from `Result` to `Option`. * @@ -630,8 +654,6 @@ export class ResultImpl { export interface Ok extends ResultImpl { [symbols.kind]: true; value: T; - isOk(): this is Ok; - isErr(): this is Err; unwrap(): T; unwrapErr(): never; expect(message: string): T; @@ -652,8 +674,6 @@ export function Ok(value?: T): Ok { export interface Err extends ResultImpl { [symbols.kind]: false; error: E; - isOk(): this is Ok; - isErr(): this is Err; unwrap(): never; unwrapErr(): E; expect(message: string): never; From 27d9d6281b438a3e57959626bd7124a9d16abc64 Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 24 Apr 2024 10:19:27 +0200 Subject: [PATCH 09/23] Add value func to some --- src/option.ts | 66 ++++++++++++++++++++++++++------------------- src/result.ts | 16 +++++------ test/option.test.ts | 38 +++++++++----------------- test/result.test.ts | 12 ++++----- 4 files changed, 64 insertions(+), 68 deletions(-) diff --git a/src/option.ts b/src/option.ts index a8b6550..b667ba7 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,6 +1,6 @@ import {AsyncOption} from "./async_option"; import {AsyncResult} from "./async_result"; -import {kind, value} from "./symbols"; +import * as symbols from "./symbols"; import {Panic} from "./error"; import {Err, Ok, type Result} from "./result"; import {inspectSymbol} from "./util_internal"; @@ -16,28 +16,16 @@ export type OptionMatchAsync = { }; export class OptionImpl { - private readonly [kind]: boolean; - private readonly [value]: T | undefined; + readonly [symbols.kind]: boolean; + private readonly [symbols.value]: T | undefined; constructor(some: boolean, x: T) { - this[kind] = some; - this[value] = x; + this[symbols.kind] = some; + this[symbols.value] = x; } private unwrapFailed(message: string): never { - throw new Panic(message, {cause: this.value}); - } - - get isSome(): boolean { - return this[kind]; - } - - get isNone(): boolean { - return !this[kind]; - } - - get value(): T | undefined { - return this[value]; + throw new Panic(message, {cause: this[symbols.value]}); } /** @@ -53,11 +41,34 @@ export class OptionImpl { * ``` */ match(pattern: OptionMatch): A | B { - return this.isSome ? pattern.Some(this.value as T) : pattern.None(); + return this[symbols.kind] ? pattern.Some(this[symbols.value] as T) : pattern.None(); } matchAsync(pattern: OptionMatchAsync): Promise { - return this.isSome ? pattern.Some(this.value as T) : pattern.None(); + return this[symbols.kind] ? pattern.Some(this[symbols.value] as T) : pattern.None(); + } + + /** + * Returns `true` if the option is a `Some` value. + */ + isSome(): this is Some { + return this[symbols.kind]; + } + + /** + * Returns `true` if the option is a `Some` value and the contained value is equal to `value`. + * + * Maybe not as useful as using `option.isSome() && f(option.value)`, because it doesn't narrow the type, but it's here for completeness. + */ + isSomeAnd(predicate: (value: T) => boolean): this is Some { + return this.isSome() && predicate(this[symbols.value] as T); + } + + /** + * Returns `true` if the option is a `None` value. + */ + isNone(): this is None { + return !this[symbols.kind]; } /** @@ -409,7 +420,7 @@ export class OptionImpl { */ xor(other: Option): Option { return this.match({ - Some: (t) => (other.isNone ? Some(t) : None), + Some: (t) => (other.isNone() ? Some(t) : None), None: () => other, }); } @@ -458,9 +469,8 @@ export class OptionImpl { } export interface Some extends OptionImpl { - readonly isSome: true; - readonly isNone: false; - readonly value: T; + [symbols.kind]: true; + value: () => T; unwrap(): T; expect(message: string): T; } @@ -469,13 +479,13 @@ export interface Some extends OptionImpl { * Some value of type `T`. */ export function Some(value: T): Some { - return new OptionImpl(true, value) as Some; + const some = new OptionImpl(true, value) as Some; + some.value = () => value; + return some; } export interface None extends OptionImpl { - readonly isSome: false; - readonly isNone: true; - readonly value: undefined; + [symbols.kind]: false; unwrap(): never; expect(message: string): never; } diff --git a/src/result.ts b/src/result.ts index 34084d7..177dae7 100644 --- a/src/result.ts +++ b/src/result.ts @@ -16,7 +16,7 @@ export type ResultMatchAsync = { export class ResultImpl { readonly [symbols.kind]: boolean; - readonly [symbols.value]: T | E; + private readonly [symbols.value]: T | E; constructor(k: boolean, v: T | E) { this[symbols.kind] = k; @@ -653,7 +653,7 @@ export class ResultImpl { export interface Ok extends ResultImpl { [symbols.kind]: true; - value: T; + value: () => T; unwrap(): T; unwrapErr(): never; expect(message: string): T; @@ -667,13 +667,13 @@ export function Ok(): Ok; export function Ok(value: T): Ok; export function Ok(value?: T): Ok { const ok = new ResultImpl(true, value as T) as Ok; - ok.value = ok[symbols.value]; + ok.value = () => value as T; return ok; } export interface Err extends ResultImpl { [symbols.kind]: false; - error: E; + error: () => E; unwrap(): never; unwrapErr(): E; expect(message: string): never; @@ -684,10 +684,10 @@ export interface Err extends ResultImpl { * Contains the error value. */ export function Err(): Err; -export function Err(value: E): Err; -export function Err(value?: E): Err { - const err = new ResultImpl(false, value as E) as Err; - err.error = err[symbols.value]; +export function Err(error: E): Err; +export function Err(error?: E): Err { + const err = new ResultImpl(false, error as E) as Err; + err.error = () => error as E; return err; } diff --git a/test/option.test.ts b/test/option.test.ts index b19e8c5..925beb4 100644 --- a/test/option.test.ts +++ b/test/option.test.ts @@ -12,46 +12,32 @@ function TestNone(): Option { describe.concurrent("core", () => { it("returns a Some option", () => { const option = Some(42); - - expect(option.isSome).toEqual(true); - expect(option.isNone).toEqual(false); - expect(option.value).toEqual(42); - - expectTypeOf(option.isSome).toEqualTypeOf(); - expectTypeOf(option.isNone).toEqualTypeOf(); - expectTypeOf(option.value).toEqualTypeOf(); + expect(option.isSome()).toEqual(true); + expect(option.isNone()).toEqual(false); + expect(option.value()).toEqual(42); + expectTypeOf(option.value).toEqualTypeOf<() => number>(); expectTypeOf(option.unwrap).toEqualTypeOf<() => number>(); }); it("returns a None option", () => { const option = None; - - expect(option.isSome).toEqual(false); - expect(option.isNone).toEqual(true); - expect(option.value).toEqual(undefined); - - expectTypeOf(option.isSome).toEqualTypeOf(); - expectTypeOf(option.isNone).toEqualTypeOf(); - expectTypeOf(option.value).toEqualTypeOf(); - + expect(option.isSome()).toEqual(false); + expect(option.isNone()).toEqual(true); + // @ts-expect-error + expectTypeOf(option.value).toEqualTypeOf(); expectTypeOf(option.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(option.expect).toEqualTypeOf<(msg: string) => never>(); }); it("works with discriminated union", () => { const option = TestSome(42); - if (option.isSome) { - expectTypeOf(option.isSome).toEqualTypeOf(); - expectTypeOf(option.isNone).toEqualTypeOf(); - expectTypeOf(option.value).toEqualTypeOf(); - + if (option.isSome()) { + expectTypeOf(option.value).toEqualTypeOf<() => number>(); expectTypeOf(option.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(option.expect).toEqualTypeOf<(msg: string) => number>(); } else { - expectTypeOf(option.isSome).toEqualTypeOf(); - expectTypeOf(option.isNone).toEqualTypeOf(); - expectTypeOf(option.value).toEqualTypeOf(); - + // @ts-expect-error + expectTypeOf(option.value).toEqualTypeOf(); expectTypeOf(option.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(option.expect).toEqualTypeOf<(msg: string) => never>(); } diff --git a/test/result.test.ts b/test/result.test.ts index 20708d8..3a08694 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -16,8 +16,8 @@ describe.concurrent("core", () => { expect(r.isOk()).toEqual(true); expect(r.isErr()).toEqual(false); - expect(r.value).toEqual(42); - expectTypeOf(r.value).toEqualTypeOf(); + expect(r.value()).toEqual(42); + expectTypeOf(r.value).toEqualTypeOf<() => number>(); // @ts-expect-error expectTypeOf(r.error).toEqualTypeOf(); @@ -38,8 +38,8 @@ describe.concurrent("core", () => { // @ts-expect-error expectTypeOf(r.value).toEqualTypeOf(); - expect(r.error).toEqual("error"); - expectTypeOf(r.error).toEqualTypeOf(); + expect(r.error()).toEqual("error"); + expectTypeOf(r.error).toEqualTypeOf<() => string>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); @@ -55,7 +55,7 @@ describe.concurrent("core", () => { // @ts-expect-error expectTypeOf(r.error).toEqualTypeOf(); if (r.isOk()) { - expectTypeOf(r.value).toEqualTypeOf(); + expectTypeOf(r.value).toEqualTypeOf<() => number>(); // @ts-expect-error expectTypeOf(r.error).toEqualTypeOf(); @@ -67,7 +67,7 @@ describe.concurrent("core", () => { } else { // @ts-expect-error expectTypeOf(r.value).toEqualTypeOf(); - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.error).toEqualTypeOf<() => string>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => string>(); From 4ce242e579bfd7b3985551c14421fa811a53d218 Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 24 Apr 2024 10:19:59 +0200 Subject: [PATCH 10/23] Remove guard --- src/guard.ts | 31 --------------------- test/guard.test.ts | 68 ---------------------------------------------- 2 files changed, 99 deletions(-) delete mode 100644 src/guard.ts delete mode 100644 test/guard.test.ts diff --git a/src/guard.ts b/src/guard.ts deleted file mode 100644 index 3d98285..0000000 --- a/src/guard.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {tryAsyncFn, tryFn, tryPromise} from "./try"; - -export function guard(fn: (...args: A) => T) { - return function (...args: A) { - return tryFn(() => fn(...args)); - }; -} - -export function guardWith( - fn: (...args: A) => T, - mapErr: (error: Error) => E, -) { - return function (...args: A) { - return tryFn(() => fn(...args)).mapErr(mapErr); - }; -} - -export function guardAsync(fn: (...args: A) => Promise) { - return function (...args: A) { - return tryAsyncFn(() => fn(...args)); - }; -} - -export function guardAsyncWith( - fn: (...args: A) => Promise, - mapErr: (error: Error) => E, -) { - return function (...args: A) { - return tryPromise(fn(...args)).mapErr(mapErr); - }; -} diff --git a/test/guard.test.ts b/test/guard.test.ts deleted file mode 100644 index 6392cd3..0000000 --- a/test/guard.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {expect, it, describe, expectTypeOf} from "vitest"; -import {guard, guardAsync, Result, AsyncResult} from "../src"; - -describe.concurrent("guard", () => { - it("transforms a function into a function that returns a Result object", () => { - const fn = (x: number, y: number) => x + y; - const wrappedFn = guard(fn); - const result = wrappedFn(40, 2); - expect(result.unwrap()).toEqual(42); - }); - - it("transforms a throwing function into a function that returns an Err result", () => { - const error = new Error("Test error"); - const fn = () => { - throw error; - }; - const wrappedFn = guard(fn); - const result = wrappedFn(); - expect(result.unwrapErr()).toEqual(error); - }); - - describe("types", () => { - it("works with a function", () => { - const f = (value: number) => value; - const guarded = guard(f); - expectTypeOf(guarded).toEqualTypeOf<(value: number) => Result>(); - }); - - it("works with a generic function", () => { - const f = (a: A, _b: B) => a; - const guarded = guard(f); - expectTypeOf(guarded).toEqualTypeOf<(a: A, b: B) => Result>(); - }); - }); -}); - -describe.concurrent("guardAsync", () => { - it("transforms an async function into a function that returns a Promise of a Result object", async () => { - const fn = async (x: number, y: number) => Promise.resolve(x + y); - const wrappedFn = guardAsync(fn); - const result = await wrappedFn(40, 2); - expect(result.unwrap()).toEqual(42); - }); - - it("transforms a throwing async function into a function that returns a Promise of an Err result", async () => { - const error = new Error("Test error"); - const fn = async (): Promise => { - throw error; - }; - const wrappedFn = guardAsync(fn); - const result = await wrappedFn(); - expect(result.unwrapErr()).toEqual(error); - }); - - describe("types", () => { - it("works with a function", () => { - const f = async (value: number) => value; - const guarded = guardAsync(f); - expectTypeOf(guarded).toEqualTypeOf<(value: number) => AsyncResult>(); - }); - - it("works with a generic function", () => { - const f = async (a: A, _b: B) => a; - const guarded = guardAsync(f); - expectTypeOf(guarded).toEqualTypeOf<(a: A, b: B) => AsyncResult>(); - }); - }); -}); From 7b4b8b592e8f68eb6c56ce7a59e6c21b42e1b336 Mon Sep 17 00:00:00 2001 From: bkiac Date: Wed, 24 Apr 2024 10:35:29 +0200 Subject: [PATCH 11/23] Consolidate try and run logic in result --- src/option.ts | 6 +- src/result.ts | 186 +++++++++++++++++++++++++++++++++++++++++++++++++- src/run.ts | 108 ----------------------------- src/try.ts | 78 --------------------- 4 files changed, 188 insertions(+), 190 deletions(-) delete mode 100644 src/run.ts delete mode 100644 src/try.ts diff --git a/src/option.ts b/src/option.ts index b667ba7..e821961 100644 --- a/src/option.ts +++ b/src/option.ts @@ -500,6 +500,8 @@ export const None = new OptionImpl(false, undefined) as None; */ export type Option = Some | None; -export function Option(value: T | undefined | null): Option { +export function Option() {} + +Option.from = (value: T | undefined | null): Option => { return value == null ? None : Some(value); -} +}; diff --git a/src/result.ts b/src/result.ts index 177dae7..3be4c98 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,8 +1,9 @@ -import {Panic} from "./error"; +import {Panic, parseError} from "./error"; import {inspectSymbol} from "./util_internal"; -import {Option, Some, None} from "./option"; +import {type Option, Some, None} from "./option"; import {AsyncResult} from "./async_result"; import * as symbols from "./symbols"; +import type {InferErr} from "./util"; export type ResultMatch = { Ok: (value: T) => A; @@ -691,6 +692,17 @@ export function Err(error?: E): Err { return err; } +function handlePanic(error: unknown) { + if (error instanceof Panic) { + throw error; + } + return error; +} + +function handleCaughtError(error: unknown) { + return parseError(handlePanic(error)); +} + /** * `Result` is a type that represents either success (`Ok`) or failure (`Err`). * @@ -699,3 +711,173 @@ export function Err(error?: E): Err { * Functions return `Result` whenever errors are expected and recoverable. */ export type Result = Ok | Err; + +export function Result() {} + +/** + * Tries to execute a function and returns the result as a `Result`. + * + * **Examples** + * + * ``` + * // const result: Result + * const result = Result.from(() => { + * if (Math.random() > 0.5) { + * return 42 + * } else { + * throw new Error("random error") + * } + * }) + * ``` + */ +Result.from = (f: () => T): Result => { + try { + return Ok(f()); + } catch (error) { + return Err(handleCaughtError(error)); + } +}; + +/** + * Tries to resolve a promise and returns the result as a `AsyncResult`. + * + * **Examples** + * + * ``` + * // const result: AsyncResult + * const result = Result.fromPromise(Promise.resolve(42)) + * ``` + */ +Result.fromPromise = (promise: Promise): AsyncResult => { + return new AsyncResult( + promise.then( + (value) => Ok(value), + (error) => Err(handleCaughtError(error)), + ), + ); +}; + +/** + * Tries to execute an async function and returns the result as a `AsyncResult`. + * + * **Examples** + * + * ``` + * // const result: AsyncResult + * const result = Result.fromAsync(() => { + * if (Math.random() > 0.5) { + * return Promise.resolve(42) + * } else { + * throw new Error("random error") + * } + * }) + * ``` + */ +Result.fromAsync = (f: () => Promise): AsyncResult => { + return Result.fromPromise(f()); +}; + +function _try, U>( + fn: () => Generator, +): Result> { + const gen = fn(); + let done = false; + let returnResult = Ok(); + while (!done) { + const iter = gen.next(returnResult.unwrap()); + if (iter.value instanceof ResultImpl) { + if (iter.value.isErr()) { + done = true; + gen.return?.(iter.value as any); + } + returnResult = iter.value as any; + } else { + done = true; + returnResult = Ok(iter.value) as any; + } + } + return returnResult as any; +} + +/** + * Runs a generator function that returns a `Result` and infers its return type as `Result`. + * + * `yield*` must be used to yield the result of a `Result`. + * + * **Examples** + * + * ```ts + * // $ExpectType Result + * const result = run(function* () { + * const a = yield* Ok(1) + * const random = Math.random() + * if (random > 0.5) { + * yield* Err("error") + * } + * return a + random + * }) + * ``` + */ +Result.try = , U>(fn: () => Generator) => { + // Variable assignment helps with type inference + const result = _try(fn); + return result; +}; + +async function toPromiseResult(value: any): Promise> { + const awaited = await value; + if (value instanceof ResultImpl) { + return awaited as any; + } + return Ok(awaited); +} + +function _tryAsync | Result, U>( + fn: () => AsyncGenerator, +): AsyncResult>> { + const gen = fn(); + const yieldedResultChain = Promise.resolve>(Ok()).then( + async function fulfill(nextResult): Promise> { + const iter = await gen.next(nextResult.unwrap()); + const result = await toPromiseResult(iter.value); + if (iter.done) { + return result; + } + if (result.isErr()) { + gen.return?.(iter.value as any); + return result; + } + return Promise.resolve(result).then(fulfill); + }, + ); + return new AsyncResult(yieldedResultChain); +} + +/** + * Runs an async generator function that returns a `Result` and infers its return type as `AsyncResult`. + * + * `yield*` must be used to yield the result of a `AsyncResult` or `Result`. + * + * **Examples** + * + * ```ts + * const okOne = () => new AsyncResult(Promise.resolve(Ok(1))) + * + * // $ExpectType AsyncResult + * const result = runAsync(async function* () { + * const a = yield* okOne() + * const random = Math.random() + * if (random > 0.5) { + * yield* Err("error") + * } + * return a + random + * }) + * ``` + */ +Result.tryAsync = | Result, U>( + fn: () => AsyncGenerator, +) => { + // Variable assignment helps with type inference + const result = _tryAsync(fn); + return result; +}; diff --git a/src/run.ts b/src/run.ts deleted file mode 100644 index 8462850..0000000 --- a/src/run.ts +++ /dev/null @@ -1,108 +0,0 @@ -import {AsyncResult} from "./async_result"; -import {type Result, Ok, ResultImpl} from "./result"; -import type {InferErr} from "./util"; - -function _run, U>( - fn: () => Generator, -): Result> { - const gen = fn(); - let done = false; - let returnResult = Ok(); - while (!done) { - const iter = gen.next(returnResult.unwrap()); - if (iter.value instanceof ResultImpl) { - if (iter.value.isErr()) { - done = true; - gen.return?.(iter.value as any); - } - returnResult = iter.value as any; - } else { - done = true; - returnResult = Ok(iter.value) as any; - } - } - return returnResult as any; -} - -/** - * Runs a generator function that returns a `Result` and infers its return type as `Result`. - * - * `yield*` must be used to yield the result of a `Result`. - * - * **Examples** - * - * ```ts - * // $ExpectType Result - * const result = run(function* () { - * const a = yield* Ok(1) - * const random = Math.random() - * if (random > 0.5) { - * yield* Err("error") - * } - * return a + random - * }) - * ``` - */ -export function run, U>(fn: () => Generator) { - // Variable assignment helps with type inference - const result = _run(fn); - return result; -} - -async function toPromiseResult(value: any): Promise> { - const awaited = await value; - if (value instanceof ResultImpl) { - return awaited as any; - } - return Ok(awaited); -} - -function _runAsync | Result, U>( - fn: () => AsyncGenerator, -): AsyncResult>> { - const gen = fn(); - const yieldedResultChain = Promise.resolve>(Ok()).then( - async function fulfill(nextResult): Promise> { - const iter = await gen.next(nextResult.unwrap()); - const result = await toPromiseResult(iter.value); - if (iter.done) { - return result; - } - if (result.isErr()) { - gen.return?.(iter.value as any); - return result; - } - return Promise.resolve(result).then(fulfill); - }, - ); - return new AsyncResult(yieldedResultChain); -} - -/** - * Runs an async generator function that returns a `Result` and infers its return type as `AsyncResult`. - * - * `yield*` must be used to yield the result of a `AsyncResult` or `Result`. - * - * **Examples** - * - * ```ts - * const okOne = () => new AsyncResult(Promise.resolve(Ok(1))) - * - * // $ExpectType AsyncResult - * const result = runAsync(async function* () { - * const a = yield* okOne() - * const random = Math.random() - * if (random > 0.5) { - * yield* Err("error") - * } - * return a + random - * }) - * ``` - */ -export function runAsync | Result, U>( - fn: () => AsyncGenerator, -) { - // Variable assignment helps with type inference - const result = _runAsync(fn); - return result; -} diff --git a/src/try.ts b/src/try.ts deleted file mode 100644 index 835cc4f..0000000 --- a/src/try.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Panic} from "./error"; -import {parseError} from "./error"; -import {Err, Ok, type Result} from "./result"; -import {AsyncResult} from "./async_result"; - -function handlePanic(error: unknown) { - if (error instanceof Panic) { - throw error; - } - return error; -} - -function handleCaughtError(error: unknown) { - return parseError(handlePanic(error)); -} - -/** - * Tries to execute a function and returns the result as a `Result`. - * - * **Examples** - * - * ``` - * // const result: Result - * const result = tryFn(() => { - * if (Math.random() > 0.5) { - * return 42 - * } else { - * throw new Error("random error") - * } - * }) - * ``` - */ -export function tryFn(fn: () => T): Result { - try { - return Ok(fn()); - } catch (error) { - return Err(handleCaughtError(error)); - } -} - -/** - * Tries to resolve a promise and returns the result as a `AsyncResult`. - * - * **Examples** - * - * ``` - * // const result: AsyncResult - * const result = tryPromise(Promise.resolve(42)) - * ``` - */ -export function tryPromise(promise: Promise): AsyncResult { - return new AsyncResult( - promise.then( - (value) => Ok(value), - (error: unknown) => Err(handleCaughtError(error)), - ), - ); -} - -/** - * Tries to execute an async function and returns the result as a `AsyncResult`. - * - * **Examples** - * - * ``` - * // const result: AsyncResult - * const result = tryAsyncFn(() => { - * if (Math.random() > 0.5) { - * return Promise.resolve(42) - * } else { - * throw new Error("random error") - * } - * }) - * ``` - */ -export function tryAsyncFn(fn: () => Promise): AsyncResult { - return tryPromise(fn()); -} From 58031d053fa472c3f6ad489d7e398dc8c778c070 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 18:40:12 +0200 Subject: [PATCH 12/23] Add value, error getters --- src/option.ts | 22 +++++++++++++++------- src/result.ts | 34 ++++++++++++++++++++++++++-------- test/option.test.ts | 21 ++++++++++----------- test/result.test.ts | 20 ++++++-------------- 4 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/option.ts b/src/option.ts index e821961..0b2dd98 100644 --- a/src/option.ts +++ b/src/option.ts @@ -17,11 +17,13 @@ export type OptionMatchAsync = { export class OptionImpl { readonly [symbols.kind]: boolean; - private readonly [symbols.value]: T | undefined; + private readonly [symbols.value]?: T; - constructor(some: boolean, x: T) { + constructor(some: boolean, x?: T) { this[symbols.kind] = some; - this[symbols.value] = x; + if (x !== undefined) { + this[symbols.value] = x; + } } private unwrapFailed(message: string): never { @@ -48,6 +50,13 @@ export class OptionImpl { return this[symbols.kind] ? pattern.Some(this[symbols.value] as T) : pattern.None(); } + /** + * Returns the contained `Some` value, if exists. + */ + value(): T | undefined { + return this[symbols.value]; + } + /** * Returns `true` if the option is a `Some` value. */ @@ -470,7 +479,7 @@ export class OptionImpl { export interface Some extends OptionImpl { [symbols.kind]: true; - value: () => T; + value(): T; unwrap(): T; expect(message: string): T; } @@ -479,13 +488,12 @@ export interface Some extends OptionImpl { * Some value of type `T`. */ export function Some(value: T): Some { - const some = new OptionImpl(true, value) as Some; - some.value = () => value; - return some; + return new OptionImpl(true, value) as Some; } export interface None extends OptionImpl { [symbols.kind]: false; + value(): undefined; unwrap(): never; expect(message: string): never; } diff --git a/src/result.ts b/src/result.ts index 3be4c98..50c8b61 100644 --- a/src/result.ts +++ b/src/result.ts @@ -64,6 +64,26 @@ export class ResultImpl { : pattern.Err(this[symbols.value] as E); } + /** + * Returns the contained value, if it exists. + */ + value(): T | undefined { + return this.match({ + Ok: (t) => t, + Err: () => undefined, + }); + } + + /** + * Returns the contained error, if it exists. + */ + error(): E | undefined { + return this.match({ + Ok: () => undefined, + Err: (e) => e, + }); + } + /** * Returns `true` if the result is `Ok`. */ @@ -654,7 +674,8 @@ export class ResultImpl { export interface Ok extends ResultImpl { [symbols.kind]: true; - value: () => T; + value(): T; + error(): undefined; unwrap(): T; unwrapErr(): never; expect(message: string): T; @@ -667,14 +688,13 @@ export interface Ok extends ResultImpl { export function Ok(): Ok; export function Ok(value: T): Ok; export function Ok(value?: T): Ok { - const ok = new ResultImpl(true, value as T) as Ok; - ok.value = () => value as T; - return ok; + return new ResultImpl(true, value as T) as Ok; } export interface Err extends ResultImpl { [symbols.kind]: false; - error: () => E; + value(): undefined; + error(): E; unwrap(): never; unwrapErr(): E; expect(message: string): never; @@ -687,9 +707,7 @@ export interface Err extends ResultImpl { export function Err(): Err; export function Err(error: E): Err; export function Err(error?: E): Err { - const err = new ResultImpl(false, error as E) as Err; - err.error = () => error as E; - return err; + return new ResultImpl(false, error as E) as Err; } function handlePanic(error: unknown) { diff --git a/test/option.test.ts b/test/option.test.ts index 925beb4..ced424a 100644 --- a/test/option.test.ts +++ b/test/option.test.ts @@ -23,21 +23,20 @@ describe.concurrent("core", () => { const option = None; expect(option.isSome()).toEqual(false); expect(option.isNone()).toEqual(true); - // @ts-expect-error - expectTypeOf(option.value).toEqualTypeOf(); + expectTypeOf(option.value).toEqualTypeOf<() => undefined>(); expectTypeOf(option.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(option.expect).toEqualTypeOf<(msg: string) => never>(); }); it("works with discriminated union", () => { const option = TestSome(42); + expectTypeOf(option.value).toEqualTypeOf<() => number | undefined>(); if (option.isSome()) { expectTypeOf(option.value).toEqualTypeOf<() => number>(); expectTypeOf(option.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(option.expect).toEqualTypeOf<(msg: string) => number>(); } else { - // @ts-expect-error - expectTypeOf(option.value).toEqualTypeOf(); + expectTypeOf(option.value).toEqualTypeOf<() => undefined>(); expectTypeOf(option.unwrap).toEqualTypeOf<() => never>(); expectTypeOf(option.expect).toEqualTypeOf<(msg: string) => never>(); } @@ -333,22 +332,22 @@ describe.concurrent("match", () => { }); }); -describe.concurrent("Option", () => { +describe.concurrent("Option.from", () => { it("returns Some when the value is not null or undefined", () => { const value = "hello" as string | number | null; - const option = Option(value); + const option = Option.from(value); expectTypeOf(option).toEqualTypeOf>(); expect(option).toEqual(Some(value)); }); it("returns Some when the value is falsy", () => { - expect(Option(false)).toEqual(Some(false)); - expect(Option(0)).toEqual(Some(0)); - expect(Option("")).toEqual(Some("")); + expect(Option.from(false)).toEqual(Some(false)); + expect(Option.from(0)).toEqual(Some(0)); + expect(Option.from("")).toEqual(Some("")); }); it("returns None when the value is null or undefined", () => { - expect(Option(null)).toEqual(None); - expect(Option(undefined)).toEqual(None); + expect(Option.from(null)).toEqual(None); + expect(Option.from(undefined)).toEqual(None); }); }); diff --git a/test/result.test.ts b/test/result.test.ts index 3a08694..5aa5d16 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -18,9 +18,7 @@ describe.concurrent("core", () => { expect(r.value()).toEqual(42); expectTypeOf(r.value).toEqualTypeOf<() => number>(); - - // @ts-expect-error - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -35,9 +33,7 @@ describe.concurrent("core", () => { expect(r.isOk()).toEqual(false); expect(r.isErr()).toEqual(true); - // @ts-expect-error - expectTypeOf(r.value).toEqualTypeOf(); - + expectTypeOf(r.value).toEqualTypeOf<() => undefined>(); expect(r.error()).toEqual("error"); expectTypeOf(r.error).toEqualTypeOf<() => string>(); @@ -50,14 +46,11 @@ describe.concurrent("core", () => { it("works as discriminated union", () => { const r = TestOk(42); - // @ts-expect-error - expectTypeOf(r.value).toEqualTypeOf(); - // @ts-expect-error - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.value).toEqualTypeOf<() => number | undefined>(); + expectTypeOf(r.error).toEqualTypeOf<() => string | undefined>(); if (r.isOk()) { expectTypeOf(r.value).toEqualTypeOf<() => number>(); - // @ts-expect-error - expectTypeOf(r.error).toEqualTypeOf(); + expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => number>(); expectTypeOf(r.unwrapErr).toEqualTypeOf<() => never>(); @@ -65,8 +58,7 @@ describe.concurrent("core", () => { expectTypeOf(r.expect).toEqualTypeOf<(msg: string) => number>(); expectTypeOf(r.expectErr).toEqualTypeOf<(msg: string) => never>(); } else { - // @ts-expect-error - expectTypeOf(r.value).toEqualTypeOf(); + expectTypeOf(r.value).toEqualTypeOf<() => undefined>(); expectTypeOf(r.error).toEqualTypeOf<() => string>(); expectTypeOf(r.unwrap).toEqualTypeOf<() => never>(); From a0cd32d336eed517740b953af706e52eca2f2777 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 18:51:11 +0200 Subject: [PATCH 13/23] Fix missing matchers and getters --- src/async_option.ts | 27 +++++++++++++++++++-------- src/async_result.ts | 28 ++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/async_option.ts b/src/async_option.ts index 2622380..3949f23 100644 --- a/src/async_option.ts +++ b/src/async_option.ts @@ -1,5 +1,5 @@ import {AsyncResult} from "."; -import type {Option, OptionMatch} from "./option"; +import type {Option, OptionMatch, OptionMatchAsync} from "./option"; /** * A promise that resolves to an `Option`. @@ -33,6 +33,24 @@ export class AsyncOption implements PromiseLike> { ); } + /** + * Async version of `Option#match`. + */ + async match(matcher: OptionMatch): Promise { + return (await this).match(matcher); + } + + async matchAsync(matcher: OptionMatchAsync): Promise { + return (await this).matchAsync(matcher); + } + + /** + * Async version of `Option#value`. + */ + async value(): Promise { + return (await this).value(); + } + /** * Async version of `Option#okOr`. */ @@ -157,11 +175,4 @@ export class AsyncOption implements PromiseLike> { this.then((thisOption) => other.then((otherOption) => thisOption.xor(otherOption))), ); } - - /** - * Async version of `Option#match`. - */ - async match(matcher: OptionMatch): Promise { - return (await this).match(matcher); - } } diff --git a/src/async_result.ts b/src/async_result.ts index 25334cf..3ddbfa8 100644 --- a/src/async_result.ts +++ b/src/async_result.ts @@ -1,5 +1,5 @@ import {AsyncOption} from "./async_option"; -import type {Result, ResultImpl, ResultMatch} from "./result"; +import type {Result, ResultImpl, ResultMatch, ResultMatchAsync} from "./result"; /** * A promise that resolves to a `Result`. @@ -39,6 +39,25 @@ export class AsyncResult implements PromiseLike> { ); } + /** + * Async version of `Result#match`. + */ + async match(matcher: ResultMatch): Promise { + return (await this).match(matcher); + } + + async matchAsync(matcher: ResultMatchAsync): Promise { + return (await this).matchAsync(matcher); + } + + async value(): Promise { + return (await this).value(); + } + + async error(): Promise { + return (await this).error(); + } + /** * Async version of `Result#ok`. */ @@ -219,11 +238,4 @@ export class AsyncResult implements PromiseLike> { async unwrapOrElse(defaultValue: (error: E) => U): Promise { return (await this).unwrapOrElse(defaultValue); } - - /** - * Async version of `Result#match`. - */ - async match(matcher: ResultMatch): Promise { - return (await this).match(matcher); - } } From 636485d6a778dc7342a1b00dee88d6a2dcf11a03 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 18:51:49 +0200 Subject: [PATCH 14/23] Remove unused export --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 3ec7cf7..c70a5fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * from "./fn"; -export * from "./guard"; export * from "./async_option"; export * from "./option"; export * from "./error"; From 6ab651b9a42c59032c6bb6c0abfee323cdd03676 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 19:02:36 +0200 Subject: [PATCH 15/23] Update try, from, run tests and structure --- src/index.ts | 1 - src/result.ts | 126 ------------------------------------- src/try.ts | 108 ++++++++++++++++++++++++++++++++ test/from.test.ts | 34 ++++++++++ test/run.test.ts | 133 --------------------------------------- test/try.test.ts | 156 +++++++++++++++++++++++++++++++++++----------- 6 files changed, 261 insertions(+), 297 deletions(-) create mode 100644 src/try.ts create mode 100644 test/from.test.ts delete mode 100644 test/run.test.ts diff --git a/src/index.ts b/src/index.ts index c70a5fd..800ddfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,5 @@ export * from "./option"; export * from "./error"; export * from "./async_result"; export * from "./result"; -export * from "./run"; export * from "./try"; export * from "./util"; diff --git a/src/result.ts b/src/result.ts index 50c8b61..2141c8a 100644 --- a/src/result.ts +++ b/src/result.ts @@ -3,7 +3,6 @@ import {inspectSymbol} from "./util_internal"; import {type Option, Some, None} from "./option"; import {AsyncResult} from "./async_result"; import * as symbols from "./symbols"; -import type {InferErr} from "./util"; export type ResultMatch = { Ok: (value: T) => A; @@ -774,128 +773,3 @@ Result.fromPromise = (promise: Promise): AsyncResult => { ), ); }; - -/** - * Tries to execute an async function and returns the result as a `AsyncResult`. - * - * **Examples** - * - * ``` - * // const result: AsyncResult - * const result = Result.fromAsync(() => { - * if (Math.random() > 0.5) { - * return Promise.resolve(42) - * } else { - * throw new Error("random error") - * } - * }) - * ``` - */ -Result.fromAsync = (f: () => Promise): AsyncResult => { - return Result.fromPromise(f()); -}; - -function _try, U>( - fn: () => Generator, -): Result> { - const gen = fn(); - let done = false; - let returnResult = Ok(); - while (!done) { - const iter = gen.next(returnResult.unwrap()); - if (iter.value instanceof ResultImpl) { - if (iter.value.isErr()) { - done = true; - gen.return?.(iter.value as any); - } - returnResult = iter.value as any; - } else { - done = true; - returnResult = Ok(iter.value) as any; - } - } - return returnResult as any; -} - -/** - * Runs a generator function that returns a `Result` and infers its return type as `Result`. - * - * `yield*` must be used to yield the result of a `Result`. - * - * **Examples** - * - * ```ts - * // $ExpectType Result - * const result = run(function* () { - * const a = yield* Ok(1) - * const random = Math.random() - * if (random > 0.5) { - * yield* Err("error") - * } - * return a + random - * }) - * ``` - */ -Result.try = , U>(fn: () => Generator) => { - // Variable assignment helps with type inference - const result = _try(fn); - return result; -}; - -async function toPromiseResult(value: any): Promise> { - const awaited = await value; - if (value instanceof ResultImpl) { - return awaited as any; - } - return Ok(awaited); -} - -function _tryAsync | Result, U>( - fn: () => AsyncGenerator, -): AsyncResult>> { - const gen = fn(); - const yieldedResultChain = Promise.resolve>(Ok()).then( - async function fulfill(nextResult): Promise> { - const iter = await gen.next(nextResult.unwrap()); - const result = await toPromiseResult(iter.value); - if (iter.done) { - return result; - } - if (result.isErr()) { - gen.return?.(iter.value as any); - return result; - } - return Promise.resolve(result).then(fulfill); - }, - ); - return new AsyncResult(yieldedResultChain); -} - -/** - * Runs an async generator function that returns a `Result` and infers its return type as `AsyncResult`. - * - * `yield*` must be used to yield the result of a `AsyncResult` or `Result`. - * - * **Examples** - * - * ```ts - * const okOne = () => new AsyncResult(Promise.resolve(Ok(1))) - * - * // $ExpectType AsyncResult - * const result = runAsync(async function* () { - * const a = yield* okOne() - * const random = Math.random() - * if (random > 0.5) { - * yield* Err("error") - * } - * return a + random - * }) - * ``` - */ -Result.tryAsync = | Result, U>( - fn: () => AsyncGenerator, -) => { - // Variable assignment helps with type inference - const result = _tryAsync(fn); - return result; -}; diff --git a/src/try.ts b/src/try.ts new file mode 100644 index 0000000..7bb80bd --- /dev/null +++ b/src/try.ts @@ -0,0 +1,108 @@ +import {AsyncResult} from "./async_result"; +import {type Result, ResultImpl, Ok} from "./result"; +import type {InferErr} from "./util"; + +function _try, U>( + fn: () => Generator, +): Result> { + const gen = fn(); + let done = false; + let returnResult = Ok(); + while (!done) { + const iter = gen.next(returnResult.unwrap()); + if (iter.value instanceof ResultImpl) { + if (iter.value.isErr()) { + done = true; + gen.return?.(iter.value as any); + } + returnResult = iter.value as any; + } else { + done = true; + returnResult = Ok(iter.value) as any; + } + } + return returnResult as any; +} + +/** + * Runs a generator function that returns a `Result` and infers its return type as `Result`. + * + * `yield*` must be used to yield the result of a `Result`. + * + * **Examples** + * + * ```ts + * // $ExpectType Result + * const result = tryFn(function* () { + * const a = yield* Ok(1) + * const random = Math.random() + * if (random > 0.5) { + * yield* Err("error") + * } + * return a + random + * }) + * ``` + */ +export function tryFn, U>(fn: () => Generator) { + // Variable assignment helps with type inference + const result = _try(fn); + return result; +} + +async function toPromiseResult(value: any): Promise> { + const awaited = await value; + if (value instanceof ResultImpl) { + return awaited as any; + } + return Ok(awaited); +} + +function _tryAsync | Result, U>( + fn: () => AsyncGenerator, +): AsyncResult>> { + const gen = fn(); + const yieldedResultChain = Promise.resolve>(Ok()).then( + async function fulfill(nextResult): Promise> { + const iter = await gen.next(nextResult.unwrap()); + const result = await toPromiseResult(iter.value); + if (iter.done) { + return result; + } + if (result.isErr()) { + gen.return?.(iter.value as any); + return result; + } + return Promise.resolve(result).then(fulfill); + }, + ); + return new AsyncResult(yieldedResultChain); +} + +/** + * Runs an async generator function that returns a `Result` and infers its return type as `AsyncResult`. + * + * `yield*` must be used to yield the result of a `AsyncResult` or `Result`. + * + * **Examples** + * + * ```ts + * const okOne = () => new AsyncResult(Promise.resolve(Ok(1))) + * + * // $ExpectType AsyncResult + * const result = tryAsyncFn(async function* () { + * const a = yield* okOne() + * const random = Math.random() + * if (random > 0.5) { + * yield* Err("error") + * } + * return a + random + * }) + * ``` + */ +export function tryAsyncFn | Result, U>( + fn: () => AsyncGenerator, +) { + // Variable assignment helps with type inference + const result = _tryAsync(fn); + return result; +} diff --git a/test/from.test.ts b/test/from.test.ts new file mode 100644 index 0000000..536e4f1 --- /dev/null +++ b/test/from.test.ts @@ -0,0 +1,34 @@ +import {describe, expect, it} from "vitest"; +import {Result} from "../src"; + +describe.concurrent("from", () => { + it("wraps a function call into a Result object", () => { + const fn = () => 42; + const result = Result.from(fn); + expect(result.unwrap()).toEqual(42); + }); + + it("wraps a throwing function call into an Err result", () => { + const error = new Error("Test error"); + const fn = () => { + throw error; + }; + const result = Result.from(fn); + expect(result.unwrapErr()).toEqual(error); + }); +}); + +describe.concurrent("fromPromise", () => { + it("settles a Promise to an Ok result", async () => { + const promise = Promise.resolve(42); + const result = await Result.fromPromise(promise); + expect(result.unwrap()).toEqual(42); + }); + + it("settles a rejected Promise to an Err result", async () => { + const error = new Error("Test error"); + const promise = Promise.reject(error); + const result = await Result.fromPromise(promise); + expect(result.unwrapErr()).toEqual(error); + }); +}); diff --git a/test/run.test.ts b/test/run.test.ts deleted file mode 100644 index 83d3e5e..0000000 --- a/test/run.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {it, expect, expectTypeOf, describe, test} from "vitest"; -import {run, Ok, Err, Result, runAsync, AsyncResult, genFn, asyncGenFn} from "../src"; - -async function wait(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -describe("run", () => { - it("should run with all Oks", () => { - const result = run(function* () { - const x = yield* Ok(42); - const y = yield* Ok(1); - return x + y; - }); - expectTypeOf(result).toEqualTypeOf>(); - expect(result.unwrap()).toEqual(43); - }); - - it("should handle transformed return type", () => { - const result = run(function* () { - const x = yield* Ok(42); - const y = yield* Ok(1); - return x.toString() + y.toString(); - }); - expectTypeOf(result).toEqualTypeOf>(); - expect(result.unwrap()).toEqual("421"); - }); - - it("works with function call", () => { - function fn() { - return run(function* () { - const x = yield* Ok(1); - const y = yield* Ok(1); - return x + y; - }); - } - - const result = run(function* () { - const x = yield* Ok(1); - const y = yield* fn(); - return x + y; - }); - - expectTypeOf(result).toEqualTypeOf>(); - expect(result.unwrap()).toEqual(3); - }); - - it("should run with early Err", () => { - const result = run(function* () { - const x = yield* Ok(42); - const y = yield* Err("error"); - return x + y; - }); - expectTypeOf(result).toEqualTypeOf>(); - expect(result.unwrapErr()).toEqual("error"); - }); -}); - -describe("runAsync", () => { - it("should run async with all Oks", async () => { - const result = runAsync(async function* () { - const x = yield* Ok(42); - const y = yield* new AsyncResult(Promise.resolve(Ok(1))); - return x + y; - }); - expectTypeOf(result).toEqualTypeOf>(); - await expect(result.unwrap()).resolves.toEqual(43); - }); - - it("should run async with early Err", async () => { - const result = runAsync(async function* () { - const x = yield* Ok(42); - const y = yield* new AsyncResult(Promise.resolve(Err("error"))); - return x + y; - }); - expectTypeOf(result).toEqualTypeOf>(); - await expect(result.unwrapErr()).resolves.toEqual("error"); - }); - - it("works with function call", async () => { - function fn() { - return runAsync(async function* () { - const x = yield* Ok(1); - const y = yield* new AsyncResult(Promise.resolve(Ok(1))); - return x + y; - }); - } - - const result = runAsync(async function* () { - const x = yield* Ok(1); - const y = yield* fn(); - return x + y; - }); - - expectTypeOf(result).toEqualTypeOf>(); - await expect(result.unwrap()).resolves.toEqual(3); - }); - - it("should handle transformed return type", async () => { - const result = runAsync(async function* () { - const x = yield* Ok(42); - const y = yield* new AsyncResult(Promise.resolve(Ok(1))); - return x.toString() + y.toString(); - }); - expectTypeOf(result).toEqualTypeOf>(); - await expect(result.unwrap()).resolves.toEqual("421"); - }); - - it( - "should not block", - async () => { - const duration = 1000; - - function shouldNotBlock() { - return runAsync(async function* () { - const x = yield* Ok(1); - await wait(duration); - const y = yield* new AsyncResult(Promise.resolve(Ok(1))); - return x + y; - }); - } - - const start = Date.now(); - setTimeout(() => { - expect(Date.now() - start).toBeLessThan(duration); - }, duration / 2); - - const result = await shouldNotBlock(); - expect(result.unwrap()).toEqual(2); - }, - 60 * 1000, - ); -}); diff --git a/test/try.test.ts b/test/try.test.ts index 6766cca..9463dfe 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -1,51 +1,133 @@ -import {describe, expect, it} from "vitest"; -import {tryAsyncFn, tryFn, tryPromise} from "../src"; +import {it, expect, expectTypeOf, describe} from "vitest"; +import {tryFn, Ok, Err, Result, tryAsyncFn, AsyncResult} from "../src"; -describe.concurrent("tryFn", () => { - it("wraps a function call into a Result object", () => { - const fn = () => 42; - const result = tryFn(fn); - expect(result.unwrap()).toEqual(42); +async function wait(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +describe("tryFn", () => { + it("should tryFn with all Oks", () => { + const result = tryFn(function* () { + const x = yield* Ok(42); + const y = yield* Ok(1); + return x + y; + }); + expectTypeOf(result).toEqualTypeOf>(); + expect(result.unwrap()).toEqual(43); }); - it("wraps a throwing function call into an Err result", () => { - const error = new Error("Test error"); - const fn = () => { - throw error; - }; - const result = tryFn(fn); - expect(result.unwrapErr()).toEqual(error); + it("should handle transformed return type", () => { + const result = tryFn(function* () { + const x = yield* Ok(42); + const y = yield* Ok(1); + return x.toString() + y.toString(); + }); + expectTypeOf(result).toEqualTypeOf>(); + expect(result.unwrap()).toEqual("421"); }); -}); -describe.concurrent("tryPromise", () => { - it("settles a Promise to an Ok result", async () => { - const promise = Promise.resolve(42); - const result = await tryPromise(promise); - expect(result.unwrap()).toEqual(42); + it("works with function call", () => { + function fn() { + return tryFn(function* () { + const x = yield* Ok(1); + const y = yield* Ok(1); + return x + y; + }); + } + + const result = tryFn(function* () { + const x = yield* Ok(1); + const y = yield* fn(); + return x + y; + }); + + expectTypeOf(result).toEqualTypeOf>(); + expect(result.unwrap()).toEqual(3); }); - it("settles a rejected Promise to an Err result", async () => { - const error = new Error("Test error"); - const promise = Promise.reject(error); - const result = await tryPromise(promise); - expect(result.unwrapErr()).toEqual(error); + it("should tryFn with early Err", () => { + const result = tryFn(function* () { + const x = yield* Ok(42); + const y = yield* Err("error"); + return x + y; + }); + expectTypeOf(result).toEqualTypeOf>(); + expect(result.unwrapErr()).toEqual("error"); }); }); -describe.concurrent("tryAsyncFn", () => { - it("wraps an async function call into a Result object", async () => { - const fn = async () => Promise.resolve(42); - const result = await tryAsyncFn(fn); - expect(result.unwrap()).toEqual(42); +describe("tryAsyncFn", () => { + it("should tryFn async with all Oks", async () => { + const result = tryAsyncFn(async function* () { + const x = yield* Ok(42); + const y = yield* new AsyncResult(Promise.resolve(Ok(1))); + return x + y; + }); + expectTypeOf(result).toEqualTypeOf>(); + await expect(result.unwrap()).resolves.toEqual(43); + }); + + it("should tryFn async with early Err", async () => { + const result = tryAsyncFn(async function* () { + const x = yield* Ok(42); + const y = yield* new AsyncResult(Promise.resolve(Err("error"))); + return x + y; + }); + expectTypeOf(result).toEqualTypeOf>(); + await expect(result.unwrapErr()).resolves.toEqual("error"); + }); + + it("works with function call", async () => { + function fn() { + return tryAsyncFn(async function* () { + const x = yield* Ok(1); + const y = yield* new AsyncResult(Promise.resolve(Ok(1))); + return x + y; + }); + } + + const result = tryAsyncFn(async function* () { + const x = yield* Ok(1); + const y = yield* fn(); + return x + y; + }); + + expectTypeOf(result).toEqualTypeOf>(); + await expect(result.unwrap()).resolves.toEqual(3); }); - it("wraps a throwing async function call into an Err result", async () => { - const error = new Error("Test error"); - const fn = async (): Promise => { - throw error; - }; - const result = await tryAsyncFn(fn); - expect(result.unwrapErr()).toEqual(error); + it("should handle transformed return type", async () => { + const result = tryAsyncFn(async function* () { + const x = yield* Ok(42); + const y = yield* new AsyncResult(Promise.resolve(Ok(1))); + return x.toString() + y.toString(); + }); + expectTypeOf(result).toEqualTypeOf>(); + await expect(result.unwrap()).resolves.toEqual("421"); }); + + it( + "should not block", + async () => { + const duration = 1000; + + function shouldNotBlock() { + return tryAsyncFn(async function* () { + const x = yield* Ok(1); + await wait(duration); + const y = yield* new AsyncResult(Promise.resolve(Ok(1))); + return x + y; + }); + } + + const start = Date.now(); + setTimeout(() => { + expect(Date.now() - start).toBeLessThan(duration); + }, duration / 2); + + const result = await shouldNotBlock(); + expect(result.unwrap()).toEqual(2); + }, + 60 * 1000, + ); }); From 4462bac67986009ea0d865f301612c5e8f284d75 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 19:20:54 +0200 Subject: [PATCH 16/23] Update try --- src/fn.ts | 10 +++--- src/try.ts | 86 ++++++++++++++++++++---------------------------- test/try.test.ts | 34 +++++++++---------- 3 files changed, 58 insertions(+), 72 deletions(-) diff --git a/src/fn.ts b/src/fn.ts index 1412068..a4daed8 100644 --- a/src/fn.ts +++ b/src/fn.ts @@ -1,6 +1,6 @@ import {type Result} from "./result"; import {AsyncResult} from "./async_result"; -import {run, runAsync} from "./run"; +import {trySync, tryAsync} from "./try"; import type {InferErr, InferOk} from "./util"; /** @@ -67,11 +67,11 @@ export function asyncFn(f: any): any { * }) * ``` */ -export function genFn, T>( +export function gen, T>( fn: (...args: A) => Generator, ): (...args: A) => Result> { return function (...args: any[]) { - return run(() => fn(...(args as A))); + return trySync(() => fn(...(args as A))); }; } @@ -93,10 +93,10 @@ export function genFn, T>( * }) * ``` */ -export function asyncGenFn | Result, T>( +export function asyncGen | Result, T>( fn: (...args: A) => AsyncGenerator, ): (...args: A) => AsyncResult>> { return function (...args: any[]) { - return runAsync(() => fn(...(args as A))); + return tryAsync(() => fn(...(args as A))); }; } diff --git a/src/try.ts b/src/try.ts index 7bb80bd..b112ef9 100644 --- a/src/try.ts +++ b/src/try.ts @@ -2,7 +2,26 @@ import {AsyncResult} from "./async_result"; import {type Result, ResultImpl, Ok} from "./result"; import type {InferErr} from "./util"; -function _try, U>( +/** + * Runs a generator function that returns a `Result` and infers its return type as `Result`. + * + * `yield*` must be used to yield the result of a `Result`. + * + * **Examples** + * + * ```ts + * // $ExpectType Result + * const result = tryFn(function* () { + * const a = yield* Ok(1) + * const random = Math.random() + * if (random > 0.5) { + * yield* Err("error") + * } + * return a + random + * }) + * ``` + */ +export function trySync, U>( fn: () => Generator, ): Result> { const gen = fn(); @@ -24,17 +43,27 @@ function _try, U>( return returnResult as any; } +async function toPromiseResult(value: any): Promise> { + const awaited = await value; + if (value instanceof ResultImpl) { + return awaited as any; + } + return Ok(awaited); +} + /** - * Runs a generator function that returns a `Result` and infers its return type as `Result`. + * Runs an async generator function that returns a `Result` and infers its return type as `AsyncResult`. * - * `yield*` must be used to yield the result of a `Result`. + * `yield*` must be used to yield the result of a `AsyncResult` or `Result`. * * **Examples** * * ```ts - * // $ExpectType Result - * const result = tryFn(function* () { - * const a = yield* Ok(1) + * const okOne = () => new AsyncResult(Promise.resolve(Ok(1))) + * + * // $ExpectType AsyncResult + * const result = tryAsyncFn(async function* () { + * const a = yield* okOne() * const random = Math.random() * if (random > 0.5) { * yield* Err("error") @@ -43,21 +72,7 @@ function _try, U>( * }) * ``` */ -export function tryFn, U>(fn: () => Generator) { - // Variable assignment helps with type inference - const result = _try(fn); - return result; -} - -async function toPromiseResult(value: any): Promise> { - const awaited = await value; - if (value instanceof ResultImpl) { - return awaited as any; - } - return Ok(awaited); -} - -function _tryAsync | Result, U>( +export function tryAsync | Result, U>( fn: () => AsyncGenerator, ): AsyncResult>> { const gen = fn(); @@ -77,32 +92,3 @@ function _tryAsync | Result, U>( ); return new AsyncResult(yieldedResultChain); } - -/** - * Runs an async generator function that returns a `Result` and infers its return type as `AsyncResult`. - * - * `yield*` must be used to yield the result of a `AsyncResult` or `Result`. - * - * **Examples** - * - * ```ts - * const okOne = () => new AsyncResult(Promise.resolve(Ok(1))) - * - * // $ExpectType AsyncResult - * const result = tryAsyncFn(async function* () { - * const a = yield* okOne() - * const random = Math.random() - * if (random > 0.5) { - * yield* Err("error") - * } - * return a + random - * }) - * ``` - */ -export function tryAsyncFn | Result, U>( - fn: () => AsyncGenerator, -) { - // Variable assignment helps with type inference - const result = _tryAsync(fn); - return result; -} diff --git a/test/try.test.ts b/test/try.test.ts index 9463dfe..e19e877 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -1,13 +1,13 @@ import {it, expect, expectTypeOf, describe} from "vitest"; -import {tryFn, Ok, Err, Result, tryAsyncFn, AsyncResult} from "../src"; +import {trySync, Ok, Err, Result, tryAsync, AsyncResult} from "../src"; async function wait(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -describe("tryFn", () => { - it("should tryFn with all Oks", () => { - const result = tryFn(function* () { +describe("trySync", () => { + it("should try with all Oks", () => { + const result = trySync(function* () { const x = yield* Ok(42); const y = yield* Ok(1); return x + y; @@ -17,7 +17,7 @@ describe("tryFn", () => { }); it("should handle transformed return type", () => { - const result = tryFn(function* () { + const result = trySync(function* () { const x = yield* Ok(42); const y = yield* Ok(1); return x.toString() + y.toString(); @@ -28,14 +28,14 @@ describe("tryFn", () => { it("works with function call", () => { function fn() { - return tryFn(function* () { + return trySync(function* () { const x = yield* Ok(1); const y = yield* Ok(1); return x + y; }); } - const result = tryFn(function* () { + const result = trySync(function* () { const x = yield* Ok(1); const y = yield* fn(); return x + y; @@ -45,8 +45,8 @@ describe("tryFn", () => { expect(result.unwrap()).toEqual(3); }); - it("should tryFn with early Err", () => { - const result = tryFn(function* () { + it("should try with early Err", () => { + const result = trySync(function* () { const x = yield* Ok(42); const y = yield* Err("error"); return x + y; @@ -57,8 +57,8 @@ describe("tryFn", () => { }); describe("tryAsyncFn", () => { - it("should tryFn async with all Oks", async () => { - const result = tryAsyncFn(async function* () { + it("should try async with all Oks", async () => { + const result = tryAsync(async function* () { const x = yield* Ok(42); const y = yield* new AsyncResult(Promise.resolve(Ok(1))); return x + y; @@ -67,8 +67,8 @@ describe("tryAsyncFn", () => { await expect(result.unwrap()).resolves.toEqual(43); }); - it("should tryFn async with early Err", async () => { - const result = tryAsyncFn(async function* () { + it("should try async with early Err", async () => { + const result = tryAsync(async function* () { const x = yield* Ok(42); const y = yield* new AsyncResult(Promise.resolve(Err("error"))); return x + y; @@ -79,14 +79,14 @@ describe("tryAsyncFn", () => { it("works with function call", async () => { function fn() { - return tryAsyncFn(async function* () { + return tryAsync(async function* () { const x = yield* Ok(1); const y = yield* new AsyncResult(Promise.resolve(Ok(1))); return x + y; }); } - const result = tryAsyncFn(async function* () { + const result = tryAsync(async function* () { const x = yield* Ok(1); const y = yield* fn(); return x + y; @@ -97,7 +97,7 @@ describe("tryAsyncFn", () => { }); it("should handle transformed return type", async () => { - const result = tryAsyncFn(async function* () { + const result = tryAsync(async function* () { const x = yield* Ok(42); const y = yield* new AsyncResult(Promise.resolve(Ok(1))); return x.toString() + y.toString(); @@ -112,7 +112,7 @@ describe("tryAsyncFn", () => { const duration = 1000; function shouldNotBlock() { - return tryAsyncFn(async function* () { + return tryAsync(async function* () { const x = yield* Ok(1); await wait(duration); const y = yield* new AsyncResult(Promise.resolve(Ok(1))); From cb8d5016de749ff090878f130139c558c502f75e Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 19:23:40 +0200 Subject: [PATCH 17/23] Fix type errors --- test/fn.test.ts | 29 ++++++++--------------------- test/option.test.ts | 2 +- test/result.test.ts | 4 ++-- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/test/fn.test.ts b/test/fn.test.ts index 09b05d2..10307cd 100644 --- a/test/fn.test.ts +++ b/test/fn.test.ts @@ -1,16 +1,5 @@ import {describe, expect, it, expectTypeOf, test} from "vitest"; -import { - asyncFn, - fn, - Ok, - Err, - tryAsyncFn, - AsyncResult, - type Result, - tryFn, - genFn, - asyncGenFn, -} from "../src"; +import {asyncFn, fn, Ok, Err, AsyncResult, Result, gen, asyncGen} from "../src"; import {TaggedError} from "./util"; describe.concurrent("fn", () => { @@ -58,7 +47,7 @@ describe.concurrent("fn", () => { // }) it("returns correct type with function returning Result", () => { - const wrapped = fn((_arg: number) => tryFn(() => 1)); + const wrapped = fn((_arg: number) => Result.from(() => 1)); expectTypeOf(wrapped).parameter(0).toBeNumber(); expectTypeOf(wrapped).returns.toEqualTypeOf>(); }); @@ -188,7 +177,7 @@ describe.concurrent("asyncFn", () => { }); it("returns correct type with function returning AsyncResult", () => { - const f = (_arg: number) => tryAsyncFn(async () => 1); + const f = (_arg: number) => Result.fromPromise(Promise.resolve(1)); const wrapped = asyncFn(f); expectTypeOf(wrapped).parameter(0).toBeNumber(); expectTypeOf(wrapped).returns.toEqualTypeOf>(); @@ -196,9 +185,7 @@ describe.concurrent("asyncFn", () => { it("returns correct type with function returning Promise", () => { const f = async (_arg: number) => { - const bar = tryAsyncFn(async () => { - return 1; - }); + const bar = Result.fromPromise(Promise.resolve(1)); return bar; }; const wrapped = asyncFn(f); @@ -235,8 +222,8 @@ describe.concurrent("asyncFn", () => { }); }); -test("genFn", () => { - const fn = genFn(function* (arg: number) { +test("gen", () => { + const fn = gen(function* (arg: number) { const x = yield* Ok(arg); const y = yield* Err(1); const z = yield* Err("error"); @@ -245,8 +232,8 @@ test("genFn", () => { expectTypeOf(fn).toEqualTypeOf<(arg: number) => Result>(); }); -test("asyncGenFn", () => { - const fn = asyncGenFn(async function* (arg: number) { +test("asyncGen", () => { + const fn = asyncGen(async function* (arg: number) { const x = yield* new AsyncResult(Promise.resolve(Ok(arg))); const y = yield* new AsyncResult(Promise.resolve(Err(1))); const z = yield* new AsyncResult(Promise.resolve(Err("error"))); diff --git a/test/option.test.ts b/test/option.test.ts index ced424a..0c2d18c 100644 --- a/test/option.test.ts +++ b/test/option.test.ts @@ -30,7 +30,7 @@ describe.concurrent("core", () => { it("works with discriminated union", () => { const option = TestSome(42); - expectTypeOf(option.value).toEqualTypeOf<() => number | undefined>(); + expectTypeOf(option.value()).toEqualTypeOf(); if (option.isSome()) { expectTypeOf(option.value).toEqualTypeOf<() => number>(); expectTypeOf(option.unwrap).toEqualTypeOf<() => number>(); diff --git a/test/result.test.ts b/test/result.test.ts index 5aa5d16..352e36b 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -46,8 +46,8 @@ describe.concurrent("core", () => { it("works as discriminated union", () => { const r = TestOk(42); - expectTypeOf(r.value).toEqualTypeOf<() => number | undefined>(); - expectTypeOf(r.error).toEqualTypeOf<() => string | undefined>(); + expectTypeOf(r.value()).toEqualTypeOf(); + expectTypeOf(r.error()).toEqualTypeOf(); if (r.isOk()) { expectTypeOf(r.value).toEqualTypeOf<() => number>(); expectTypeOf(r.error).toEqualTypeOf<() => undefined>(); From b984ebc019a7a660fdad01f76cb8f25752ca36a1 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 19:38:19 +0200 Subject: [PATCH 18/23] Rename --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 35b9374..993dfe8 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "patina", + "name": "@patina/std", "version": "0.1.0", "type": "module", - "description": "Type-safe error-handling library for TypeScript", + "description": "Type-safe nothing-handling and error-handling library", "repository": { "type": "git", "url": "git+https://github.com/bkiac/patina.git" From 1b15afcfd89c52e12ac04e6914f4686cf7aee78f Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 19:40:47 +0200 Subject: [PATCH 19/23] Simplify none --- src/option.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/option.ts b/src/option.ts index 0b2dd98..1962ce0 100644 --- a/src/option.ts +++ b/src/option.ts @@ -501,7 +501,7 @@ export interface None extends OptionImpl { /** * No value. */ -export const None = new OptionImpl(false, undefined) as None; +export const None = new OptionImpl(false) as None; /** * `Option` represents an optional value: every `Option` is either `Some` and contains a value, or `None`, and does not. From f1440eb11847feb7679b80e7e90aff3cc387e3b8 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 19:48:36 +0200 Subject: [PATCH 20/23] Update kind property --- src/option.ts | 14 +++++++------- src/result.ts | 16 +++++++++------- src/symbols.ts | 1 + 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/option.ts b/src/option.ts index 1962ce0..6050453 100644 --- a/src/option.ts +++ b/src/option.ts @@ -16,12 +16,12 @@ export type OptionMatchAsync = { }; export class OptionImpl { - readonly [symbols.kind]: boolean; + private readonly [symbols.kind]?: true; private readonly [symbols.value]?: T; constructor(some: boolean, x?: T) { - this[symbols.kind] = some; - if (x !== undefined) { + if (some) { + this[symbols.kind] = true; this[symbols.value] = x; } } @@ -61,7 +61,7 @@ export class OptionImpl { * Returns `true` if the option is a `Some` value. */ isSome(): this is Some { - return this[symbols.kind]; + return this[symbols.kind] ?? false; } /** @@ -77,7 +77,7 @@ export class OptionImpl { * Returns `true` if the option is a `None` value. */ isNone(): this is None { - return !this[symbols.kind]; + return !this.isSome(); } /** @@ -478,7 +478,7 @@ export class OptionImpl { } export interface Some extends OptionImpl { - [symbols.kind]: true; + [symbols.tag]: "Some"; value(): T; unwrap(): T; expect(message: string): T; @@ -492,7 +492,7 @@ export function Some(value: T): Some { } export interface None extends OptionImpl { - [symbols.kind]: false; + [symbols.tag]: "None"; value(): undefined; unwrap(): never; expect(message: string): never; diff --git a/src/result.ts b/src/result.ts index 2141c8a..fa511fd 100644 --- a/src/result.ts +++ b/src/result.ts @@ -15,11 +15,13 @@ export type ResultMatchAsync = { }; export class ResultImpl { - readonly [symbols.kind]: boolean; + private readonly [symbols.kind]?: boolean; private readonly [symbols.value]: T | E; constructor(k: boolean, v: T | E) { - this[symbols.kind] = k; + if (k) { + this[symbols.kind] = k; + } this[symbols.value] = v; } @@ -87,7 +89,7 @@ export class ResultImpl { * Returns `true` if the result is `Ok`. */ isOk(): this is Ok { - return this[symbols.kind]; + return this[symbols.kind] ?? false; } /** @@ -96,14 +98,14 @@ export class ResultImpl { * Maybe not as useful as using `result.isOk() && f(result.value)`, because it doesn't narrow the type, but it's here for completeness. */ isOkAnd(f: (value: T) => boolean): this is Ok { - return this[symbols.kind] && f(this[symbols.value] as T); + return this.isOk() && f(this[symbols.value] as T); } /** * Returns `true` if the result is `Err`. */ isErr(): this is Err { - return !this[symbols.kind]; + return !this.isOk(); } /** @@ -672,7 +674,7 @@ export class ResultImpl { } export interface Ok extends ResultImpl { - [symbols.kind]: true; + [symbols.tag]: "Ok"; value(): T; error(): undefined; unwrap(): T; @@ -691,7 +693,7 @@ export function Ok(value?: T): Ok { } export interface Err extends ResultImpl { - [symbols.kind]: false; + [symbols.tag]: "Err"; value(): undefined; error(): E; unwrap(): never; diff --git a/src/symbols.ts b/src/symbols.ts index c8cffce..32ce50f 100644 --- a/src/symbols.ts +++ b/src/symbols.ts @@ -1,2 +1,3 @@ export const kind = Symbol("kind"); export const value = Symbol("value"); +export const tag = Symbol("tag"); From fea7888426770a4ea21c36746460fd446aaa2759 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 20:23:57 +0200 Subject: [PATCH 21/23] Optimize access by removing match --- src/option.ts | 176 +++++++++++++----------------------------- src/result.ts | 208 ++++++++++++++++---------------------------------- 2 files changed, 116 insertions(+), 268 deletions(-) diff --git a/src/option.ts b/src/option.ts index 6050453..ea57f52 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,9 +1,9 @@ import {AsyncOption} from "./async_option"; import {AsyncResult} from "./async_result"; -import * as symbols from "./symbols"; import {Panic} from "./error"; import {Err, Ok, type Result} from "./result"; import {inspectSymbol} from "./util_internal"; +import * as symbols from "./symbols"; export type OptionMatch = { Some: (value: T) => A; @@ -16,18 +16,18 @@ export type OptionMatchAsync = { }; export class OptionImpl { - private readonly [symbols.kind]?: true; - private readonly [symbols.value]?: T; + readonly #kind?: true; + readonly #val?: T; constructor(some: boolean, x?: T) { if (some) { - this[symbols.kind] = true; - this[symbols.value] = x; + this.#kind = true; + this.#val = x!; } } private unwrapFailed(message: string): never { - throw new Panic(message, {cause: this[symbols.value]}); + throw new Panic(message, {cause: this.#val}); } /** @@ -43,25 +43,25 @@ export class OptionImpl { * ``` */ match(pattern: OptionMatch): A | B { - return this[symbols.kind] ? pattern.Some(this[symbols.value] as T) : pattern.None(); + return this.#kind ? pattern.Some(this.#val as T) : pattern.None(); } matchAsync(pattern: OptionMatchAsync): Promise { - return this[symbols.kind] ? pattern.Some(this[symbols.value] as T) : pattern.None(); + return this.#kind ? pattern.Some(this.#val as T) : pattern.None(); } /** * Returns the contained `Some` value, if exists. */ value(): T | undefined { - return this[symbols.value]; + return this.#val; } /** * Returns `true` if the option is a `Some` value. */ isSome(): this is Some { - return this[symbols.kind] ?? false; + return this.#kind === true; } /** @@ -70,14 +70,14 @@ export class OptionImpl { * Maybe not as useful as using `option.isSome() && f(option.value)`, because it doesn't narrow the type, but it's here for completeness. */ isSomeAnd(predicate: (value: T) => boolean): this is Some { - return this.isSome() && predicate(this[symbols.value] as T); + return this.#kind === true && predicate(this.#val as T); } /** * Returns `true` if the option is a `None` value. */ isNone(): this is None { - return !this.isSome(); + return this.#kind !== true; } /** @@ -93,10 +93,7 @@ export class OptionImpl { * ``` */ expect(message: string): T { - return this.match({ - Some: (t) => t, - None: () => this.unwrapFailed(message), - }); + return this.#kind ? (this.#val as T) : this.unwrapFailed(message); } /** @@ -128,10 +125,7 @@ export class OptionImpl { * ``` */ unwrapOr(defaultValue: U): T | U { - return this.match({ - Some: (t) => t, - None: () => defaultValue, - }); + return this.#kind ? (this.#val as T) : defaultValue; } /** @@ -147,17 +141,11 @@ export class OptionImpl { * ``` */ unwrapOrElse(defaultValue: () => U): T | U { - return this.match({ - Some: (t) => t, - None: () => defaultValue(), - }); + return this.#kind ? (this.#val as T) : defaultValue(); } unwrapOrElseAsync(defaultValue: () => Promise): Promise { - return this.matchAsync({ - Some: (t) => Promise.resolve(t), - None: () => defaultValue(), - }); + return this.#kind ? Promise.resolve(this.#val as T) : defaultValue(); } /** @@ -171,19 +159,11 @@ export class OptionImpl { * ``` */ map(f: (value: T) => U): Option { - return this.match({ - Some: (t) => Some(f(t)), - None: () => None, - }); + return this.#kind ? Some(f(this.#val as T)) : None; } mapAsync(f: (value: T) => Promise): AsyncOption { - return new AsyncOption( - this.matchAsync({ - Some: (t) => f(t).then(Some), - None: () => Promise.resolve(None), - }), - ); + return new AsyncOption(this.#kind ? f(this.#val as T).then(Some) : Promise.resolve(None)); } /** @@ -196,21 +176,17 @@ export class OptionImpl { * ``` */ inspect(f: (value: T) => void): this { - return this.match({ - Some: (t) => { - f(t); - return this; - }, - None: () => this, - }); + if (this.#kind) { + f(this.#val as T); + } + return this; } inspectAsync(f: (value: T) => Promise): AsyncOption { return new AsyncOption( - this.matchAsync({ - Some: (t) => f(t).then(() => Some(t)), - None: () => Promise.resolve(None), - }), + this.#kind + ? f(this.#val as T).then(() => this as unknown as Some) + : Promise.resolve(None), ); } @@ -225,10 +201,11 @@ export class OptionImpl { * ``` */ mapOr(defaultValue: A, f: (value: T) => B): A | B { - return this.match({ - Some: (t) => f(t), - None: () => defaultValue, - }); + return this.#kind ? f(this.#val as T) : defaultValue; + } + + mapOrAsync(defaultValue: A, f: (value: T) => Promise): Promise { + return this.#kind ? f(this.#val as T) : Promise.resolve(defaultValue); } /** @@ -243,20 +220,14 @@ export class OptionImpl { * ``` */ mapOrElse(defaultValue: () => A, f: (value: T) => B): A | B { - return this.match({ - Some: (t) => f(t), - None: () => defaultValue(), - }); + return this.#kind ? f(this.#val as T) : defaultValue(); } mapOrElseAsync( defaultValue: () => Promise, f: (value: T) => Promise, ): Promise { - return this.matchAsync({ - Some: (t) => f(t), - None: () => defaultValue(), - }); + return this.#kind ? f(this.#val as T) : defaultValue(); } /** @@ -270,10 +241,7 @@ export class OptionImpl { * ``` */ okOr(err: E): Result { - return this.match({ - Some: (t) => Ok(t), - None: () => Err(err), - }); + return this.#kind ? Ok(this.#val as T) : Err(err); } /** @@ -287,18 +255,14 @@ export class OptionImpl { * ``` */ okOrElse(err: () => E): Result { - return this.match({ - Some: (t) => Ok(t), - None: () => Err(err()), - }); + return this.#kind ? Ok(this.#val as T) : Err(err()); } okOrElseAsync(err: () => Promise): AsyncResult { - return new AsyncResult( - this.matchAsync({ - Some: (t) => Promise.resolve(Ok(t)), - None: () => err().then(Err), - }) as Promise>, + return new AsyncResult( + this.#kind + ? Promise.resolve(Ok(this.#val as T)) + : (err().then(Err) as Promise>), ); } @@ -315,10 +279,7 @@ export class OptionImpl { * ``` */ and(other: Option): Option { - return this.match({ - Some: () => other, - None: () => None, - }); + return this.#kind ? other : None; } /** @@ -333,19 +294,11 @@ export class OptionImpl { * ``` */ andThen(f: (value: T) => Option): Option { - return this.match({ - Some: (t) => f(t), - None: () => None, - }); + return this.#kind ? f(this.#val as T) : None; } andThenAsync(f: (value: T) => Promise> | AsyncOption): AsyncOption { - return new AsyncOption( - this.matchAsync({ - Some: (t) => f(t) as Promise>, - None: () => Promise.resolve(None), - }), - ); + return new AsyncOption(this.#kind ? f(this.#val as T) : Promise.resolve(None)); } /** @@ -362,10 +315,7 @@ export class OptionImpl { * ``` */ filter(predicate: (value: T) => boolean): Option { - return this.match({ - Some: (t) => (predicate(t) ? Some(t) : None), - None: () => None, - }); + return (this.#kind && predicate(this.#val as T) ? this : None) as Option; } /** @@ -381,10 +331,7 @@ export class OptionImpl { * ``` */ or(other: Option): Option { - return this.match({ - Some: (t) => Some(t), - None: () => other, - }); + return (this.#kind ? this : other) as Option; } /** @@ -400,18 +347,14 @@ export class OptionImpl { * ``` */ orElse(f: () => Option): Option { - return this.match({ - Some: (t) => Some(t), - None: () => f(), - }); + return (this.#kind ? this : f()) as Option; } orElseAsync(f: () => Promise> | AsyncOption): AsyncOption { return new AsyncOption( - this.matchAsync({ - Some: (t) => Promise.resolve(Some(t)), - None: () => f() as Promise>, - }) as Promise>, + (this.#kind ? Promise.resolve(this as unknown as Some) : f()) as Promise< + Option + >, ); } @@ -428,10 +371,7 @@ export class OptionImpl { * ``` */ xor(other: Option): Option { - return this.match({ - Some: (t) => (other.isNone() ? Some(t) : None), - None: () => other, - }); + return (this.#kind ? (other.#kind ? None : this) : other) as Option; } /** @@ -445,31 +385,19 @@ export class OptionImpl { * ``` */ flatten(this: Option>): Option { - return this.match({ - Some: (t) => t, - None: () => None, - }); + return this.#kind ? (this.#val as Option) : None; } toObject(): {isSome: true; value: T} | {isSome: false; value: null} { - return this.match({ - Some: (value) => ({isSome: true, value}), - None: () => ({isSome: false, value: null}), - }); + return this.#kind ? {isSome: true, value: this.#val as T} : {isSome: false, value: null}; } toJSON(): {meta: "Some"; value: T} | {meta: "None"; value: null} { - return this.match({ - Some: (value) => ({meta: "Some", value}), - None: () => ({meta: "None", value: null}), - }); + return this.#kind ? {meta: "Some", value: this.#val as T} : {meta: "None", value: null}; } toString(): `Some(${string})` | "None" { - return this.match({ - Some: (value) => `Some(${String(value)})` as const, - None: () => "None" as const, - }); + return this.#kind ? `Some(${String(this.#val)})` : "None"; } [inspectSymbol](): ReturnType["toString"]> { diff --git a/src/result.ts b/src/result.ts index fa511fd..732f52a 100644 --- a/src/result.ts +++ b/src/result.ts @@ -15,18 +15,14 @@ export type ResultMatchAsync = { }; export class ResultImpl { - private readonly [symbols.kind]?: boolean; - private readonly [symbols.value]: T | E; + readonly #kind?: true; + readonly #val: T | E; constructor(k: boolean, v: T | E) { if (k) { - this[symbols.kind] = k; + this.#kind = true; } - this[symbols.value] = v; - } - - private unwrapFailed(message: string): never { - throw new Panic(message, {cause: this[symbols.value]}); + this.#val = v; } *[Symbol.iterator](): Iterator, T, any> { @@ -34,6 +30,10 @@ export class ResultImpl { return yield self; } + private unwrapFailed(message: string): never { + throw new Panic(message, {cause: this.#val}); + } + /** * Matches the result with two functions. * @@ -54,42 +54,32 @@ export class ResultImpl { * ``` */ match(pattern: ResultMatch): A | B { - return this[symbols.kind] - ? pattern.Ok(this[symbols.value] as T) - : pattern.Err(this[symbols.value] as E); + return this.#kind ? pattern.Ok(this.#val as T) : pattern.Err(this.#val as E); } matchAsync(pattern: ResultMatchAsync): Promise { - return this[symbols.kind] - ? pattern.Ok(this[symbols.value] as T) - : pattern.Err(this[symbols.value] as E); + return this.#kind ? pattern.Ok(this.#val as T) : pattern.Err(this.#val as E); } /** * Returns the contained value, if it exists. */ value(): T | undefined { - return this.match({ - Ok: (t) => t, - Err: () => undefined, - }); + return this.#kind ? (this.#val as T) : undefined; } /** * Returns the contained error, if it exists. */ error(): E | undefined { - return this.match({ - Ok: () => undefined, - Err: (e) => e, - }); + return this.#kind ? undefined : (this.#val as E); } /** * Returns `true` if the result is `Ok`. */ isOk(): this is Ok { - return this[symbols.kind] ?? false; + return this.#kind === true; } /** @@ -98,14 +88,14 @@ export class ResultImpl { * Maybe not as useful as using `result.isOk() && f(result.value)`, because it doesn't narrow the type, but it's here for completeness. */ isOkAnd(f: (value: T) => boolean): this is Ok { - return this.isOk() && f(this[symbols.value] as T); + return this.#kind === true && f(this.#val as T); } /** * Returns `true` if the result is `Err`. */ isErr(): this is Err { - return !this.isOk(); + return this.#kind === undefined; } /** @@ -114,7 +104,7 @@ export class ResultImpl { * Maybe not as useful as using `result.isErr() && f(result.error)`, because it doesn't narrow the type, but it's here for completeness. */ isErrAnd(f: (error: E) => boolean): this is Err { - return !this[symbols.kind] && f(this[symbols.value] as E); + return this.#kind === undefined && f(this.#val as E); } /** @@ -131,10 +121,7 @@ export class ResultImpl { * ``` */ ok(): Option { - return this.match({ - Ok: (t) => Some(t), - Err: () => None, - }); + return this.#kind ? Some(this.#val as T) : None; } /** @@ -151,10 +138,7 @@ export class ResultImpl { * ``` */ err(): Option { - return this.match({ - Ok: () => None, - Err: (e) => Some(e), - }); + return this.#kind ? None : Some(this.#val as E); } /** @@ -169,10 +153,7 @@ export class ResultImpl { * ``` */ map(f: (value: T) => U): Result { - return this.match({ - Ok: (t) => Ok(f(t)), - Err: (e) => Err(e), - }); + return this.#kind ? Ok(f(this.#val as T)) : Err(this.#val as E); } /** @@ -188,10 +169,9 @@ export class ResultImpl { */ mapAsync(f: (value: T) => Promise): AsyncResult { return new AsyncResult( - this.matchAsync({ - Ok: (t) => f(t).then((v) => Ok(v)), - Err: (e) => Promise.resolve(Err(e)), - }), + this.#kind + ? f(this.#val as T).then((v) => Ok(v)) + : Promise.resolve(Err(this.#val as E)), ); } @@ -209,17 +189,11 @@ export class ResultImpl { * ``` */ mapOr(defaultValue: A, f: (value: T) => B): A | B { - return this.match({ - Ok: (t) => f(t), - Err: () => defaultValue, - }); + return this.#kind ? f(this.#val as T) : defaultValue; } mapOrAsync(defaultValue: A, f: (value: T) => Promise): Promise { - return this.matchAsync({ - Ok: (t) => f(t), - Err: () => Promise.resolve(defaultValue), - }); + return this.#kind ? f(this.#val as T) : Promise.resolve(defaultValue); } /** @@ -238,20 +212,14 @@ export class ResultImpl { * ``` */ mapOrElse(defaultValue: (error: E) => A, f: (value: T) => B): A | B { - return this.match({ - Ok: (t) => f(t), - Err: (e) => defaultValue(e), - }); + return this.#kind ? f(this.#val as T) : defaultValue(this.#val as E); } mapOrElseAsync( defaultValue: (error: E) => Promise, f: (value: T) => Promise, ): Promise { - return this.matchAsync({ - Ok: (t) => f(t), - Err: (e) => defaultValue(e), - }); + return this.#kind ? f(this.#val as T) : defaultValue(this.#val as E); } /** @@ -266,10 +234,7 @@ export class ResultImpl { * ``` */ mapErr(f: (error: E) => F): Result { - return this.match({ - Ok: (t) => Ok(t), - Err: (e) => Err(f(e)), - }); + return this.#kind ? Ok(this.#val as T) : Err(f(this.#val as E)); } /** @@ -285,10 +250,9 @@ export class ResultImpl { */ mapErrAsync(f: (error: E) => Promise): AsyncResult { return new AsyncResult( - this.matchAsync({ - Ok: (t) => Promise.resolve(Ok(t)), - Err: (e) => f(e).then((v) => Err(v)), - }), + this.#kind + ? Promise.resolve(Ok(this.#val as T)) + : f(this.#val as E).then((v) => Err(v)), ); } @@ -303,13 +267,10 @@ export class ResultImpl { * ``` */ inspect(f: (value: T) => void): this { - return this.match({ - Ok: (t) => { - f(t); - return this; - }, - Err: () => this, - }); + if (this.#kind) { + f(this.#val as T); + } + return this; } /** @@ -342,13 +303,10 @@ export class ResultImpl { * ``` */ inspectErr(f: (error: E) => void): this { - return this.match({ - Ok: () => this, - Err: (e) => { - f(e); - return this; - }, - }); + if (!this.#kind) { + f(this.#val as E); + } + return this; } /** @@ -363,10 +321,9 @@ export class ResultImpl { */ inspectErrAsync(f: (error: E) => Promise): AsyncResult { return new AsyncResult( - this.matchAsync({ - Ok: (t) => Promise.resolve(Ok(t)), - Err: (e) => f(e).then(() => Err(e)), - }), + this.#kind + ? Promise.resolve(Ok(this.#val as T)) + : f(this.#val as E).then(() => Err(this.#val as E)), ); } @@ -383,10 +340,7 @@ export class ResultImpl { * ``` */ expect(message: string): T { - return this.match({ - Ok: (v) => v, - Err: () => this.unwrapFailed(message), - }); + return this.#kind ? (this.#val as T) : this.unwrapFailed(message); } /** @@ -418,10 +372,7 @@ export class ResultImpl { * ``` */ expectErr(message: string): E { - return this.match({ - Ok: () => this.unwrapFailed(message), - Err: (e) => e, - }); + return this.#kind ? this.unwrapFailed(message) : (this.#val as E); } /** @@ -464,10 +415,7 @@ export class ResultImpl { * ``` */ and(other: Result): Result { - return this.match({ - Ok: () => other, - Err: (e) => Err(e), - }); + return (this.#kind ? other : this) as Result; } /** @@ -484,10 +432,7 @@ export class ResultImpl { * ``` */ andThen(f: (value: T) => Result): Result { - return this.match({ - Ok: (t) => f(t), - Err: (e) => Err(e), - }); + return (this.#kind ? f(this.#val as T) : this) as Result; } /** @@ -507,10 +452,9 @@ export class ResultImpl { f: (value: T) => AsyncResult | Promise>, ): AsyncResult { return new AsyncResult( - this.matchAsync({ - Ok: (t) => f(t) as Promise>, - Err: (e) => Promise.resolve(Err(e)), - }) as Promise>, + (this.#kind + ? f(this.#val as T) + : Promise.resolve(this as unknown as Err)) as Promise>, ); } @@ -527,10 +471,7 @@ export class ResultImpl { * ``` */ or(other: Result): Result { - return this.match({ - Ok: (t) => Ok(t), - Err: () => other, - }); + return (this.#kind ? this : other) as Result; } /** @@ -547,10 +488,7 @@ export class ResultImpl { * ``` */ orElse(f: (error: E) => Result): Result { - return this.match({ - Ok: (t) => Ok(t), - Err: (e) => f(e), - }); + return (this.#kind ? this : f(this.#val as E)) as Result; } /** @@ -570,10 +508,9 @@ export class ResultImpl { f: (error: E) => AsyncResult | Promise>, ): AsyncResult { return new AsyncResult( - this.matchAsync({ - Ok: (t) => Promise.resolve(Ok(t)), - Err: (e) => f(e) as Promise>, - }) as Promise>, + (this.#kind + ? Promise.resolve(this as unknown as Ok) + : f(this.#val as E)) as Promise>, ); } @@ -591,10 +528,7 @@ export class ResultImpl { * ``` */ unwrapOr(defaultValue: U): T | U { - return this.match({ - Ok: (t) => t, - Err: () => defaultValue, - }); + return this.#kind ? (this.#val as T) : defaultValue; } /** @@ -611,17 +545,11 @@ export class ResultImpl { * ``` */ unwrapOrElse(defaultValue: (error: E) => U): T | U { - return this.match({ - Ok: (t) => t, - Err: (e) => defaultValue(e), - }); + return this.#kind ? (this.#val as T) : defaultValue(this.#val as E); } unwrapOrAsync(defaultValue: (error: E) => Promise): Promise { - return this.matchAsync({ - Ok: (t) => Promise.resolve(t), - Err: (e) => defaultValue(e), - }); + return this.#kind ? Promise.resolve(this.#val as T) : defaultValue(this.#val as E); } /** @@ -641,31 +569,23 @@ export class ResultImpl { * ``` */ flatten(this: Result, E>): Result { - return this.match({ - Ok: (t) => t, - Err: (e) => Err(e), - }) as Result; + return this.#kind ? (this.#val as Result) : Err(this.#val as E); } toObject(): {isOk: true; value: T} | {isOk: false; error: E} { - return this.match({ - Ok: (value) => ({isOk: true, value}), - Err: (error) => ({isOk: false, error}), - }); + return this.#kind + ? {isOk: true, value: this.#val as T} + : {isOk: false, error: this.#val as E}; } toJSON(): {meta: "Ok"; value: T} | {meta: "Err"; error: E} { - return this.match({ - Ok: (value) => ({meta: "Ok", value}), - Err: (error) => ({meta: "Err", error}), - }); + return this.#kind + ? {meta: "Ok", value: this.#val as T} + : {meta: "Err", error: this.#val as E}; } toString(): `Ok(${string})` | `Err(${string})` { - return this.match({ - Ok: (value) => `Ok(${String(value)})` as const, - Err: (error) => `Err(${String(error)})` as const, - }); + return this.#kind ? `Ok(${this.#val})` : `Err(${this.#val})`; } [inspectSymbol](): ReturnType["toString"]> { From 233b531c7d1d29663db6377eeb4eaf7c3448ec28 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 20:25:04 +0200 Subject: [PATCH 22/23] Remove unused symbols --- src/symbols.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/symbols.ts b/src/symbols.ts index 32ce50f..08667f3 100644 --- a/src/symbols.ts +++ b/src/symbols.ts @@ -1,3 +1 @@ -export const kind = Symbol("kind"); -export const value = Symbol("value"); export const tag = Symbol("tag"); From c82d091fd48b70c711afdc52678fb85afee3fd81 Mon Sep 17 00:00:00 2001 From: bkiac Date: Mon, 29 Apr 2024 20:26:11 +0200 Subject: [PATCH 23/23] Update inspect symbol --- src/option.ts | 3 +-- src/result.ts | 3 +-- src/symbols.ts | 1 + src/util_internal.ts | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 src/util_internal.ts diff --git a/src/option.ts b/src/option.ts index ea57f52..d0751cc 100644 --- a/src/option.ts +++ b/src/option.ts @@ -2,7 +2,6 @@ import {AsyncOption} from "./async_option"; import {AsyncResult} from "./async_result"; import {Panic} from "./error"; import {Err, Ok, type Result} from "./result"; -import {inspectSymbol} from "./util_internal"; import * as symbols from "./symbols"; export type OptionMatch = { @@ -400,7 +399,7 @@ export class OptionImpl { return this.#kind ? `Some(${String(this.#val)})` : "None"; } - [inspectSymbol](): ReturnType["toString"]> { + [symbols.inspect](): ReturnType["toString"]> { return this.toString(); } } diff --git a/src/result.ts b/src/result.ts index 732f52a..8e3cb21 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,5 +1,4 @@ import {Panic, parseError} from "./error"; -import {inspectSymbol} from "./util_internal"; import {type Option, Some, None} from "./option"; import {AsyncResult} from "./async_result"; import * as symbols from "./symbols"; @@ -588,7 +587,7 @@ export class ResultImpl { return this.#kind ? `Ok(${this.#val})` : `Err(${this.#val})`; } - [inspectSymbol](): ReturnType["toString"]> { + [symbols.inspect](): ReturnType["toString"]> { return this.toString(); } } diff --git a/src/symbols.ts b/src/symbols.ts index 08667f3..05b595c 100644 --- a/src/symbols.ts +++ b/src/symbols.ts @@ -1 +1,2 @@ +export const inspect = Symbol.for("nodejs.util.inspect.custom"); export const tag = Symbol("tag"); diff --git a/src/util_internal.ts b/src/util_internal.ts deleted file mode 100644 index 0018a06..0000000 --- a/src/util_internal.ts +++ /dev/null @@ -1 +0,0 @@ -export const inspectSymbol = Symbol.for("nodejs.util.inspect.custom");