【问题标题】:Funciton composition in Typescript without overloads没有重载的 Typescript 中的函数组合
【发布时间】:2019-01-16 07:58:49
【问题描述】:

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

没有类型推断,在我之前的question里面有一个marvelous answer

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

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,又不会丢失类型推断?

【问题讨论】:

  • 不,这是目前唯一的方法
  • 唯一的改进可能是没有覆盖多个参数(删除 A1...AN 重载只保留一组)。

标签: typescript types pipe flow function-composition


【解决方案1】:

没有办法删除所有的重载。 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

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

      现在已经 4 年了,但我设法得到了一个没有重载的类型化版本:

      需要做一些讨厌的事情:

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

      • 给定一个特定的索引,我们需要能够检索以前的索引
      • 我们需要能够将字符串元组索引转换为数字索引
      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-26
        • 1970-01-01
        • 2017-01-11
        • 2021-02-20
        • 2012-10-24
        • 2012-09-24
        相关资源
        最近更新 更多