Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript. Function typing

Tags:

typescript

Hello I am learning typescript and i follow this exercises: Link

I have problem understanding how should i proced with this example.

there is a code like this:

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);
}

and I am supposed to add types to this. I managed to create something like this:

export function map<T>(mapper: Function, input : T[]| any) : T[] | Function {
    if (arguments.length === 0) {
        return map;
    }
    if (arguments.length === 1) {
        return function subFunction(subInput: T[]|any): T[] | Function {
            if (arguments.length === 0) {
                return subFunction;
            }
            return subInput.map(mapper);
        };
    }
    return input.map(mapper);
}

typescript do not returns compilation errors now but i still fail the test. I do not understand what it is expected from me to make this work.

I can check suggested answer but whe i look at it this is dark magic for me.

I could check test.ts for what is expected but notation like const mapResult1 = map()(String)()([1, 2, 3]); is looking very strange for me.

like image 804
szpic Avatar asked Jan 20 '26 13:01

szpic


2 Answers

Well it's just some use case of the map function:

  • When writing map() the result is map itself
  • The next step is map(String) which returns subFunction
  • The next step is subFunction() which returns subFunction itself
  • The last step is subFunction([1, 2, 3]) which returns the result of the expression [1, 2, 3].map(String): ['1', '2', '3']

The type of this result is a string[] because every element of the final array is the result of String being called as a function is always a string. Your typings are supposed to resolve this type without even executing the code.

Here is one possible solution found in the source code of the exercises you are doing. They actually revamped the implementation to make it "functional" (return a new function for every unresolved parameter)

function toFunctional<T extends Function>(func: T): Function {
    const fullArgCount = func.length;
    function createSubFunction(curriedArgs: unknown[]) {
        return function(this: unknown) {
            const newCurriedArguments = curriedArgs.concat(Array.from(arguments));
            if (newCurriedArguments.length > fullArgCount) {
                throw new Error('Too many arguments');
            }
            if (newCurriedArguments.length === fullArgCount) {
                return func.apply(this, newCurriedArguments);
            }
            return createSubFunction(newCurriedArguments);
        };
    }
    return createSubFunction([]);
}

interface MapperFunc<I, O> {
    (): MapperFunc<I, O>;
    (input: I[]): O[];
}

interface MapFunc {
    (): MapFunc;
    <I, O>(mapper: (item: I) => O): MapperFunc<I, O>;
    <I, O>(mapper: (item: I) => O, input: I[]): O[];
}

/**
 * 2 arguments passed: returns a new array
 * which is a result of input being mapped using
 * the specified mapper.
 *
 * 1 argument passed: returns a function which accepts
 * an input and returns a new array which is a result
 * of input being mapped using original mapper.
 *
 * 0 arguments passed: returns itself.
 */
export const map = toFunctional(<I, O>(fn: (arg: I) => O, input: I[]) => input.map(fn)) as MapFunc;
like image 180
Guerric P Avatar answered Jan 22 '26 20:01

Guerric P


Here is a simple solution:

interface SubFunction<I, O> {
  (): SubFunction<I, O>;
  (input: I[]): O[];
}

function map<I, O>(): typeof map;
function map<I, O>(mapper: (i: I) => O): SubFunction<I, O>;
function map<I, O>(mapper: (i: I) => O, input: I[]): O[];

function map<I, O>(mapper?: (i: I) => O, input?: I[]) {
  if (mapper && input) {
    return input.map(mapper);
  }
  if (mapper) {
    const subFunction = (input?: I[]) => input ? input.map(mapper) : subFunction;
    return subFunction;
  }
  return map;
}

const mapResult1 = map()(String)()([1, 2, 3]);
  • It uses a helper type for the SubFunction.
  • It uses idiomatic modern TypeScript (no function apart from top-level, no arguments)
  • It uses function overload to have different return types.

Playground

like image 23
Clashsoft Avatar answered Jan 22 '26 19:01

Clashsoft



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!