Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Generic higher order function parameter type

When creating a function with a generic function parameter, is there a way to prevent bad utilization of the parameter?

The perceived standard case types the function as "any function whatsoever" inside:

const rip = <T extends (...args: any[]) => any>(f: T) => f('invalid');
rip((a: number) => a.toFixed(2)); // oops

Given any is a boo-doo and makes everything bivariant, i tried the following, but it still doesn’t account for number of parameters:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

const rip2 = <T extends (...args: readonly never[]) => unknown>(f: T) => f();
rip2((a: number) => a.toFixed(2)); // oops again

This has likely been asked before, but searching for it proved difficult for me (too many similar topics around "generic" and "higher order").


To avoid XY-problems, the origin of the problem is writing a function, which wraps functions with a check. Here is how i first intended to write it:

declare const check: () => boolean;
const checked = <T extends (...args: any[]) => any>(f: T): (...args: Parameters<T>) => 
ReturnType<T> | undefined => (...args) => {
  if (check()) return;
  return f(...args); // `f('invalid')` would not cause an error
};
const g = checked((a: number) => a.toFixed(2));

It suffers from the same issue. Inside, the function is (...args: any[]) => any, and therefore has no checks. Similar to above, a more strict constraint doesn’t solve the issue entirely (no parameters still works), readonly causes an error, and a cast is necessary:

declare const check: () => boolean;
const checked = <T extends (...args: /* readonly */ never[]) => unknown>(f: T): (...args: 
Parameters<T>) => ReturnType<T> | undefined => (...args) => {
  if (check()) return;
  return f(...args) as ReturnType<T>; // `f()` would not cause an error
};
const g = checked((a: number) => a.toFixed(2));

>Solution :

By being more specific with types (i.e. creating a separate generic type for args and return-type, rather than inferring them from a single function type), TypeScript is better able to deduce that something’s wrong if the function is supplied incorrect parameters.

declare const check: () => boolean;
const checked = <A extends any[], R>(f: (...args: A) => R): (...args: A) =>
    R | undefined => (...args) => {
        if (check()) return;
        return f(...args);
    };
const g = checked((a: number) => a.toFixed(2));

Playground Link

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading