【问题标题】:TypeScript variadic tuple type inference issue, for function that runs a sequence of "Result"-returning functionsTypeScript 可变参数元组类型推断问题,用于运行一系列“结果”返回函数的函数
【发布时间】:2021-05-04 13:34:10
【问题描述】:

(TypeScript Playground 中的完整代码:link。)

我有一个 Result<T, E> 类型,用于计算成功并返回 T 类型的值或失败并返回 E 类型的错误。

type Result<T, E> =
    | {ok: true, value: T}
    | {ok: false, value: E};

我想编写一个辅助函数,它可以按顺序运行Result-returning 函数的列表。如果任何函数失败,则停止处理并返回错误。如果全部成功,则返回成功值列表。

以下是专门为长度为 3 的列表实现该功能的方法:

function sequenceFixed<T1, T2, T3, E>(fns: [() => Result<T1, E>, () => Result<T2, E>, () => Result<T3, E>]): Result<[T1, T2, T3], E> {
    const [f1, f2, f3] = fns;
    const r1 = f1();
    if (!r1.ok) return r1;
    const r2 = f2();
    if (!r2.ok) return r2;
    const r3 = f3();
    if (!r3.ok) return r3;
    return {ok: true, value: [r1.value, r2.value, r3.value]};
}

我想使用 TypeScript 4.0 的可变元组类型来使它适用于任何长度的列表。 (函数体本身不需要类型检查,只需要调用站点。)

这是我的两次尝试:

declare function sequenceGeneric1<T extends Array<unknown>, E>(
    fns: Readonly<{[P in keyof T]: () => Result<T[P], E>}>,
): Result<[...T], E>;

declare function sequenceGeneric2<Fns extends Array<unknown>, E>(
    fns: Fns,
): Result<{[P in keyof Fns]: ExtractResultSuccessType<Fns[P]>}, E>;

type ExtractResultSuccessType<Fn> = Fn extends () => Result<infer T, unknown> ? T : unknown;

如果我明确传递类型参数,则正确调用这些函数类型检查,但我不知道如何让 TypeScript 推断类型参数。

declare function f1(): Result<string, string>;
declare function f2(): Result<boolean, string>;
declare function f3(): Result<number, string>;

function checkFixed() {
    return sequenceFixed([f1, f2, f3]);
}
function checkGeneric1() {
    return sequenceGeneric1([f1, f2, f3]);
}

function checkGeneric2() {
    return sequenceGeneric2([f1, f2, f3]);
}

function check() {
    // ok
    const c1: Result<[string, boolean, number], string> = checkFixed();

    // ERROR: Type 'Result<(string | number | boolean)[], unknown>' is not assignable to
    //        type 'Result<[string, boolean, number], string>'.
    const c2: Result<[string, boolean, number], string> = checkGeneric1();
    
    // ERROR: Type 'Result<(string | number | boolean)[], unknown>' is not assignable to
    //        type 'Result<[string, boolean, number], string>'.
    const c3: Result<[string, boolean, number], string> = checkGeneric2();
}

function checkGeneric1WithAnnontation(): Result<[string, boolean, number], string> {
    return sequenceGeneric1<[string, boolean, number], string>([f1, f2, f3]);
}

【问题讨论】:

标签: typescript typescript-generics variadic-tuple-types


【解决方案1】:

在这种情况下,走sequenceGeneric2 的路线肯定会更容易,因为你有一个与传入参数类型直接对应的泛型参数;毕竟,编译器从T 类型的值推断T 类型比编译器从Readonly&lt;{[K in keyof T]: SomeFunction&lt;T[K]&gt;}&gt; 类型的值推断T 类型更直接。

在您的情况下,您使用 Fns 朝着正确的方向前进,但您仍然希望编译器推断出 E,这与您做不到的原因相同t 在sequenceGeneric1 中很好地推断出T 元组。


还有一件事:如果将数组字面量传递给函数,编译器很有可能会为其推断出无序数组类型而不是元组类型。有一些方法可以向编译器提示您希望推断出元组类型。一种方法是使用可变元组表示法……所以你可以写fns: Fns,而不是写fns: readonly [...Fns]。就fns 可以是什么类型而言,这并没有太大区别,但它确实会导致编译器更喜欢元组类型的Fns


所以,继续:让我们只有一个泛型类型参数Fns,然后让编译器在输出中计算T 类型E 类型:

declare function sequenceGeneric<Fns extends Array<() => Result<any, any>>>(
    fns: readonly [...Fns],
): Result<
  { [P in keyof Fns]: ExtractResultSuccessType<Fns[P]> },
  ExtractResultErrorType<Fns[keyof Fns]>
>;

在哪里

type ExtractResultSuccessType<Fn> = Fn extends () => Result<infer T, any> ? T : never;
type ExtractResultErrorType<Fn> = Fn extends () => Result<any, infer E> ? E : never;

我已将推断类型中的unknown 更改为any,因为编译器更容易验证它。 unknown 是一种真正的顶级类型,它有时会以您并不真正想要的方式严格遵守协变/逆变。


让我们看看它是否有效:

function checkGeneric() {
    return sequenceGeneric([f1, f2, f3]);
}
// function checkGeneric(): Result<[string, boolean, number], string>

看起来不错!


Playground link to code

【讨论】:

    猜你喜欢
    • 2020-01-23
    • 1970-01-01
    • 2020-10-31
    • 2018-08-28
    • 2019-01-07
    • 2021-07-18
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    相关资源
    最近更新 更多