traverse1984 / oxide.ts

Rust's Option<T> and Result<T, E>, implemented for TypeScript.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Expose ResultType under core?

rrichardson opened this issue · comments

This is a minor nit, as I can work around this by using import { ResultType } from "oxide.ts/dist/result"

I use ResultType, because, unfortunately, I have to use instanceof for type inspection, e.g.

      if (result instanceof Result && result.isErr()) {
        const e = result.unwrapErr();
        return handleErrorResponse(e, res);
      }

typescript thinks that Result is {} but instanceof ResultType works.

If you know another way around this, I'm all ears.

I should also mention that using Result.is(result) in this case results in an error later on, because it doesn't know the type of unwrapErr()

Maybe the fix is to make a version of is() that is typed? is this possible?

Result is a function and not the class constructor, and the ResultType is ideally an implementation detail and not part of the toolkit.

I'm not entirely sure where you are losing type information. If you already have a typed Result<T, E> then why do you need to check with instanceof? If you don't have that, then I don't think you are in a position to infer it.

It would help to have a better idea of the problem and your current workaround that is solving it.

Adding type parameters to is to inform the type guard is equivalent to doing x as Result<_, _> and I prefer the latter as it's at least clear you are telling the compiler something.

Or am I missing something?

I don't have the type information. This is in a middleware function which unwraps and wraps API results. Unfortunately, not everything yet returns Result :(

If I use the instance of and isErr() then TS pretty much just assumes that the result is still any

      if (result instanceof Result && result.isErr()) {
        const e = result.unwrapErr();
        return handleErrorResponse(e,  res);
      }
      
      where   handleErrorResponse = <E extends Error>(e: E, res: Response): void 

But if I use Result.is() then it narrows the type into Result<unknown, unknown> . Apparently I'm not allowed to pass unknown into handleErrorResponse

The deal with TypeScript is that you won't be able to infer the correct types if you don't know them in advance or have some kind of type-guard. The Result.is() method asserts that you are working with a ResultType but does nothing else.

Additionally, the E is not necessarily an Error or any variant of it, rather it's just the value you that you choose to use to represent an Error. Do you know for certain that the e in your script above is unquestionably an Error if that branch of code executes? If so, you can just tell the compiler that it is. It's not a perfect solution, but it's like a stop-gap until the rest of your codebase is typed:

if (Result.is(result) && result.isErr()) {
   const e = result.unwrapErr() as Error;
   return handleErrorResponse(e, res);
}

You could also make your own type guard function, which checks the Result type and provides appropriate typing. This is the solution I would be most likely to use.

function isResultError(val: unknown): val is Result<unknown, Error> {
   return Result.is(val) && val.isErr() && val.unwrapUnchecked() instanceof Error;
}

if (isResultError(result)) {
   const e = result.unwrapErr();
   return handleErrorResponse(e, res); // Should be typed now
}

To follow on from the issue raised, My assumption is that you are hoping for something like this:

if (Result.is<string, Error>(val)) {
   const e = val.unwrapErr(); // Compiler knows val is Result<string, Error>
}

Such a function might be possible to make, but the inner workings would not be able to use the type parameters at runtime and would thus essentially be requiring that the developer enters the correct types (and thus, defeats the point of TypeScript). The function would just be this:

function isResult<T, E>(val: unknown): val is Result<T, E> {
   return Result.is(val);
}

So, I'm happy to leave this open for a day or two for if you have any follow-ups but I think this issue will be closed as the function isn't really possible to implement in a useful way,.

That's fine by me. Thanks for the explanation.