typescript - Hur man starkt typiserar funktioner med variabla argument


Jag skriver en Parser Combinator i Typescript. En av funktionerna är avsedd att ta en annan funktion med vilken typ av signatur som helst och ett antal argument, och sedan kalla på den:

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

Finns det något sätt att starkt typa detta så att funktionen kan kallas på detta sätt:

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

Men ska det misslyckas med felaktigt typade argument, eller ett felaktigt antal av dem?

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

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

(Lite ytterligare bearbetning sker i f, vilket är anledningen till att jag inte bara kallar func direkt).

Svar #1

Även om du inte kan göra detta för ett godtyckligt antal argument, kan du definiera en funktion som kontrollerar detta för ett begränsat antal argument, och lägga till fler vid behov:

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  
)

Ovanstående version kommer att kontrollera typer, men den kan anropas med fler argument. Detta beror på att överlagringen med 2 generiska typer kommer att väljas, och func kan ha färre argument och ändå vara kompatibel.

Du kan uppnå full säkerhet om du använder en tvåstegsmetod, som låser fast func i det första anropet och returnerar en andra funktion som redan har antalet parametrar bestämt:

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
)

Svar #2

Du kan använda följande kod för att uppnå stark typkontroll:

function f<T extends any[], R>(func: (...args: T) => R, ...args: T) {
    func.apply(null, args);
}