使用TypeScript进行部分应用

时间:2018-05-17 20:34:59

标签: typescript

我喜欢使用对象文字作为函数参数,因为它允许我标记参数名称。我认为有一种类型安全的通用方法可以对这些类型的函数进行部分函数应用。假设一个函数接受一个X类型的参数。然后我通过另一个名为" partial"的函数运行该函数。在我提供部分X的情况下,它可以返回一个只需要缺少值的新函数。通过下面的示例最容易看到。这里的好处是我的"部分"函数我可以提供任意数量或参数组合,并获得一个结果函数,清楚地表明仍然需要什么。

function partial<Args, Fixed extends Partial<Args>, Result>(fn: (args: Args) => Result, fixed: Fixed) {
    type Unspecified = { [P in Exclude<keyof Args, keyof Fixed>]: Args[P] };
    const result = (args: Unspecified) => {
        const combined = {};
        Object.assign(combined, fixed, args);
        return fn(combined as Args);
    };
    return result as ({} extends Unspecified ? () => Result : (args: Unspecified) => Result);
}

interface AddThreeNumbersArgs {
    a: number;
    b: number;
    c: number;
    caption: string;
}

function addThreeNumbers(args: AddThreeNumbersArgs) {
    return `${args.caption} ${args.a + args.b + args.c}`;
}

test("fix one number and the caption", () => {
    const f = partial(addThreeNumbers, { b: 10, caption: "The answer is:" });
    const result = f({ a: 1, c: 25 });
    expect(result).toBe("The answer is: 36");
});

这一切都适用于上面的AddThreeNumbers示例。但是当函数参数是泛型时它不起作用 - 见下文 - 我不知道为什么。相反,partial的结果是一个函数,它接受NO参数而不是缺失的部分。任何TypeScript大师都知道为什么吗?

interface ConcatenateArrayArgs<TItem> {
    first: TItem[],
    second: TItem[]
}

function concatenate<T>(args: ConcatenateArrayArgs<T>) {
    return [...args.first, ...args.second];
}

test("concatenate", () => {
    const result = concatenate({ first: [1, 2, 3], second: [4, 5, 6] });
    expect(result).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]));
});

test("fix first array in concatenate to array of numbers", () => {
    const f = partial(concatenate, { first: [1, 2, 3] });
    // expect f to take a { second: [4,5,6] } here but instead
    // f is a function with no arguments
});

1 个答案:

答案 0 :(得分:2)

我认为你在Microsoft/TypeScript#9366遇到了这个问题; TypeScript对涉及higher-rank function types的类型推断没有很大的支持。

我能想到的唯一解决方法是让你在某处明确指定类型。例如,您可以将回调函数的等级降低到非泛型函数,如:

const f = partial(
  concatenate as (x: ConcatenateArrayArgs<number>)=>number, 
  { first: [1, 2, 3] }
);

然后推理按预期工作。或者您可以在调用partial()时明确指定类型参数,以便 it 基本上是非泛型的,并且没有推断编译器出错:

const r = partial<
  ConcatenateArrayArgs<number>,
  { first: number[] },
  number[]
>(concatenate, { first: [1, 2, 3] });

这些都有效,但并不是特别令人满意。希望至少能指出你正确的方向。祝你好运!