I have an Either
type that is used to represent Failure
and Success
values, and I would like to write a function that takes an arbitrary number of Either
s and returns either the first Failure
in the sequence, or a new Success
with a value that is a tuple of the unwrapped Success
values. The behavior is similar to Promise.all()
.
I think I've got this working with types using a mix of conditional and mapped types, but I'm having trouble implementing the actual function to do the work. I'm not sure how to infer the types of the input as a tuple of Either
s of an arbitrary length. I'm still a bit of a TypeScript noob so bear with me and let me know if there's a better / more idiomatic way of doing something.
Here's what I have so far (and a TypeScript Playground Link):
type Either<E, T> = Failure<E, T> | Success<E, T>;
type Union<T> =
T extends Array<infer U> ? U :
T extends { [index: string]: infer U } ? U :
never;
type FailureType<T> = T extends Failure<infer U, infer V> ? U : never;
type SuccessType<T> = T extends Success<infer U, infer V> ? V : never;
type FailureTypesUnion<T> = Union<{ [K in keyof T]: FailureType<T[K]> }>;
type SuccessTypesAggregate<T> = { [K in keyof T]: SuccessType<T[K]> };
// This type represents the result type that `Result.sequence` should have.
type EitherTypesSequence<T> = Either<FailureTypesUnion<T>, SuccessTypesAggregate<T>>;
// this gives the desired type:
// `type Test = Failure<string | number | boolean> | Success<[boolean, string, number]>`
type Test = EitherTypesSequence<
[
Either<string, boolean>,
Either<number, string>,
Either<boolean, number>
]
>
class Result {
public static Ok<E, T>(value: T): Either<E, T> {
return new Success(value);
}
public static Err<E, T>(error: E): Either<E, T> {
return new Failure(error);
}
public static isSuccess<E, T>(target: Either<E, T>): target is Success<E, T> {
return target instanceof Success;
}
public static isFailure<E, T>(target: Either<E, T>): target is Failure<E, T> {
return target instanceof Failure;
}
public static sequence(
// ...args: ???
// How do I constrain the input to this function to be an array of Eithers?
// How to I infer the types of the Eithers as a tuple?
) {
// How wold I implement an angorithm here that plays nice with the types above?
}
}
// A Success class that represents the `right` path
class Success<E, T> {
private _value: T;
constructor(value: T) {
this._value = value;
}
public map<U>(fn: (v: T) => U): Either<E, U> {
return new Success(fn(this._value));
}
public chain<U, V>(fn: (v: T) => Either<U, V>): Either<E | U, V> {
return fn(this._value);
}
public either<U, V>(onFailure: (v: E) => U, onSuccess: (v: T) => V): U | V {
return onSuccess(this._value);
}
public get(): T {
return this._value;
}
}
// A Failure class that represents the `left` path
class Failure<E, T> {
private _value: E;
constructor(error: E) {
this._value = error;
}
public map<U>(fn: (v: T) => U): Either<E, U> {
return new Failure(this._value);
}
public chain<U, V>(fn: (v: T) => Either<U, V>): Either<E | U, V> {
return new Failure(this._value);
}
public either<U, V>(onFailure: (v: E) => U, onSuccess: (v: T) => V): U | V {
return onFailure(this._value);
}
public get(): E {
return this._value;
}
}
Any help with this is greatly appreciated.