没有重载的 Typescript 中的函数组合

Funciton composition in Typescript without overloads

是否可以为函数组合定义 Typescript 类型(参见 flow or pipe) 对于任意数量的参数(要组合的函数)没有覆盖但能够提示类型?

没有类型推断,有 in my previous .

唉,这个解决方案只在显式定义类型时验证链并报告错误:

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

但是所有参数都是

flow(
  (x: number)=>"string",
  (y: string)=>false,
  z => {/*z is any, but should be inferred as boolean*/}
);

这个推论适用于 lodash 和 ramda 类型,但它的定义使用了长时间无法维护的重载,如我之前的问题所述。

有没有办法避免overwrites并且不丢失类型推断?

没有办法删除所有重载。类型 R* 参数相互依赖的方式目前在类型系统中无法表达。

我们可以做的一项改进是消除在第一个函数(添加 A* 类型参数的函数)上添加额外参数的重载需求。这可以在 3.0 中使用 tuples in rest parameters

完成
interface LoDashStatic {

    flow<A extends any[], R1, R2>(f1: (...a: A) => R1, f2: (a: R1) => R2): (...a: A) => R2;

    flow<A extends any[], R1, R2, R3>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (...a: A) => R3;

    flow<A extends any[], R1, R2, R3, R4>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (...a: A) => R4;

    flow<A extends any[], R1, R2, R3, R4, R5>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (...a: A) => R5;

    flow<A extends any[], R1, R2, R3, R4, R5, R6>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (...a: A) => R6;

    flow<A extends any[], R1, R2, R3, R4, R5, R6, R7>(f1: (...a: A) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (...a: A) => R7;

}

declare const _: LoDashStatic;

let f = _.flow((n: number, s: string) => n + s, o => o.toUpperCase()); // f: (n: number, s: string) => string

为什么仍然需要重载可能不是很明显。我尝试了各种实现并找到了我认为的问题的核心,至少给出了一种方法,它解决了自下而上枚举所有链的问题(这比重载稍微好一些,因为你可以自我引用较低的级别)并且你仍然应该得到类型推断。

const a: [(_: string) => number, (_: number) => boolean] | [(_: string) => boolean] = [x => x.length, y => true]

只需要重载来检测元组的长度。 TS 可以通过重载(select 基于参数数量的签名)来管理它,但无法使用没有函数签名的普通元组来做到这一点。这就是为什么 y 没有在代码片段中推断出来,以及为什么在没有重载的情况下使解决方案更紧凑的努力无法在当前状态下取得成功。

所以批准的答案似乎是目前最好的解决方案!

这已经四岁了,但我设法让一个没有重载的类型版本工作:

需要一些讨厌的东西:

我们需要数字列表的原因有两个:

  • 给定一个特定的索引,我们需要能够检索以前的索引
  • 我们需要能够将字符串元组索引转换为数字索引
type SNumbers = [
   "0",  "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",  "10", "11", "12", "13", "14", "15",
   "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31",
   "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47",
   "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63"];

type Numbers = [
   0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
   16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
   32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
   48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63];

// Util to prepend a value to a Tuple from: 
type PrependTuple<A, T extends Array<any>> =
  (((a: A, ...b: T) => void) extends (...a: infer I) => void ? I : [])

// Get the previous number (for indexing)    (2=>1, 1=>0, 0=>never)
type PrevN<T extends number> = PrependTuple<never, Numbers>[T];

// Convert a string index to a number
type S_N<S extends SNumbers[number]> = {
   [K in SNumbers[number]]: Numbers[K]
}[S]

几个帮手:

Pipe/Compose 作用于一元函数

// Only unary functions wanted 
type Unary = (i: any) => any;

// Get the (single) argument of a given unary function
type ParameterUnary<F extends Unary> = Parameters<F>["0"]

// ReturnType is a builtin

主要类型:

UnariesToPiped/UnariesToComposed 获取一元函数的元组并尝试将其映射到包含正确函数类型的元组

然后 Pipe/Compose 只需将映射的元组作为参数并拉取第一个参数类型和最后一个 return 类型。

type UnariesToPiped<F extends Unary[]> = {
   [K in keyof F]:
   K extends SNumbers[number] 
      ? K extends "0"
         ? F[K]
         : (i: ReturnType<F[PrevN<S_N<K>>]>) => ReturnType<F[S_N<K>]>
      : F[K]
}

type Pipe = <F extends Unary[]>(...funcs: UnariesToPiped<F>) => (i: ParameterUnary<F[0]>) => ReturnType<F[PrevN<F["length"]>]>

type UnariesToComposed<F extends Unary[]> = {
   [K in keyof F]:
   K extends SNumbers[number] 
      ? K extends "0"
         ? F[K]
         : (i: ParameterUnary<F[S_N<K>]>) => ParameterUnary<F[PrevN<S_N<K>>]>
      : F[K]
}

type Compose = <F extends Unary[]>(...funcs: UnariesToComposed<F>) => (i: ParameterUnary<F[PrevN<F["length"]>]>) => ReturnType<F[0]>

有关使用示例,我将其发布到 Github and NPM