输入功能组成系统

时间:2018-03-28 07:14:36

标签: javascript typescript

如何为compose添加类型?
问题基本上归结为为此编写类型:

const compose = (...funcs) => x => funcs.reduce((acc, func) => func(acc), x);

并使用它:

compose(x => x + 1, x => x * 2)(3);

在此示例中,compose的类型推断为:

const compose: (...funcs: any[]) => (x: any) => any

这只是一堆any ...

是否有一种向compose添加类型的好方法?

3 个答案:

答案 0 :(得分:3)

虽然无法输入这样的函数来接受任意数量的函数,但我们可以编写一个版本compose,其重载次数高达给定数量的函数。对于大多数实际应用,它应该足以允许最多5个函数的组合,并且我们总是可以在需要时添加更多的重载。

我还添加了一个重载,当类型没有不同时,我们只有一个类型在整个compose中被处理。如果参数类型和返回类型相同,这允许传入任意数量的函数。

function compose<A, B, C, D, E, F>(fn: (p: A) => B, fn2: (p: B) => C, fn3: (p: C) => D, fn4: (p: D) => E, fn5: (p: E) => F): (p: A) => F
function compose<A, B, C, D, E>(fn: (p: A) => B, fn2: (p: B) => C, fn3: (p: C) => D, fn4: (p: D) => E): (p: A) => E
function compose<A, B, C, D>(fn: (p: A) => B, fn2: (p: B) => C, fn3: (p: C) => D): (p: A) => D
function compose<A, B, C>(fn: (p: A) => B, fn2: (p: B) => C): (p: A) => C
function compose<T>(...funcs: Array<(p: T) => T>) : (p: T) => T // Special case of parameter and return having the same type
function compose(...funcs: Array<(p: any) => any>) {
    return (x: any) => funcs.reduce((acc, func) => func(acc), x)
};

// Usage
// First argument type must be specified, rest are inferred correctly
let fn = compose((x : number) => x + 1, x => x * 2); // fn will be (p: number) => number
let d = fn(3); // d will be number 

let fn2 = compose((x : number) => x + 1, x => x * 2, x=> x.toString(), x => x.toLowerCase()); // fn will be (p: number) => string and we get intelisense and type safety in each function 

// Any number of functions but a single type
let fnMany = compose((x : number) => x + 1, x => x * 2, x => x * 2, x => x * 2, x => x * 2, x => x * 2, x => x * 2); 

答案 1 :(得分:2)

你可以介绍一些类型信息,我通常建议采用迭代方法,例如这个早期版本不是很灵活 - 但最终类型是正确的:

interface Composer {
    (...funcs: { (arg: any): any }[]): (arg: any) => number;
}

const compose: Composer = (...funcs) => x => funcs.reduce((acc, func) => func(acc), x);

// result: number
const result = compose(x => x + 1, x => x * 2)(3);

您可以针对您的特定用途进行迭代。例如,最终类型可能需要是通用类型,因此您可以将Composer升级为Composer<T>。或者你可能想让中间类型更不宽容(它们目前可以是任何类型,但你也可以改进它)。

最终,您需要决定在何处修复类型,或引入动态类型,或者完全停止关注Composer类型内的类型。

答案 2 :(得分:1)

可能不是。

在TypeScript之外思考片刻并考虑用Hindley-Milner推理的ML型语言,两种函数的组合类型将是

compose2: (a -> b) -> (b -> c) -> a -> c
compose2 f g x = g(f x)

如果您有三个功能,则类型为

compose3: (a->b) -> (b->c) -> (c->d) -> a -> d

继续前进

compose4: (a->b) -> (b->c) -> (c->d) -> (d->e) -> a -> e

你希望编写一个compose函数,它接受任意数量的函数(通过rest参数),然后通过reduce进行合成。这非常酷,但你真的无法锁定所有中间函数的所有中间类型,因为一般来说它们都非常非常不同。

处理您希望编写任意数量的函数的灵活性的唯一方法是使(至少)所有中间函数都具有类型

any -> any

即使TypeScript的类型变量与ML系列中的那些语言一样复杂,但是对于任意数量的函数的需求使得无法避免any s(除非你想将所有函数限制为a->a,或您特定的TypeScript示例number=>number)。