打字稿递归函数组合 [英] Typescript recursive function composition

查看:27
本文介绍了打字稿递归函数组合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

type Chain= [] |[(参数:输入)=>出] |[(参数:输入)=>Tmp1, (i: Tmp1) =>Tmp2,...链];

这个想法在草案中.

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

  1. Type alias 'Chain' 循环引用自身.(明白为什么,不知道怎么解决)
  2. rest 元素类型必须是数组类型.(可能扩展不适用于泛型元组)
  3. Type 'Chain' 不是通用的.(甚至不明白为什么会出现这个错误)

Chain 的这个定义在 Typescript 中可行吗?如果是这样,请附上一个片段.

(在最新的 tsc 3.1.6 上测试)

解决方案

除非在某些情况下,否则并不真正支持循环类型别名.(更新 TS 4.1,这些现在得到更多支持,但我仍然倾向于将 flow() 表示为在 AsChain 上操作,验证特定 函数数组,而不是试图提出一个与所有有效函数数组匹配的Chain)

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

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

type Lookup= K 扩展 keyof T ?T[K] : 其他类型 Tail= T 扩展 [any, ...infer R] ?R:从不;类型 Func1 = (arg: any) =>任何;类型 ArgType= F 扩展 (arg: 推断 A) =>任何 ?答:否则;type AsChain<F extends [Func1, ...Func1[]], G extends Func1[]= Tail<F>={ [K in keyof F]: (arg: ArgType) =>ArgType<Lookup<G,K,any>,any>};类型 Last= T 扩展 [...推断 F, 推断 L] ?L:从不;输入 LaxReturnType= F extends (...args: any) =>推断 R ?R:从不;声明函数流any, ...Array<(arg: any) =>任何>]>(... f: F &AsChain<F>): (arg: ArgType) =>LaxReturnType;

让我们看看它是否有效:

const stringToString = flow((x: 字符串) =>x.长度,(y: 数字) =>y + !");//好的const str = stringToString("嘿");//这是一个字符串const tooFewParams = flow();//错误const badChain = 流((x: number)=>字符串",(y: string)=>false,(z: number)=>哎呀");//错误,布尔值不可分配给数字

我觉得不错.


我不确定是否值得详细介绍类型定义的工作原理,但我不妨解释一下如何使用它们:

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

  • Tail 接受一个元组类型 T 并返回一个删除了第一个元素的元组.所以Tail<[a",b",c"]>就是[b",c"].>

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

  • ArgType 如果是单参数函数,则返回 F 的参数类型,否则返回 Else.所以 ArgType<(x: string)=>number, boolean>string,而 ArgType<123, boolean>布尔值.

  • AsChain 接受一个单参数函数的元组,并试图通过替换 F 使用下一个函数的参数类型(最后一个使用 any).如果 AsChainF 兼容,一切都很好.如果 AsChainF 不兼容,那么 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 接受一个元组并返回最后一个元素,我们需要用它来表示flow() 的返回类型.最后一个<[a"、b"、c"]>c".

  • 最后,LaxReturnType 就像 ReturnType 但对 F 没有限制.


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

代码的游乐场链接

I want to create a function chain, which would be an input of a pipe/flow/compose function.

Is this possible without the literal expansion of the types to selected depth, as is this usually handled? See lodash's flow.

I want to achieve typecheck of the data flow in the chain. - Argument of a function is result of the previous one - First argument is a template parameter - Last return is a template parameter

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

The idea is in the draft.

This however produces tho following errors:

  1. Type alias 'Chain' circularly references itself. (understand why, don't know how to resole)
  2. A rest element type must be an array type. (probably spread is not available for generic tuples)
  3. Type 'Chain' is not generic. (don't even understand why this error is even here)

Is this definition of Chain possible in Typescript? If so, please enclose a snippet.

(Tested on latest tsc 3.1.6)

解决方案

Circular type aliases are not really supported except in certain cases. (UPDATE TS 4.1, these are more supported now, but I'm still inclined to represent flow() as operating on AsChain that verifies a particular array of functions instead of trying to come up with a Chain that matches all valid arrays of functions)

Instead of trying to represent the specific type you've written there in a TypeScript-friendly way, I think I'll back up and interpret your question as: how can we type a flow()-like function, which takes as its arguments a variable number of one-argument functions, where each one-argument-function return type is the argument type for the next one-argument-function, like a chain... and which returns a one-argument function representing the collapsed chain?

I've got something that I believe works, but it's quite complicated, using a lot of conditional types, tuple spreads, and mapped tuples. Here it is:

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>>;

Let's see if it works:

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

Looks good to me.


I'm not sure if it's worth it to go through in painstaking detail about how the type definitions work, but I might as well explain how to use them:

  • Lookup<T, K, Else> tries to return T[K] if it can, otherwise it returns Else. So Lookup<{a: string}, "a", number> is string, and Lookup<{a: string}, "b", number> is number.

  • Tail<T> takes a tuple type T and returns a tuple with the first element removed. So Tail<["a","b","c"]> is ["b","c"].

  • Func1 is just the type of a one-argument function.

  • ArgType<F, Else> returns the argument type of F if it's a one-argument function, and Else otherwise. So ArgType<(x: string)=>number, boolean> is string, and ArgType<123, boolean> is boolean.

  • AsChain<F> takes a tuple of one-argument functions and tries to turn it into a chain, by replacing the return type of each function in F with the argument type of the next function (and using any for the last one). If AsChain<F> is compatible with F, everything's good. If AsChain<F> is incompatible with F, then F is not a good chain. So, AsChain<[(x: string)=>number, (y:number)=>boolean]> is [(x: string)=>number, (y: number)=>any], which is good. But AsChain<[(x: string)=>number, (y: string)=>boolean]> is [(x: string)=>string, (y: string)=>any], which is not good.

  • Last<T> takes a tuple and returns the last element, which we need to represent the return type of flow(). Last<["a","b","c"]> is "c".

  • Finally, LaxReturnType<F> is just like ReturnType<F> but without a constraint on F.


Okay, hope that helps; good luck!

Playground link to code

这篇关于打字稿递归函数组合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆