Home > OS >  Generic higher order function parameter type
Generic higher order function parameter type

Time:04-22

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:

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));

CodePudding user response:

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

  • Related