jbailie1991
jbailie1991

Reputation: 1345

Type and function for dividing into Tuple of variable length

Problem
I want to create a function, alongside a typing for said function, that allows me to divide an array into a tuple containing a variable number of typed arrays. The idea is call .divide on an array instance, and provide a variable number of typeguards/typeguard like functions, the output of divide would be a Tuple containing the same number of typed arrays as typeguards provided. If items do not fit any provided typeguard, they are discarded from the result. Sample usage:

const [typeAs, typeBs, typeCs] = myArr.divide(isTypeA, isTypeB, isTypeC);
const [typeFs] = myArr.divide(isTypeF)
const [typeAs, typeFs, typeDs, typeCs, typeXYZs] = myArr.divide(isTypeA,isTypeF,isTypeD,isTypeC,isTypeXYZ)
//etc

As a starting point I've attempted to create a function that divides to 3 vs. a variable length Tuple + args list. I've thrown together the following on an interface for Array:

type TGuard<S> = ((input: any) => input is S) | ((input: any) => boolean);
divide<OrigType, NewT1, NewT2, NewT3>(
            t1: TGuard<NewT1>,
            t2: TGuard<NewT2>,
            t3: TGuard<NewT3>,
        ): [NewT1[], NewT2[], NewT3[]];

and an implementation:

Array.prototype.divide = function <OGType, NewT1, NewT2, NewT3>(
    t1: TGuard<NewT1>,
    t2: TGuard<NewT2>,
    t3: TGuard<NewT3>,
): [NewT1[], NewT2[], NewT3[]] {
    const d3: [NewT1[], NewT2[], NewT3[]] = [[], [], []];
    const discards: OGType[] = [];
    this.forEach((item) => {
        t1(item) ? d3[0].push(item) : t2(item) ? d3[1].push(item) : t3(item) ? d3[2].push(item) : discards.push(item);
    });

    return d3;
};

This works but I'm at a loss on expanding this to accept a variable number of typeguards, and produce a variable number of typed arrays in a Tuple wrapper. I have several ideas for the eventual implementation, At present the main challenge I'm facing is the type signature

Upvotes: 0

Views: 36

Answers (1)

tenshi
tenshi

Reputation: 26344

If we define divide like this:

function divide<T, G extends (((item: T) => item is any) | ((item: T) => boolean))[]>(array: T[], ...guards: G): InferGuards<G>;

then we can map over G and infer the guard types:

type InferGuards<G> = {
    [K in keyof G]: G[K] extends (item: any) => item is infer U ? U[] : unknown[];
};

If the element isn't a type guard, then the resulting type should be unknown[], since we don't really know what the type should be.

Playground

Upvotes: 0

Related Questions