【问题标题】:Mapping an array of Eithers to an Either of an array of values将 Eithers 数组映射到值数组中的 Either
【发布时间】:2020-03-26 13:54:51
【问题描述】:

我有一个 Either 类型,用于表示 FailureSuccess 值,我想编写一个函数,它接受任意数量的 Eithers 并返回第一个 Failure在序列中,或者一个新的Success,其值是未包装的Success 值的元组。行为类似于Promise.all()

我想我已经使用条件类型和映射类型混合使用了这种类型,但是我在实现实际功能来完成这项工作时遇到了麻烦。我不确定如何将输入的类型推断为任意长度的Eithers 元组。我仍然是一个 TypeScript 菜鸟,所以请耐心等待,如果有更好/更惯用的做某事的方式,请告诉我。

这是我目前所拥有的(以及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;
  }
}

非常感谢任何帮助。

【问题讨论】:

    标签: typescript tuples either mapped-types conditional-types


    【解决方案1】:

    typescript 3.1 中引入了丰富的元组? 3.2?我不记得了;但是现在有了它们,所有函数的参数都可以表示为元组(元组已经展开,可选参数就像函数一样)。

    您可以像这样以元组的形式获取输入。

    如果您提供有关返回类型的更多信息,我也可以提供帮助。

    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)
    
    

    更新后它看起来像这样,我留下了一个“我不知道这里有什么字符串,关于我在哪里遗漏了一些东西,也许你可以填写空白,但这就是它。

    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]>
    

    认为这是对的。最终编辑。

    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]>
    

    【讨论】:

    • Either 类型必须用两个变量声明:一个用于Failure 路径,一个用于Success 路径,例如。 Either&lt;string, number&gt;: type Either&lt;E, T&gt; = Failure&lt;E, T&gt; | Success&lt;E, T&gt;; 我希望函数的行为如下: declare const EitherOne: Either&lt;string, boolean&gt;; declare const EitherTwo: Either&lt;number, string&gt;; declare const EItherThree: Either&lt;boolean, number&gt;; const combined = sequence(EitherOne, EitherTwo, EitherThree); // Failure&lt;string | number | boolean&gt; | Success&lt;[boolean, string, number]&gt;;
    • 为什么失败有两个类型参数?并且是以太左侧的类型总是失败的情况。快完成了
    • 添加了一条更新的评论,我不明白哪种类型的成功/失败,因为双方都有字符串编号布尔值。 (即所有的例子 EitherOne、two 等都有字符串 | 数字 | boolean 在每一边)因此我无法弄清楚哪些类型应该放在哪里,但也许你可以编辑实现并足够容易地移动它们
    • SuccessFailure 类采用两个类型参数,因为它们需要知道另一个类型的类型才能使多态方法正常工作。但是Failure 实例将始终具有E 泛型的值,而Success 实例将具有T 泛型的值。其他的纯粹用于类型推断。
    • 添加了最后的编辑让我知道我是否有任何问题
    猜你喜欢
    • 1970-01-01
    • 2020-07-22
    • 2016-01-13
    • 1970-01-01
    • 1970-01-01
    • 2021-04-20
    • 2017-05-26
    • 2019-07-21
    • 1970-01-01
    相关资源
    最近更新 更多