Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Strongly Type Functions with Variable Arguments

Tags:

typescript

I'm writing a Parser Combinator in Typescript. One of the functions is meant to take another function with any type signature and some number of arguments, then call it:

function f(func, ...args) {
  func.apply(null, args);
}

Is there any way to strongly type this so that the function can be called like this:

f(
  (s: string) => { console.log(s) },
  "Hello world!"
)

But have it fail with incorrectly typed arguments, or an incorrect number of them?

f(
  (s: string) => { console.log(s) },
  1234.56 // incorrect type
)

f(
  (s: string) => { console.log(s) },
  "Hello",
  "World" // too many arguments
)

(Some additional processing happens in f, which is why I don't just call func directly).

like image 871
Valhalla Avatar asked Feb 24 '26 18:02

Valhalla


1 Answers

While you can't do this for an arbitrary number of arguments, you can define a function that checks this for a limited number of arguments, and add more as needed:

function f<T1>(func: (a1: T1) => void, a1: T1): void;
function f<T1, T2>(func: (a1: T1, a2: T2) => void, a1: T1, a2: T2): void;
function f<T1, T2, T3>(func: (a1: T1, a2: T2, a3: T3) => void, a1: T1, a2: T2, a3: T3): void;
function f<T1, T2, T3, T4>(func: (a1: T1, a2: T2, a3: T3, a4: T4) => void, a1: T1, a2: T2, a3: T3, a4: T4): void;
// Private signature, not publicly available
function f(func: Function, ...args: any[]): void {
    func.apply(null, args);
}
f(
    (s: string) => { console.log(s) },
    1234.56 // incorrect type
)

f(
    (s: string) => { console.log(s) },
    "Hello",
    "World" // will accept more arguments  
)

The above version will check types, but it can be invoked with more arguments. This is because the overload with 2 generic types will be chosen, and the func can have fewer arguments and still be compatible.

You can achieve full safety, if you use a 2 call approach, which locks in the func in the first call, and returns a second function that already has the number of parameters decided:

function f2<T1>(func: (a1: T1) => void): (a1: T1) => void;
function f2<T1, T2>(func: (a1: T1, a2: T2) => void): (a1: T1, a2: T2) => void;
function f2<T1, T2, T3>(func: (a1: T1, a2: T2, a3: T3) => void): (a1: T1, a2: T2, a3: T3) => void;
function f2<T1, T2, T3, T4>(func: (a1: T1, a2: T2, a3: T3, a4: T4) => void): (a1: T1, a2: T2, a3: T3, a4: T4) => void;
// Private2 signature, not publicly available
function f2(func: Function) {
    return function (...args: any[]) { func.apply(null, args) };
}
f2((s: string) => { console.log(s) })(1234.56) // type mismatched 

f2((s: string) => { console.log(s) })(
    "Hello",
    "World" // too many arguments
)
like image 150
Titian Cernicova-Dragomir Avatar answered Feb 26 '26 09:02

Titian Cernicova-Dragomir



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!