In Typescript, define a type for an array where the first element is more specific than the rest
In current versions of Typescript this is possible using array spread:
type FAs = [Function, ...Array<{}>]
It supports any length from 1 to n (the first element is required).
If you define
type FAs = [Function, {}];
Then values of type FAs
will require a first element of type Function
, a second element of type {}
, and succeeding elements of Function | {}
. That is how TypeScript literal array types work. From the TS docs:
When accessing an element outside the set of known indices, a union type is used instead:
This should do everything you want except for the fact that you will be able to pass in a Function
-typed value as the third element etc. of the array. But actually that would be the case anyway, since Function
is compatible with {}
.
There is no way around this. There is no way in TS to define an array type where the first n elements are of some specific type(s), and there are an arbitrary number of remaining elements of some other specific type.
I also wonder if the type system supports what is arguably an arbitrary-length tuple.
Actually, the type system only supports arbitrary-length tuples. If you say
type Tuple = [number, number];
this type is compatible with any array, of length two or greater, that contains numbers. If you say
type Tuple = [string, number];
this type is compatible with any array, of length two or longer, that has a string as its first element, a number as its second, and either a string or number as its third etc. I would not call the reasons for this behavior "computer-science based"; it's more a matter of what it's feasible for TS to check.
An alternate approach
interface Arglist {
[index: number]: object;
0: Function;
}
const a1: Arglist = [func];
const a2: Arglist = [22]; // fails
const a3: Arglist = [func, "foo"]; // fails
const a4: Arglist = [func, obj];
const a5: Arglist = [func, obj, obj];
I also wonder if the type system supports what is arguably an arbitrary-length tuple.
Since TS 4.0, you can use variadic tuple types.
For example, assert [func, ...<proper func args>]
in a type-safe way:
type FAs<A extends unknown[], R> = [(...args: A) => R, ...A]
const myCaller = <A extends unknown[], R>([fn, ...args]: FAs<A, R>) =>
fn.apply(null, args)
Example:
const fn1 = (a1: string, a2: number) => true
const r1 = myCaller([fn1, "foo", 42]) // OK, r1 has type `boolean`
const r2 = myCaller([fn1, "foo", "bar"]) // error, `bar` has wrong type
const r3 = myCaller([fn1, "foo"]) // error, not enough arguments
Playground