I'm trying to type a higher order function. The basic idea is that high is passed a function fn, and it returns a function that takes the exact same parameters and gives the same return type.
It's an exercise in trying to understand the language better. I have something that works but erases the type from the input parameters. Please see the types Test1 vs Test2 below:
export function high<R>(fn: (...args: any[]) => R) {
return (...args: any[]) => {
const moddedArgs = args.map((el) =>
typeof el === "string" ? el "OMG" : el
);
return fn(...moddedArgs);
};
}
const test1 = (nr1: number, str1?: string) => (str1 ?? "Wow").repeat(nr1);
test1(3, "yo");
// returns yoyoyo
test1(3);
// returns WowWowWow
const test2 = high(test1);
test2(3, "yo");
// returns yoOMGyoOMGyoOMG
type Test1 = typeof test1;
// type Test1 = (nr1: number, str1?: string) => string
type Test2 = typeof test2;
// type Test2 = (...args: any[]) => string
I tried having a P type argument for the parameters, which I sort of got working But it didn't handle optional parameters well.
Any ideas how this can be solved?
CodePudding user response:
You should store the whole type of fn
in a generic type. To type ...args
, we can use Parameters<Fn>
.
export function high<Fn extends (...args: any[]) => any>(fn: Fn) {
return (...args: Parameters<Fn>) => {
const moddedArgs = args.map((el) =>
typeof el === "string" ? el "OMG" : el
);
return fn(...moddedArgs);
};
}
CodePudding user response:
Instead of trying to guess generics for the fn
function parameters and return types separately, we can have a single generic type F
for the whole function, and then extract its constituent types with Parameters
and ReturnType
TypeScript built-in utility types:
export function high<F extends (...args: any[]) => any>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
const moddedArgs = args.map((el) =>
typeof el === "string" ? el "OMG" : el
);
return fn(...moddedArgs);
};
}
const test2 = high(test1);
// ^? (nr1: number, str1?: string | undefined) => string
We can even go a little further, in case it might make sense in your case: by asserting the modified function as having exactly the same type as the original function (type F
above), we get its associated JSDoc as well!
function high2<F extends (...args: any[]) => any>(fn: F) {
return high(fn) as F // If we assert the returned function type as exactly F, we get its associated JSDoc as well!
}
const test22 = high2(test1);
// ^? (nr1: number, str1?: string) => string
// In the IDE, we should have any JSDoc associated to test1, popup as well on test22, since it is said to be exactly the same type.