Ian
Ian

Reputation: 685

Generic tuple type for [function,...params] with low overhead?

I'd like to tell the type checker that such and such tuples must each consist of a function and valid arguments, e.g.

let expressions:TYPE[] = [
    [(a:number, b:string)=>{},1,"ok"], // ok
    [(a:number)=>{},true] // error
    [(a:number)=>{}] // error
    [(a:number, b:string)=>{},1] // error
]

The current solution I have uses a function. It gets redundant:

expression<F extends (...args:any[])=>any>(fn:F, ...params:Parameters<F>){
return [fn,...params]
}

let queue:ReturnType<expression>[] = [
    expression(...),
    expression(...),
    expression(...),// seriously?
] 

It might be tolerable if I could do it in one call, e.g.

let queue = expressions(
    [(a:number)=>{},1], // ok
    [(a:number)=>{},true] // error
    [(a:number)=>{}] // error
)

But writing that function is beyond me. It seems to require a generic tuple type I don't think is possible and that landed me with the expression function call in the first place.

Here's a naive tuple type that doesn't work / requires gross overhead.

type Expression<F extends (...args:any[])=>any = (...args:any[])=>any>  = [F,...Parameters<F>]


let queue:Expression[] = [
    [(a:number)=>{},true] // No error
]

// Gross alternative:

const fn = (a:number)=>{}
queue = [
    [fn,true] as Expression<fn>  // Error as desired, but c'mon.
]

Upvotes: 1

Views: 76

Answers (1)

Tobias S.
Tobias S.

Reputation: 23885

The solution to this problem looks like this:

function expressions<
  F extends any[]
>(...functions: [...{
    [K in keyof F]: [
      (...arg: F[K]) => any, 
      ...F[K]
    ]
  }]
){
  return functions
}

We use the generic type F to store the the types of the arguments of the passed functions in a tuple. The type of each function will be stored in F[K] which we will also use to type the second element in the tuple.

Let's see if it works:

let queue = expressions(
    [ (a:number, b: string) => {}, 1, "abc"],
    [ (a:boolean) => {}, true],
    [ (a: Date) => {}, new Date()],
    [ (a: number) => {}, false] // Error: Type 'boolean' is not assignable to type 'number'
)

let queue2 = expressions(  
    [ (a: number) => {} ] // Error: 'unknown' is not assignable to type 'number'
)

Playground

Upvotes: 2

Related Questions