Richard Coates
Richard Coates

Reputation: 21

Enforce function parameter length to be same as array / tuple length

I'm writing a function that takes two parameters, A and B. A is an array of strings with length L. B is a function that takes L numbers as parameters and returns a number.

const acceptSameLength = (stringArray: string[], fnc: (...inputNums: number[]) => number) => ({
    names: stringArray,
    calcFunc: fnc
})

I'd like to enforce L being consistent between the two.

acceptSameLength(['foo'], (a: number) => 123) // Should work fine
acceptSameLength(['foo'], (a: number, b: number) => 123) // Should error
acceptSameLength(['foo', 'bar'], (a: number) => 123) // Should error

Because the function takes numbers as the parameters, but the array is of strings, I can't just use

T extends string[], U extends T

which does work for instances where the types are consistent, and my attempts of using a union type with Exclude also failed, but I didn't pursue that line of reasoning for a particularly long time.

The following fails as the spread operator can't be used. However, the logic for deriving the length of the tuple works fine.

const myAttempt = <
    T extends (readonly [] | readonly unknown[]) & (number extends T["length"] ? readonly [] : unknown),
>(
    categories: T,
    fnc: (...args: { [K in keyof T]: number }) => number,
) => {
    return {
        names: categories,
        calcFunc: fnc,
    };
};

The function is invoked with a known, hard-coded set of parameters, so using tuples is okay, but in the near future there will be instances where this is used with lengths of 20-30 and I'd like to avoid hardcoding each different overload (plus, it makes the code a lot cleaner!).

Upvotes: 1

Views: 201

Answers (1)

Richard Coates
Richard Coates

Reputation: 21

My solution to this came via realising that there was an inverse way of looking at the problem: instead of trying to restrict the parameters of the function by referencing the array, we could instead restrict the array, by referencing the parameters of the function!

function myAttempt<T extends number[]>(
    categories?: string[] & { [K in keyof T]: string },
    calcFunc?: (...args: T) => number,
) {
    return {
        category: category,
        categories: categories,
        calcFunc: calcFunc as (...args: number[]) => number,
    };
}

As you can see here, this fulfils what I was looking for.

Upvotes: 0

Related Questions