打字稿递归函数组成

Jar*_*lík 2 javascript recursion function-composition typescript spread-syntax

我想创建一个函数链,它将是一个管道/流/组合函数的输入.

如果没有将类型的字面扩展到选定的深度,这是否可能,这通常是处理?见lodash的流程.

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

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

这个想法在草案中.

然而,这会产生以下错误:

  1. Type alias 'Chain' circularly references itself. (明白为什么,不知道怎么解决)
  2. A rest element type must be an array type. (可能传播不适用于通用元组)
  3. Type 'Chain' is not generic. (甚至不理解为什么这个错误甚至在这里)

这是ChainTypecript 中可能的定义吗?如果是这样,请附上一个片段.

(测试最新的tsc 3.1.6)

jca*_*alz 8

除某些情况外,实际上不支持循环类别别名.我没有尝试用TypeScript友好的方式来表示你在那里写的特定类型,我想我会将你的问题备份并解释为:我们如何键入一个类似flow()函数,它将变量作为参数单参数函数,其中每个单参数函数返回类型是下一个参数函数的参数类型,如链......并返回表示折叠链的单参数函数?

我有一些我相信有用的东西,但它很复杂,使用了很多条件类型,元组扩展映射元组.这里是:

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>]>;
Run Code Online (Sandbox Code Playgroud)

让我们看看它是否有效:

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
Run Code Online (Sandbox Code Playgroud)

在我看来很好.


我不确定是否值得仔细阅读有关类型定义如何工作的详细信息,但我不妨解释如何使用它们:

  • Lookup<T, K, Else>尝试返回,T[K]如果可以,否则返回Else.所以Lookup<{a: string}, "a", number>IS string,和Lookup<{a: string}, "b", number>number.

  • Tail<T>采用元组类型T并返回第一个元素被删除的元组.所以Tail<["a","b","c"]>["b","c"].

  • Func1 只是一个参数函数的类型.

  • ArgType<F, Else>返回参数类型Fif if是单参数函数,Else否则返回.所以ArgType<(x: string)=>number, boolean>IS 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.


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

  • 对于另一个问题(作为该问题的重复项关闭:-),我找到了这个递归解决方案:https://tsplay.dev/wQ5r1w 错误消息更准确一些,并且代码相对容易理解。如果有人仍然对这个主题感兴趣,我可以将其添加为问题的第二个答案。 (4认同)
  • 你在这里所做的令人印象深刻。它确实有效。唯一的小挫折是错误似乎总是在第一个函数上,无论链在哪里中断(尽管消息是正确的),这似乎是解决方案的固有问题。您正在验证 AsChain 中的链,而不是定义类型。主要问题显然是符号的复杂性。为什么不支持递归类型?是否计划在以下版本中得到支持?我提供的草案(如果得到支持)简短、有意识、可维护且易于编写。 (2认同)