typescript递归函数组合

IT技术 javascript typescript recursion function-composition spread-syntax
2021-01-21 03:17:11

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

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

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

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. (明白为什么,不知道怎么resol)
  2. A rest element type must be an array type. (可能传播不适用于通用元组)
  3. Type 'Chain' is not generic. (甚至不明白为什么这个错误甚至在这里)

这个定义Chain在 Typescript可能吗?如果是这样,请附上一个片段。

(在最新的 tsc 上测试3.1.6

1个回答

除非在某些情况下,否则并不真正支持循环类型别名。(UPDATE TS 4.1,这些都是现在更多的支持,但我仍然倾向于表现flow()为对工作AsChain验证一个特定的功能阵列,而不是试图拿出一个Chain符合所有有效的功能阵列)

与其试图以 TypeScript 友好的方式表示您在那里编写的特定类型,我想我会备份并将您的问题解释为:我们如何键入一个flow()-like 函数,该函数将一个可变数字作为其参数单参数函数,其中每个单参数函数返回类型是下一个单参数函数的参数类型,就像一个链......并且它返回一个代表折叠链的单参数函数?

我有一些我认为有效的东西,但它非常复杂,使用了很多条件类型元组传播映射元组这里是:

type Lookup<T, K extends keyof any, Else=never> = K extends keyof T ? T[K] : Else

type Tail<T extends any[]> = T extends [any, ...infer R] ? R : 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 Last<T extends any[]> = T extends [...infer F, infer L] ? L : never;
type LaxReturnType<F> = F extends (...args: any) => infer R ? R : never;

declare function flow<F extends [(arg: any) => any, ...Array<(arg: any) => any>]>(
  ...f: F & AsChain<F>
): (arg: ArgType<F[0]>) => LaxReturnType<Last<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>stringLookup<{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>stringArgType<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],这不好。

  • Last<T>接受一个元组并返回最后一个元素,我们需要用它来表示 的返回类型flow()Last<["a","b","c"]>"c"

  • 最后,LaxReturnType<F>就像ReturnType<F>但没有约束F.


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

Playground 链接到代码

你在这里所做的令人印象深刻。它确实有效。唯一的小挫折是错误似乎总是在第一个函数上,无论链在哪里中断(尽管消息是正确的),这似乎是解决方案的固有问题。您正在验证 AsChain 中的链,而不是定义类型。主要问题显然是符号的复杂性。为什么不支持递归类型?是否计划在以下版本中支持它们?我提供的草案(如果得到支持)简短、有意识、可维护且易于编写。
2021-03-18 03:17:11
“他们是否计划得到支持”?在此处阅读“递归类型”设计说明部分:github.com/Microsoft/TypeScript/issues/27102
2021-04-04 03:17:11
至于错误总是在第一个函数上,是的,我注意到了。我不确定递归类型在这方面会更好。这似乎是 IntelliSense 中的一个错误。也许我会报告它。
2021-04-06 03:17:11
好的,我报告了错误消息问题,这显然是一个错误。也许它会在 TS3.3 中修复?
2021-04-07 03:17:11
也许像这样解释可能超出了问题的范围,不适合评论。
2021-04-09 03:17:11