Deftomat
Deftomat

Reputation: 629

TypeScript type inference issue when function returns function

I have a following example functions (compose is from Ramda):

    declare function compose<V0, T1, T2>(fn1: (x: T1) => T2, fn0: (x0: V0) => T1): (x0: V0) => T2;

    interface User {
      id: number
    }

    function fn1(input: any): User {
      return {id: 1};
    }

    function fn2<I, O>(fn: (i: I) => O): (i: I) => O {
       return (data) => {
         try {
           return fn(data);
         } catch (e) {
           return undefined
         }
      };
    }

When I tried to use it like this:

compose(fn2(user => user.id), fn1);

TypeScript throws a following error:

TS2339:Property 'id' does not exist on type '{}'.

Does anyone know, what should I do to help TypeScript to infer a proper type for user?

Of course, following code will work:

compose<any, User, number>(fn2(user => user.id), fn1);

Upvotes: 2

Views: 189

Answers (2)

lilezek
lilezek

Reputation: 7354

It looks like that parametric types are resolved left to right and because you are writing an any to any function first you have that error. Look at this code which is similar but the functions are reversed:

declare function compose<V0, T1, T2>(fn0: (x0: V0) => T1, fn1: (x: T1) => T2,): (x0: V0) => T2;

interface User {
  id: number
}

function fn1(input: number): User {
  return {id: 1};
}

function fn2<I, O>(fn: (i: I) => O): (i: I) => O {
   return (data) => {
     try {
       return fn(data);
     } catch (e) {
       return undefined
     }
  };
}

compose(fn1, fn2(user => user.id));

It works as you would expect. but the compose definition must be changed.

Edit

Similar to Titian Cernicova's answer, you can define at least the first parametric element that goes into your compose chain like this:

declare function compose<T1 = any, T2 = any, V0 = any>(fn1: (x: T1) => T2, fn0: (x0: V0) => T1): (x0: V0) => T2;

interface User {
  id: number
}

function fn1(input: number): User {
  return {id: 1};
}

function fn2<I, O>(fn: (i: I) => O): (i: I) => O {
   return (data) => {
     try {
       return fn(data);
     } catch (e) {
       return undefined
     }
  };
}

compose<User>(fn2(user => user.id), fn1);

Upvotes: 1

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250106

You don't have to specify all the types but you need to specify the user parameter type for this to work.

compose(fn2((user: User) => user.id), fn1);

If you were to not use fn2 type inference would work, but deciding that fn2's argument has to be of type (user:User)=> number based on the required value to be returned by fn2 is a bit too much for the Typescript compiler.

This works:

compose((user) => user.id, fn1);

Upvotes: 0

Related Questions