Bene
Bene

Reputation: 934

Repeat multiple function arguments

Is there a way in Typescript to dynamically type a function which takes 2 arguments by default but should be able to handle these arguments reapeatedly.

// should be allowed
myFunction(paramA, paramB)
myFunction(paramA, paramB, paramA1, paramB1)
myFunction(paramA, paramB, paramA1, paramB1, paramA2, paramB2)
...

// should produce an error
myFunction(paramA, paramB, paramA1)

It is of course possible to package the params into an object and accept an iterable of this object

interface Param { a: string; b: number; };
function myFunction(...params: Param[]);

but I'd like to achieve it without the need for an object

another possibility would be to add method overloads but this limits the amount of parameters to how many overloads I offer and still allows for passing an odd amount of parameters without type error

function myFunction(paramA: string, paramB: number);
function myFunction(paramA: string, paramB: number, paramA1: string, paramB1: number);
function myFunction(paramA: string, paramB: number, paramA1?: string, paramB1?: number);

Is there some trickery to elegantly allow for the wanted behaviour?

Upvotes: 3

Views: 688

Answers (1)

I believe this question is related to this. My solution works only with TS >=4.4

In fact we need to allow an even number of arguments. This is the most complicated part.

Let's define our allowed tuple item:

type Elem = number | string | boolean;


type Tuple = [Elem, Elem]

Next, we need to model a tuple with infinity and even length.

While we can model recursive object type, it seems to be impossible (or I just don't know how) to model a tuple with even infinity length. Because how smth infinity might have an even length?

I don't have enough Math background even to understand it.

I decided to model a tuple with even length as longest as possible.

Consider this example:



type Elem = number | string | boolean;


type Tuple = [Elem, Elem]

/**
 * It is not allowed to increase this number, at least in TS.4.4
 */
type MAXIMUM_ALLOWED_BOUNDARY = 110

type Mapped<
    Arr extends Array<unknown>,
    Result extends Array<unknown> = [],
    Original extends any[] = [],
    Count extends ReadonlyArray<number> = []
    > =
    /**
     * If Length reached the MAXIMUM - return Result
     */
    (Count['length'] extends MAXIMUM_ALLOWED_BOUNDARY
        ? Result
        /**
         * If we passed an empty Arr - return empty tuple
         */
        : (Arr extends []
            ? []
            /**
             * If Arr has only one element - infer it
             */
            : (Arr extends [infer H]
                /**
                 * and Return Result concatenated with infered element and recursive call
                 */
                ? [...Result, H, ...([] | Mapped<Original, [], [], [...Count, 1]>)]
                /**
                 * If Arr has more than one element
                 */
                : (Arr extends [infer Head, ...infer Tail]
                    /**
                     * Call Mapped recursively with appropriate parameters
                     */
                    ? Mapped<[...Tail], [...Result, Head], Arr, [...Count, 1]>
                    : Readonly<Result>
                )
            )
        )
    )

// [Elem, Elem] | [Elem, Elem, Elem, Elem] | [Elem, Elem, Elem, Elem, Elem, Elem] | [Elem, ... 6 more ..., Elem] | ... 50 more ... | [...]
type CaseCallParams = Mapped<Tuple>

function myFunction<Fst, Scd>(...params: Mapped<[Fst, Scd]>) {

}

myFunction(1, true) // ok
myFunction(1, true, 42, false) // ok

myFunction(1, 1, 2, 2) // ok
myFunction(1, 1, 2, 2, 3, 3) // ok

myFunction(1, true, 1, 1) // error
myFunction(1) // error
myFunction(1, 1, 2) // error

Playground

I hope 110 elements (max allowed number of arguments) is enough for your case

Here you can find my article with some explanation and tests

Upvotes: 2

Related Questions