Home > Back-end >  Type declaration of a generic `map` function in TypeScript
Type declaration of a generic `map` function in TypeScript

Time:07-17

In exercise #14 of typescript-exercises you get to annotate the following function:

export function map(mapper, input) {
    if (arguments.length === 0) {
        return map;
    }
    if (arguments.length === 1) {
        return function subFunction(subInput) {
            if (arguments.length === 0) {
                return subFunction;
            }
            return subInput.map(mapper);
        };
    }
    return input.map(mapper);
}

I've tried to tightly type it using generics but failed:

type Apply<In, Out> = (element: In) => Out;

declare function subFunction<In2, Out2>(subInput: In2[]): Out2[];
declare function subFunction(): typeof subFunction;

export function map<In, Out>(): typeof map;
export function map<In, Out>(mapper: Apply<In, Out>): typeof subFunction;
export function map<In, Out>(mapper: Apply<In, Out>, input: In[]): Out[];

export function map<In, Out>(mapper?: Apply<In, Out>, input?: In[]): ((typeof map) | (typeof subFunction) | Out[]) {
    if (mapper === undefined) {
        return map;
    }
    if (input === undefined) {
        // Line 61 (the error) ahead
        return function subFunction(subInput?: In[]): ((typeof subFunction) | Out[]) {
            if (subInput === undefined) {
                return subFunction;
            }
            return subInput.map(mapper);
        };
    }
    return input.map(mapper);
}

The error is:

index.ts(61,9): error TS2322: Type '(subInput?: In[] | undefined) => Out[] | ...' is not assignable to type '{ <In2, Out2>(subInput: In2[]): Out2[]; (): typeof subFunction; } | { <In, Out>(): typeof map; <In, Out>(mapper: Apply<In, Out>): { <In2, Out2>(subInput: In2[]): Out2[]; (): typeof subFunction; }; <In, Out>(mapper: Apply<...>, input: In[]): Out[]; } | Out[]'. Type '(subInput?: In[] | undefined) => Out[] | ...' is not assignable to type '{ <In2, Out2>(subInput: In2[]): Out2[]; (): typeof subFunction; }'. Type 'Out[] | ((subInput?: In[] | undefined) => Out[] | ...)' is not assignable to type 'any[]'. Type '(subInput?: In[] | undefined) => Out[] | ...' is missing the following properties from type 'any[]': pop, push, concat, join, and 25 more.

When I change the return type of subFunction @ line 61 to any, the error disappears.

What am I doing wrong?

CodePudding user response:

I think the issue is with the use of the declare keyword. I'm not sure exactly where the problem comes in, but the keyword isn't being used correctly here. It should be used to inform the compiler that a function named subFunction exists, but that isn't the case since subFunction is actually declared within map.

You can fix this by replacing the declarations with another type which I've called SubMap<In, Out> here.

type Apply<In, Out> = (element: In) => Out;
type SubMap<In, Out> = (subInput?: In[]) => SubMap<In, Out> | Out[];

export function map<In, Out>(): SubMap<In, Out>;
export function map<In, Out>(mapper: Apply<In, Out>): SubMap<In, Out>;
export function map<In, Out>(mapper: Apply<In, Out>, input: In[]): Out[];

export function map<In, Out>(mapper?: Apply<In, Out>, input?: In[]): typeof map | SubMap<In, Out> | Out[]
{
    if (mapper === undefined)
    {
        return map;
    }
    if (input === undefined)
    {
        return function subFunction(subInput?: In[]): SubMap<In, Out> | Out[]
        {
            if (subInput === undefined)
            {
                return subFunction;
            }
            return subInput.map(mapper);
        };
    }
    return input.map(mapper);
}

Another option is to actually create subFunction outside of map, although this requires some refactoring.

type Apply<In, Out> = (element: In) => Out;

function subFunction<In, Out>(mapper: Apply<In, Out>): typeof subFunction;
function subFunction<In, Out>(mapper: Apply<In, Out>, subInput: In[]): typeof subFunction | Out[];

function subFunction<In, Out>(mapper: Apply<In, Out>, subInput?: In[]): typeof subFunction | Out[]
{
    if (subInput === undefined)
    {
        return subFunction;
    }
    return subInput.map(mapper);
}

export function map<In, Out>(): typeof map;
export function map<In, Out>(mapper: Apply<In, Out>): typeof subFunction;
export function map<In, Out>(mapper: Apply<In, Out>, input: In[]): Out[];

export function map<In, Out>(mapper?: Apply<In, Out>, input?: In[]): typeof map | typeof subFunction | Out[]
{
    if (mapper === undefined)
    {
        return map;
    }
    if (input === undefined)
    {
        return subFunction(mapper);
    }
    return input.map(mapper);
}
  • Related