0

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 Eithers 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 Eithers 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.

jmartinezmaes
  • 359
  • 1
  • 8

1 Answers1

0

Rich tuples came in in typescript 3.1? 3.2? i can't remember; but now with them all functions arguments can be represented as tuples (tuples have spread, optional parameters just like functions do).

You can get the input as a tuple doing something like this.

if you give me more information around the return type i can help with that aswell.

interface Either<A> {
    type: A
}

const sequence = <T extends Either<any>[]>(...args: T): T => {
    // don't know whats supposed to be here till you tell me more.
    return "" as any; // placeholder;
}


declare const EitherNumber: Either<number>;
declare const EitherString: Either<string>;
declare const EitherDate: Either<Date>;
const whatType = sequence(EitherNumber, EitherString, EitherDate) // [EitherNumber, EitherString, EitherDate] (tuple)

Updated it looks like this iv'e left a "I dont know what goes here string as to where im missing something maybe you can fill in the blanks but this is just about it.

type Success<E, T> = {e: E, t: T}; // doesn't matter but i don't understand two type parameters.
type Failure<E, T> = {e: E, t: T}; // doesn't matter but i dont understand two type parameters
type Either<E, T> = Failure<E, T> | Success<E, T>;
declare const EitherOne: Either<string, boolean>;
declare const EitherTwo: Either<number, string>;
declare const EitherThree: Either<boolean, number>;


type MapRight<T extends Either<any, any>[]> = {
    [K in keyof T]: T[K] extends Either<any, infer Right> ? Right : never;
}
type GetLeft<T extends Either<any, any>> = [T] extends [Either<infer Left, infer Right>] ? Failure<Left, Right> : never;
const sequence = <T extends Either<any, any>[]>(...args: T): GetLeft<T[number]>  | Success<"Dont Know what goes here", MapRight<T>>=> {
    // don't know whats supposed to be here till you tell me more.
    return "" as any; // placeholder;
}

const combined = sequence(EitherOne, EitherTwo, EitherThree); // Failure<string | number | boolean> | Success<"I dont know what goes here", [boolean, string, number]>

Think this is right. final edit.

type Success<E, T> = {e: E, t: T}; // doesn't matter but i don't understand two type parameters.
type Failure<E, T> = {e: E, t: T}; // doesn't matter but i dont understand two type parameters
type Either<E, T> = Failure<E, T> | Success<E, T>;
declare const EitherOne: Either<string, boolean>;
declare const EitherTwo: Either<number, string>;
declare const EitherThree: Either<boolean, number>;


type MapRight<T extends Either<any, any>[]> = {
    [K in keyof T]: T[K] extends Either<any, infer Right> ? Right : never;
}
type GetRight<T extends Either<any, any>> = T extends Either<any, infer Right> ? Right : never;
type GetLeft<T extends Either<any, any>> = T extends Either<infer Left, any> ? Left : never;
const sequence = <T extends Either<any, any>[]>
    (...args: T): Failure<GetLeft<T[number]>, GetRight<T[number]>> | Success<GetLeft<T[number]>, MapRight<T>> => {
    // don't know whats supposed to be here till you tell me more.
    return "" as any; // placeholder;
}

const combined = sequence(EitherOne, EitherTwo, EitherThree); // Failure<string | number | boolean> | Success<string | number | boolean, [boolean, string, number]>
Shanon Jackson
  • 5,873
  • 1
  • 19
  • 39
  • The `Either` type must be declared with two variables: one for the `Failure` path and one for the `Success` path, ex. `Either`: `type Either = Failure | Success;` I would want the function to behave like this: `declare const EitherOne: Either;` `declare const EitherTwo: Either;` `declare const EItherThree: Either;` `const combined = sequence(EitherOne, EitherTwo, EitherThree);` `// Failure | Success<[boolean, string, number]>;` – jmartinezmaes Dec 02 '19 at 00:21
  • Why does failure have two type parameters? and is the type on the left of ether always the failure case. Almost done – Shanon Jackson Dec 02 '19 at 00:33
  • Added a updated comment, i don't understand which types go to which success/failure because both sides of each either have string number boolean. (I.E all the examples EitherOne, two etc all have string | number | boolean on each side) Therefore i couldn't figure out which types should go where but maybe you can edit the implementation and move them easy enough – Shanon Jackson Dec 02 '19 at 00:37
  • The `Success` and `Failure` classes take two type parameters because they need to know about the other's type in order for the polymorphic methods to work properly. But a `Failure` instance will always have the value of the `E` generic, and a `Success` instance will have the value of the `T` generic. The others are purely for type inference. – jmartinezmaes Dec 02 '19 at 00:42
  • added a final edit let me know if i got anything wrong – Shanon Jackson Dec 02 '19 at 01:24