Home > Net >  Dynamic TypeScript signature for optional argument
Dynamic TypeScript signature for optional argument

Time:11-06

Alright so don't ask why but I am trying to implement a reduce method on subclass of Map:

class MapArray<A, B> extends Map<A, B> {

    reduce<T = [A, B]>(f: (prev: T, next: [A, B]) => any, initialVal?: T) {

        let prev = arguments.length > 1 ? arguments[1] : nah;
        let index = 0;

        for (const [k, v] of this) {

            if (prev === nah) {
                prev = [k, v];
                continue;
            }

            prev = f(prev, [k, v]);

        }

        return prev;

    }

}

it's not the prettiest thing, but it basically works. The problem is the typing. If I do:

new MapArray<string,boolean>().reduce((a,b) => 'whatever');

versus:

new MapArray<string,boolean>().reduce((a,b) => 'whatever', []);

I want it to able to capture the type of the 2nd argument []

using my invalid version of TS, I tried something like this:

 type X = (if initialVal ? typeof initialVal : [A, B])
 reduce<R = any, T = X>(f: (prev: T, next: [A, B]) => R, initialVal?: T);

this is obviously completely wrong, but hopefully you get the idea, doubt it's possible though. Does anyone know if I can capture the type of initialVal if it's passed, and if not, default to [A,B]?

here is the problem: TS playground

It will compile but "a is not iterable" (a as in first argument to the reduce callback)...a is not iterable because a is 55.

CodePudding user response:

A solution may be to use undefined instead of your nah and method overloading:

class MapArray<A, B> extends Map<A, B> {

    reduce(f: (prev: [A, B], next: [A, B]) => any): [A, B];
    reduce<T>(f: (prev: T, next: [A, B]) => any, initialVal: T): T;

    reduce<T>(f: (prev: T | [A, B], next: [A, B]) => any, initialVal?: T) {

        let prev: [A, B] | T | undefined = initialVal;
        let index = 0;

        for (const [k, v] of this) {

            if (prev === undefined) {
                prev = [k, v];
                continue;
            }

            prev = f(prev, [k, v]);

        }

        return prev;

    }

}

new MapArray<string,boolean>().reduce((a,b) => 'whatever');
new MapArray<string,boolean>().reduce((a,b) => 'whatever', []);

playground

I see that this is quite similar to @Alexander Mills solution.

This is intended to be as close as possible with your original code, but I would like to point out that the return type of the function f should be the same as prev and in this case the returned value of your example function (the string "whatever")doesn't work out.

CodePudding user response:

This doesn't 100% answer the original question, but it might help someone.

A partial solution is to use overloads like so:

reduce<R = any>(f: (prev: [A, B], next: [A, B]) => R): R;
reduce<R = any, T = any>(f: (prev: T, next: [A, B]) => R, initialVal?: T) {...}

the reason this doesn't really serve as an answer to OP is that I would like to grab the user-supplied type of initial val, instead of having to declare it with the generics parameter.

So again, looking to do something like:

reduce<R = any>(f: (prev: [A, B], next: [A, B]) => R): R;
reduce<R = any, T = typeof initialVal>(f: (prev: T, next: [A, B]) => R, initialVal?: <T>) {...}
  • Related