#Problems with type inference

1 messages · Page 1 of 1 (latest)

queen ether
#

I want the error message to be:

error: TS2322 [ERROR]: Type '{}' is not assignable to type 'Result<string[], Error>'.
r = {};

But I got:

error: TS2322 [ERROR]: Type '{}' is not assignable to type 'Result<string[], unknown>'.
r = {};

I want the types to stay generic and I don't want to invoke the function with explicit types. The Failure type doesn't need to be an Error.
We must figure out if the callback, which was passed as argument, returns a Failure. And if it doesn't return a Failure we use the type from the passed $result argument.
Can I fix it?

#
export type Success<V> = {
  value: V;
  kind: "success";
};
export type Failure<E> = {
  error: E;
  kind: "failure";
};
export type Result<V, E> = Success<V> | Failure<E>;

/**
 * Create a success result.
 */
export function succeed<V>(value: V): Success<V> {
  return {
    value,
    kind: "success",
  };
}

/**
 * Create a failure result.
 */
export function fail<E>(error: E): Failure<E> {
  return {
    error,
    kind: "failure",
  };
}


/**
 * Invert an array of Result objects to a single Result of an array.
 * If all Results are successes, it returns a Success containing an
 * array of values.
 * If any Result is a failure, it returns the first Failure.
 * @example
 * const result = await invertResults(succeed("aaa"), succeed("bbb"));
 * console.log(result); // { value: ["aaa", "bbb"], kind: "success" }
 *

 */
export function invertResults<V, E>(
  ...results: (Success<V> | Failure<E>)[]
): Promise<Success<V[]> | Failure<E>> {
  // @ts-ignore for simplicity
  return fold(async (acc: Result<V[], E>, result: Result<V, E>) =>
    await chainResult(async (arr: V[]) =>
      await mapResult(appendTo(arr))(result)
    )(
      acc,
    )
  )(succeed([]))(results);
}

export function chainResult<V, E, N>(
  f: (value: V) => Result<N, E> | Promise<Result<N, E>>,
) {
  return async <F>(
    $result: Result<V, F>,
  ): Promise<Result<N, E>> => {
    const result = await $result;
    switch (result.kind) {
      case "success":
        return await f(result.value) as any;
      case "failure":
        return result as any;
      default:
        throwUnsupportedKind(result);
    }
  };
}


const success = succeed("first");
const failure = fail(new Error("Oops"));
const result = await invertResults(success, failure);
console.log(result);
let r = await chainResult((values: string[]) => {
  const newValues = [...values, "fourth"];
  return newValues.length > 2 ? succeed(newValues) : succeed(newValues);
})(result);
console.log(r);
r = {};
gleaming thicket
#

We can assume E will always be for the Error, no?```ts
export function chainResult<V, N, E extends Error>(

queen ether
#

No, that's the hard part. All type variables are generic. And the type of Failure can be changed inside the callback which chainResult takes as argument. The same goes for the Success type.

gleaming thicket
#

ah, then you might need to pass failure as an argument in either chainResult or in returned function to automagically asseert that it's an error

queen ether
#

I passed a Failure type to invertResults. here and I want this type to be passed through chainResult to the end, if the callback passed to chainResult doesn't return a Failure but only Success.

const success = succeed("first");
const failure = fail(new Error("Oops"));
const result = await invertResults(success, failure);
gleaming thicket
#

Ah ye, this works ```ts
export function chainResult<V, E, N>(
f: (value: V, fail?: E) => Result<N, E> | Promise<Result<N, E>>
) {
// nothing changed here
}

#

It basically looks like it's not inferring from here })(result), so you could pass the failure await chainResult((values: string[], failure)

queen ether
#

I think I need an type operator for this line:

  ): Promise<Result<N, E>> => {

I want the type of E only being returned if E is not unknown, otherwise F shall be returned. Does it somehow work?

gleaming thicket
#

Oh, actually this also fixes it, i think. set return type to Promise<Result<N, F>>

queen ether
#

Thank you! But I think I will go with this solution. Do you like it?

type IfUnknown<E, F> = [unknown] extends [E] ? F : E;
/**
 * Chain a function to the value of a success result.
 */
export function chainResult<V, E, N>(
  f: (value: V) => Result<N, E> | Promise<Result<N, E>>,
) {
  return async <F>(
    $result: Result<V, F>,
  ): Promise<Result<N, IfUnknown<E, F>>> => {
    const result = await $result;
    switch (result.kind) {
      case "success":
        return await f(result.value) as any;
      case "failure":
        return result as any;
      default:
        throwUnsupportedKind(result);
    }
  };
}