Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PROPOSAL: Safe assignment using neverthrow #614

Open
vekexasia opened this issue Nov 14, 2024 · 0 comments
Open

PROPOSAL: Safe assignment using neverthrow #614

vekexasia opened this issue Nov 14, 2024 · 0 comments

Comments

@vekexasia
Copy link

Hello all,

I started using neverthrow and enjoying it big time. Unfortunately all the attempts to have clean code when handling errors are failing big time.

I tried with safeTry but I instantly hit #604. I also tried other approaches like chaining andThen as per this comment in #301. My brain forbids me have that (although i have found brief relief in using it)..

Following the safe assignment proposal I figured that the best way would be to inherit from Go error handling (but with error first as explained in the repo).

I then created the following snippet of code that would let me call .safeRet() to produce a tuple containing the Error and the Result.

declare module "neverthrow" {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  export class Err<T, E> {
    public safeRet(): E extends never ? [E, T] : [E, undefined];
  }
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  export class Ok<T, E> {
    public safeRet(): T extends never ? [E, T] : [undefined, T];
  }
}
Ok.prototype.safeRet = function () {
  return [undefined, this.value];
};
Err.prototype.safeRet = function () {
  return [this.error, undefined];
};

What happens is the following:

const [e, s] = ok("string").safeRet();
// e: undefined, s: string
const [e2, s2] = err("error").safeRet();
// e2: "error", s2: undefined

function a(x: Result<number, string>): Result<number, string> {
  const [e3, s3] = x.safeRet();
  // e3: string | undefined, s3: number | undefined
  // this forces you to check for e3
  if (typeof e3 !== "undefined") {
    return err(e3);
  }
  // s3 is now number
  return ok(s3);
}

NOTE: when using ok we can directly use the result as typescript properly infer that s is always set

If by any case we change from Result<*, never> to an Error, then automatically typescript infers the result to be T | undefined forcing you to either use the Non-null assertion operator (!) or check for error like in the a function above.

just to give you perspective of what i mean this is what a chain of neverthrow "compatible" functions might look like.

declare function randomNumber(): Ok<number, never>;
declare function safeDivision(
  a: number,
  b: number,
): Result<number, "cannot divide by zero">;
declare function errIfAbove1(
  what: number,
): Result<undefined, "CannotBe>1" | undefined>;

function randomDivision(): Result<
  number,
  "cannot divide by zero" | "CannotBe>1"
> {
  const [, firstOperand] = randomNumber().safeRet();
  const [, secondOperand] = randomNumber().safeRet();

  const [divErr, divRes] = safeDivision(firstOperand, secondOperand).safeRet();

  if (typeof divErr !== "undefined") {
    return err(divErr);
  }

  const [above1Err] = errIfAbove1(divRes).safeRet();
  if (typeof above1Err !== "undefined") {
    return err(above1Err);
  }

  return ok(divRes);
}

For now i am monkey patching like shown above. I was thinking of writing a small library but I thought I should first stop by here and see if maybe this could come handy and get included in a future neverthrow version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant