zoul
zoul

Reputation: 104115

Variable number of generic parameters in Typescript

I have a function like this in Typescript:

function combine<A, B>(p1: Parser<A>, p2: Parser<B>): Parser<A | B> { … }

Is it possible to type it for variable number of type variables (A, B, C, …) and correctly set the result type to Parser<A | B | C | …>? I know I can do this by writing the type signatures by hand for different arities:

function combine<A>(p1: Parser<A>): Parser<A>;
function combine<A, B>(p1: Parser<A>, p2: Parser<B>): Parser<A | B>;
// …and so on…
function combine(...parsers: Parser<any>[]): Parser<any> { … }

Is that the only option?

PS. I was looking at this similar question, but the types are a bit above my head and I don’t know whether it’s the same case or not (the “endless union type” looks like an extra requirement here).

Upvotes: 1

Views: 2181

Answers (2)

Teneff
Teneff

Reputation: 32148

Here's how you you can infer the result type based on the arguments. Probably that's what you're trying to achieve

function combine<T extends string>(...ps: Parser<T>[]): typeof ps extends Array<infer R> ? R : never {
    throw new Error('not implemented')
}

TS Playground


Edit:

After reading your comment I realized that you probably want to define the function like this:

// generic extending parser Array
function combine<T extends Parser[]>(...ps: T): typeof ps extends Array<infer R> ? R : never {
    throw new Error('not implemented')
}

this way you'd be able to pass a tuple as generic argument to define the type and the position of the arguments

const result = combine3<[B, A]>(parserB, parserA)
// typeof result is B | A

or don't enforce position, just let ts infer the return type

const result = combine(parserB, parserA, parserA, parserC)
// typeof result is B | A | C

examples

Upvotes: 1

Mike Kokadii
Mike Kokadii

Reputation: 523

It is possible to implement it with array of generic types instead of unions. You can do in few ways:

  • Create a type and has abbility to do many arrow function implementation

    type ParserFunc<T extends any[]> = (...args: {
      [P in keyof T]: T[Parser<P>]
    }) => Parser<T[number]>
    
    const function1: ParserFunc<[string,number]> = (p1, p2) => { ... }  // => Parser<string | number>
    const function2: ParserFunc<[string,string]> = (p1, p2) => { ... }  // => Parser<string>
    const function3: ParserFunc<[string,boolean]> = (p1, p2) => { ... } // => Parser<string | boolean>
    
  • Create a single standart function

    function combineParser<T extends any[]>(...args: {
      [P in keyof T]: T[Parser<P>]
    }): Parser<T[number]> {
      // ...
    }
    
    combineParser(1,'2',true, null) // => Parser<number | string | boolean | null>
    

Upvotes: 0

Related Questions