Typescript中的函数组合没有重载

问题描述 投票:1回答:3

是否有可能为任意数量的参数(要编写的函数)定义函数组合的Typescript类型(参见flowpipe)而不会覆盖但能够提示类型?

没有类型推断,在我以前的marvelous answer中有一个question

唉此解决方案仅在明确定义类型时验证链并报告错误:

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而不是丢失类型推断?

typescript types pipe flow function-composition
3个回答
2
投票

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

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

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

0
投票

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

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

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

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


0
投票

这已经有四年了,但是我设法让一个打字版本在没有重载的情况下工作:

需要一些令人讨厌的东西:

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

  • 给定一个特定的索引,我们需要能够检索以前的索引
  • 我们需要能够将字符串元组索引转换为数字索引
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: https://stackoverflow.com/a/54607819/5308589
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简单地将映射的元组作为参数,并拉出第一个参数类型和最后一个返回类型。

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

有关使用示例,我将其发布到GithubNPM

© www.soinside.com 2019 - 2024. All rights reserved.