Mark Gibson
Mark Gibson

Reputation: 61

How to parameterise a functions argument tuple in Typescript

Using Typescript 1.4, say I have a higher-order function with the signature:

interface F<A,B> {
    (a: (...x:any[]) => A): (...x:any[]) => B
}

Function 'F' takes function 'a' which has a set of parameters 'x'. Function 'F' returns a new function which has exactly the same set of args as function 'a'.

I've been trying to find a way to express this in Typescript, but i'm coming up short. For example:

interface F<X extends Array<any>,A,B> {
    (a: (...x:X) => A): (...x:X) => B
}

the compiler just complains with: error TS2370: A rest parameter must be of an array type.

Although that doesn't feel right anyway, even if it did compile. I guess i'd really need something like:

interface F<X extends Tuple,A,B> {
    (a: (...x:X) => A): (...x:X) => B
}

Anyone know if this kind of thing is even possible with Typescript currently (1.4 at time of writing)? or any suggestions?


Example:

(NOTE: This is not my actual use-case, i'm only using logging here as a simple example - please don't focus on that aspect)

// a higher-order function that takes any function and
// returns a new function which takes the same args
// passing them to the original fn and logging the args along with the result

function f(a:(...x:any[]) => any): (...x:any[]) => void {
    return (...x:any[]) => {
        console.log('('+x.join(',')+') => ', a.apply(undefined, x));
    }
}

function a(j:string, k:number): boolean {
    return j === String(k);
}

var b = f(a);

b("1", 1);
b("a", 2);

console output:

(1,1) =>  true
(a,2) =>  false

So this works, but the derived function 'b' has the implied signature of:

(...x:any[]) => void

ideally i'd like it to have the same arguments as function 'a' ie:

(j: string, k: number) => void

I know I could explicitly define that, but it's very verbose and not at all ideal, kind of defeats the point of having the strong typing in the first place:

var b: (j:string, k:number) => void = f(a);

Upvotes: 4

Views: 8090

Answers (3)

Zalastax
Zalastax

Reputation: 361

It's currently not possible to express this in the TypeScript type system. It's a long standing issue which you can follow on https://github.com/Microsoft/TypeScript/issues/212 and https://github.com/Microsoft/TypeScript/issues/5453

Upvotes: 0

Rora
Rora

Reputation: 11

To wrap a function with another that has different return type, one hacky trick is possible with overloading:

function wrap<R>(fn0: ()=> any, p:(f,a)=>R): () => R;
function wrap<R,T>(fn1: (t:T)=> any, p:(f,a)=>R): (t:T) => R;
function wrap<R, T, U>(fn2: (t:T, u:U)=> any, p:(f,a)=>R): (t:T,u:U) => R;
function wrap<R, T, U, V>(fn3: (t:T, u:U, v:V)=> any, p:(f,a)=>R): (t:T,u:U, v:V) => R;
function wrap<R, T, U, V, X>(fn4: (t:T, u:U, v:V, x:X)=> any, p:(f,a)=>R): (t:T,u:U, v:V, x:X) => R;
// ...
function wrap<R>(fn: Function, proc:Function):Function {
	return (...args) => proc(fn, args);
}

// wrap is called with two functions fn and proc
// result is a function with argument types from fn and return type of proc 

function serialize(fn, args):string {
  return JSON.stringify(fn(...args))
}

function foo(a:number,b:string) {
  return true;
}

var wrapped = wrap(foo,serialize)
// type is (a:number,b:string) => string

Be careful, it will work only for functions with limited number of arguments.

Upvotes: 1

basarat
basarat

Reputation: 275819

Function 'F' takes function 'a' which has a set of parameters 'x'. Function 'F' returns a new function which has exactly the same set of args as function 'a'.

The specification is a bit unclear, but based on my understanding of what you meant, Here is a sample

// Function 'F' takes function 'a' which has a set of parameters 'x'
// and returns a new function which has exactly the same set of args as function 'a'
function F<A extends Function>(a:A):A{
    return a;
}

var foo = F(function(a:number,b:number):void{});

foo (1,3); // okay 
foo(1); // ERROR

UPDATE

An example for your use case:

function f<A extends Function>(a:A):A {
    var newFunc = (...x:any[]) => {
        console.log('('+x.join(',')+') => ', a.apply(undefined, x));
        return null;
    }
    return <any>newFunc;
}

function a(j:string, k:number): boolean {
    return j === String(k);
}

var b = f(a);

b("1", 1);
b("a", 2);
b('123','123'); // ERROR

Note: We've capture both the arguments and the return type. I don't see a way to capture just the arguments.

Upvotes: 0

Related Questions