Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Varargs in typescript

Tags:

typescript

I want to pass arbitrary number of arguments of different types into a function and use these types. It looks like following:

function f<A>(a: A): A;
function f<A, B>(a: A, b: B): A & B;
function f<A, B, C>(a: A, b: B, c: C): A & B & C;
function f<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D;

function f(...args: any[]) {
    return Object.assign({}, ...args);
}

var smth = f({ x: 1 }, { y: 2 }, { z: 3 });
var res = smth.x + smth.y + smth.z;

As I want arbitrary number of parameters, I'd like to get rid of these declarations

function f<A>(a: A): A;
function f<A, B>(a: A, b: B): A & B;
function f<A, B, C>(a: A, b: B, c: C): A & B & C;
function f<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D;

and use a single declaration like:

function f<...T>(args: [...T]): &<...T>;

but this thing is syntactically wrong.

Is there a way to rewrite it in a correct way?

PS: Same question in Russian.

like image 287
Qwertiy Avatar asked Jun 06 '18 20:06

Qwertiy


People also ask

What does () => void mean TypeScript?

According to the TypeScript docs: void represents the return value of functions which don't return a value. Whenever you see a function returning void , you are explicitly told there is no return value. All functions with no return value have an inferred return type of void .

How do I return two values in TypeScript?

To return multiple values from a function in TypeScript, group the values in an array and return the array, e.g. return [myValue1, myValue2] as const . You can then destructure and use the values the function returns. Copied! We declared a function that returns multiple values by grouping them in an array.

What is rest parameter in TypeScript?

A rest parameter allows us to pass zero or any number of arguments of the specified type to a function. In the function definition where we specify function parameters rest parameters should always come at last or the typescript compiler will raise errors.

How do I add a return type in TypeScript?

To define the return type for the function, we have to use the ':' symbol just after the parameter of the function and before the body of the function in TypeScript. The function body's return value should match with the function return type; otherwise, we will have a compile-time error in our code.


1 Answers

Edit for 3.0

While the original answer is correct, since I first gave it typescript has changed. In typescript 3.0 it is possible to use tuples in rest parameters to capture the type of the arguments in a tuple

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

function f<A extends any[]>(...args: A): UnionToIntersection<A[number]> { return null! }
var smth = f({ x: 1 }, new A(), new B());  // will have type  A & B & { x: number; }

Original answer

While as others have mentioned Proposal: Variadic Kinds would help with this task, we can find some workarounds for your specific example.

If we write the function signature with a single type argument, we can get a union type of the arguments:

function f<A>(...args: A[]): A {}
var smth = f({ x: 1 }, { y: 2 }, { z: 3 });
typeof smth = {
    x: number;
    y?: undefined;
    z?: undefined;
} | {
    y: number;
    x?: undefined;
    z?: undefined;
} | {
    z: number;
    x?: undefined;
    y?: undefined;
}

The problem with this approach is that if we use a class instead of an object literal the compiler will refuse to infer the union and give us an error instead. If we let the rest parameter go (...) and just use an array the compiler will infer a union of the parameter type:

function f<A>(args: A[]): A { /*…*/}
var smth = f([{ x: 1 }, new A(), new B()]); 
typeof smth == A | B | {
    x: number;
}

So now we have a union of types, but you want an intersection. We can convert the union to an intersection using conditional types (see this answer)

type UnionToIntersection<U> = 
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

function f<A>(args: A[]): UnionToIntersection<A> {
    return Object.assign({}, ...args);
}

class A { z: number }
class B { y: number }

var smth = f([{ x: 1 }, new A(), new B()]); // will have type  A & B & { x: number; }
var res = smth.x + smth.y + smth.z;

Hope this helps, and gives you a usable workaround at least until we get variadic kinds.

like image 76
Titian Cernicova-Dragomir Avatar answered Nov 03 '22 18:11

Titian Cernicova-Dragomir