TypeScript undergoes type analysis in a top-down manner, aligning with the program data flow where data is also passed top-down. The language scrutinizes control-flow utilizing information provided from the top rather than the bottom.
Let's examine a function as an example:
function functionMaker(x: string, y: string) {
return () => x + y;
}
In the above function, another function is being returned. While there is no explicit typing in the definition of the anonymous function being returned, TypeScript can deduce that functionMaker
consistently returns a function of the type () => string
.
Attempting to analyze it the other way around proves challenging, as TypeScript cannot anticipate how the arguments will be utilized. Consider the following scenario:
function functionA(x: string, y: number, z: SpecialType): void { }
function functionB(x: number): void { }
const functionWrapper: (x, y, z) => {
functionA(x, y, z); // x should be a string
functionB(x); // x should be a number
}
Here, TypeScript encounters two functions that each take one argument, but with different type requirements. Any attempt to resolve this dilemma would result in failure for either function.
To address this issue, we can create a generic function that acts as a wrapper for another function.
function functionA(x: string, y: number, z: SpecialType): void { }
const wrap = <X, Y, Z, R>(f: (x: X, y: Y, z: Z) => R) => (x: X, y: Y, z: Z): R => f(x,y,z);
const functionWrapper = wrap(functionA);
Our wrap
function serves explicitly as a wrapper, tasked with inferring types from the given function and creating a new function with identical arguments and return type.