打字稿递归函数组成

时间:2018-11-06 13:46:02

标签: javascript typescript recursion function-composition spread-syntax

我想创建一个功能链,该功能链是管道/流程/组成功能的输入。

是否可以像通常那样处理,而无需将字面量扩展到选定的深度? See lodash's flow

我想实现链中数据流的类型检查。  -函数的参数是前一个的结果  -第一个参数是模板参数  -最后返回的是模板参数

type Chain<In, Out, Tmp1 = any, Tmp2 = any> = [] | [(arg: In) => Out] | [(arg: In) => Tmp1, (i: Tmp1) => Tmp2, ...Chain<Tmp2, Out>];

这个想法在草案中。

这会产生以下错误:

  1. Type alias 'Chain' circularly references itself.(理解原因,不知道如何解决)
  2. A rest element type must be an array type.(普通元组可能不可用价差)
  3. Type 'Chain' is not generic.(甚至不知道为什么出现此错误)

在打字稿中是否可以定义Chain?如果是这样,请附上摘要。

(在最新的tsc 3.1.6上进行了测试)

1 个答案:

答案 0 :(得分:6)

除非在某些情况下,否则实际上不支持圆形类型别名。我想我不会备份并以TypeScript友好的方式表示您在此处编写的特定类型,而是备份并解释您的问题:我们如何键入类似flow()的函数,它的参数是可变数量的单参数函数,其中每个单参数函数的返回类型都是下一个单参数函数的参数类型,例如链...,并且返回表示折叠的单参数函数链条?

我有一些我认为可行的方法,但是使用很多conditional typestuple spreadsmapped tuples却很复杂。在这里:

type Lookup<T, K extends keyof any, Else=never> = K extends keyof T ? T[K] : Else
type Tail<T extends any[]> = 
  ((...t: T) => void) extends ((x: any, ...u: infer U) => void) ? U : never;
type Func1 = (arg: any) => any;
type ArgType<F, Else=never> = F extends (arg: infer A) => any ? A : Else;
type AsChain<F extends [Func1, ...Func1[]], G extends Func1[]= Tail<F>> =
  { [K in keyof F]: (arg: ArgType<F[K]>) => ArgType<Lookup<G, K, any>, any> };
type LastIndexOf<T extends any[]> =
  ((...x: T) => void) extends ((y: any, ...z: infer U) => void)
  ? U['length'] : never

declare function flow<F extends [(arg: any) => any, ...Array<(arg: any) => any>]>(
  ...f: F & AsChain<F>
): (arg: ArgType<F[0]>) => ReturnType<F[LastIndexOf<F>]>;

让我们看看它是否有效:

const stringToString = flow(
  (x: string) => x.length, 
  (y: number) => y + "!"
); // okay
const str = stringToString("hey"); // it's a string

const tooFewParams = flow(); // error

const badChain = flow(
  (x: number)=>"string", 
  (y: string)=>false, 
  (z: number)=>"oops"
); // error, boolean not assignable to number

对我很好。


我不确定是否值得进行关于类型定义如何工作的细致研究,但我还可以解释如何使用它们:

  • Lookup<T, K, Else>尝试返回T[K](如果可以),否则返回Else。因此Lookup<{a: string}, "a", number>string,而Lookup<{a: string}, "b", number>number

  • Tail<T>采用元组类型T,并返回删除了第一个元素的元组。因此Tail<["a","b","c"]>["b","c"]

  • Func1只是单参数函数的类型。

  • ArgType<F, Else>如果是单参数函数,则返回F的参数类型,否则返回Else。因此ArgType<(x: string)=>number, boolean>string,而ArgType<123, boolean>boolean

  • AsChain<F>取一元参数函数的元组,并尝试将其变成一个链,方法是将F中每个函数的返回类型替换为下一个函数的参数类型(最后一个使用any)。如果AsChain<F>F兼容,那么一切都很好。如果AsChain<F>F不兼容,则F不是一个好的链。因此,AsChain<[(x: string)=>number, (y:number)=>boolean]>[(x: string)=>number, (y: number)=>any],这很好。但是AsChain<[(x: string)=>number, (y: string)=>boolean]>[(x: string)=>string, (y: string)=>any],不好。

  • 最后,LastIndexOf<T>取一个元组并返回最后一个索引,我们需要用它表示flow()的返回类型。 LastIndexOf<["a","b","c"]>2


好的,希望能有所帮助;祝你好运!