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

Typing a polymorphic function in TypeScript

Here is what I am trying to type:

type CallbackMap = {
  n: (value: number) => void;
  s: (value: string) => void;
};

type MyFunction = <K extends keyof CallbackMap>(
  callbacks: CallbackMap[K][]
) => CallbackMap[K];

const myFunction: MyFunction = (callbacks) => (value) => {
  //                                           ^^^^^ (1)
  for (const callback of callbacks) {
    callback(value);
    //       ^^^^^ (2)
  }
};

myFunction([(value: number) => {}])(1);
//                                  ^ (3)
myFunction([(value: string) => {}])("one");
//                                  ^^^^^ (4)

Link to the TypeScript Playground

So myFunction takes an array of callbacks as a parameter and returns a function having the same signature than the callbacks in the array passed to myFunction. Calling the function returned by myFunction would then call every callback in the array of callbacks passing them the argument value given to the function.

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

There are 4 errors with this code:

(1) Parameter 'value' implicitly has an 'any' type.
(2) Argument of type 'any' is not assignable to parameter of type 'never'.
(3) Argument of type 'number' is not assignable to parameter of type 'never'.
(4) Argument of type 'string' is not assignable to parameter of type 'never'.

I can get rid of errors (1), (3), and (4), by refactoring the type of the return value of myFunction:

type MyFunction = <K extends keyof CallbackMap>(
  callbacks: CallbackMap[K][]
) => (value: Parameters<CallbackMap[K]>[0]) => void;

const myFunction: MyFunction = (callbacks) => (value) => {
  for (const callback of callbacks) {
    callback(value);
    //       ^^^^^ (5)
  }
};

Link to the TypeScript Playground

Now the only error left is:

(5) Argument of type 'string | number' is not assignable to parameter of type 'never'.
      Type 'string' is not assignable to type 'never'.

With a bit of refactoring the types can be rewritten like this:

type Callback<V> = (value: V) => void;

type CallbackMap = {
  n: Callback<number>;
  s: Callback<string>;
};

type MyFunction = <K extends keyof CallbackMap>(
  callbacks: CallbackMap[K][]
) => Callback<Parameters<CallbackMap[K]>[0]>;

Link to the TypeScript Playground

Refactoring the type of the callbacks parameter of myFunction the same way would only move the issue to the function calls:

type MyFunction = <K extends keyof CallbackMap>(
  callbacks: Callback<Parameters<CallbackMap[K]>[0]>[]
) => Callback<Parameters<CallbackMap[K]>[0]>;

const myFunction: MyFunction = (callbacks) => (value) => {
  for (const callback of callbacks) {
    callback(value);
  }
};

myFunction([(value: number) => {}])(1);
//          ^^^^^^^^^^^^^^^^^^^^^ (6)
myFunction([(value: string) => {}])("one");
//          ^^^^^^^^^^^^^^^^^^^^^ (7)

Link to the TypeScript Playground

Errors on the function calls:

(6) Type '(value: number) => void' is not assignable to type 'Callback<string | number>'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'string | number' is not assignable to type 'number'.
          Type 'string' is not assignable to type 'number'.
(7) Type '(value: string) => void' is not assignable to type 'Callback<string | number>'.
      Types of parameters 'value' and 'value' are incompatible.
        Type 'string | number' is not assignable to type 'string'.
          Type 'number' is not assignable to type 'string'.

Is there a way to type myFunction without TypeScript complaining?

>Solution :

The TypeScript code provided is almost correct, but the type definition of MyFunction is incorrectly defined. It tries to infer the type of the generic K from the callbacks parameter, but it’s not directly possible to infer the type of K like this, which is the cause of the error.

type Callback<V> = (value: V) => void;

type CallbackMap = {
  n: Callback<number>;
  s: Callback<string>;
};

type MyFunction = <K extends keyof CallbackMap>(
  callbacks: Callback<Parameters<CallbackMap[K]>[0]>[]
) => Callback<Parameters<CallbackMap[K]>[0]>;

const myFunction: MyFunction = (callbacks) => (value) => {
  for (const callback of callbacks) {
    callback(value);
  }
};

myFunction<"n">([(value: number) => {}])(1);
myFunction<"s">([(value: string) => {}])("one");


Edit: Used Type inference during function call as opposed to extra argument

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