1

A function expects another generic function as input and it's defined as:

function higherOrderFunction(fn: Function): Function {
  // do something
  return (...args: any[]) => fn(...args);
} 

called as

function fun(x: number, y: string) { return `${x}${+y}`; }

const higherOrderFunctionWithFunction= higherOrderFunction(fun);
console.log(higherOrderFunctionWithFunction(1, '3'))

This will work in typescript (no compilation error) no matter x or y types.

How to enforce the function returned by higherOrderFunction is correctly compiled by TSC only if x:number, y:string? (ideally for any number of parameters and types, not just this specific case)

dragonmnl
  • 14,578
  • 33
  • 84
  • 129

2 Answers2

1

Turns out this PR achieves the goal.

higherOrderFunction needs to refactored to:

function higherOrderFunction<A extends any[], B>(fn: (...a: A) => B) {
  return (...args: A) => fn(...args);
}

Now if we call for example

console.log(higherOrderFunctionWithFunction(1, 3))

will throw

Argument of type '3' is not assignable to parameter of type 'string'

which effectively enforces the signature (passed onto and returned from higherOrderFunction) of the lower order function ( fun ) to be exactly the same

This works also if fun receives more than 2 parameters (whose type is specified in fun)

dragonmnl
  • 14,578
  • 33
  • 84
  • 129
0

Maybe I'm not following but if the higher order function returns a function with any parameters, that when called will return the result of the function passed in, passing through arguments - can't you just instead return the function passed in? Then the generic typing is pretty simple and I don't see what else you'd need for typing.

function higherOrderFunction<T>(fn: T): T {
  // do something
  return fn;
} 

function fun(x: number, y: string) { return `${x}${+y}`; }

const higherOrderFunctionWithFunction = higherOrderFunction(fun);
console.log(higherOrderFunctionWithFunction(1, 3)) // error can't assign number to string

Edit:

If your higher order function were the below then that would change things and I'd change my answer. But you have the // do something before the return so going off your example it looks like you may as well just return fn...

function higherOrderFunction<T>(fn: T): T {
  return (...args) => {
      // do something common on every call
      return fn(...args);
  };
} 

Edit2: For the above scenario, if you really need to wrap fn in another function - here is the only way I was able to get the type checks you're looking for, with no other errors. I had to use type assertion for the return of your higher order function to assert that T is being returned and it's not instantiated as a different subtype... Can't say I'm 100% understanding the issue there in this specific case but it's related to this: could be instantiated with a different subtype of constraint 'object'

function higherOrderFunction<T extends (...args: any[]) => unknown>(fn: T): T  {
  return ((...args) => {
      // do something common on every call
      return fn(...args);
  }) as T;
}

function fun(x: number, y: string) { return `${x}${+y}`; }

const higherOrderFunctionWithFunction = higherOrderFunction(fun);
console.log(higherOrderFunctionWithFunction(1, 3)) // error can't assign number to string

Edit3: Looking at your answer, I like it better and upvoted, I think you got it...

MrRobboto
  • 722
  • 3
  • 10
  • type '(...args: any[]) => any' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to '(...args: any[]) => any'.ts(2322) Rest parameter 'args' implicitly has an 'any[]' type. Using `(...args:any[])` won't do it either – dragonmnl Aug 16 '20 at 22:38
  • Excuse me? What are you referring to? The first snippet I show a solution that does not use ...args at all - and then I asked a question about requirements with a pseudo code example that yes has a type error if you try to actually use it, it's just an example of where your //do something code is actually going to go. Can you confirm where you will actually be "doing something"? The higher order block or in your returned function? – MrRobboto Aug 17 '20 at 13:40
  • Your OP has it in the higher order block - if that is actually where you plan to // do something - then you don't need a wrapper function around fn. Only reason you'd need to wrap fn in another function is if you want to run some common code when fn is called, which is not how your OP reads. – MrRobboto Aug 17 '20 at 13:42
  • i should probably clarify. higher order function shall indeed return a function (... args) =>(common code). what's important is that fn passed on higher order function can enforce fn arguments (in my question : x and y as number and string respectively, but ideally fn can have any number /type of parameters) – dragonmnl Aug 17 '20 at 14:21
  • Well that doesn't clarify why you need to return another function, that is all I was pointing out - the way your OP reads is that the function you're returning literally does nothing but pass through arguments to fn and return the result. Based on that, you don't need the wrapping function. If you are doing something else inside that function then you should show that in your OP is all I'm saying to make the question more clear. – MrRobboto Aug 17 '20 at 17:12
  • Adding common logic is part of the scope (so that part you got it right). However, also enforcing the type of parameters of the function passed onto and returned from the higher order function is also expected (I believe that's clear enough in the question) – dragonmnl Aug 17 '20 at 22:05
  • "adding common logic" - yes you put // do something in the block for the higher order function. If that's actually where the common logic is supposed to go (when you call the higher order function, not when you call it's returned function) - then you don't need to wrap fn. I'm a broken record at this point, I've explained it. WHERE you do common logic is just as relevant in this case. If it's in the block of the returned function (called every time you call the returned function), then I get why you'd ask this question - but your OP shows common logic in the higher function, not the returned. – MrRobboto Aug 18 '20 at 15:25