Typescript: The Art of Typing Functions

Hey there! I've been diving into learning typescript and have been working through some exercises. If you're curious, you can check out the exercise here.

I'm a bit stuck on grasping how to approach this particular example.

Here's the code snippet that's giving me trouble:

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 adding types to it like so:

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

Although TypeScript isn't throwing any compilation errors, I'm still unable to pass the test cases. I'm feeling a bit lost on what exactly is expected for this to work.

I could peek at the provided solution, but honestly, it feels like magic to me at this point.

Looking into the test.ts file for clues seems daunting with expressions like

const mapResult1 = map()(String)()([1, 2, 3]);
. It's all a bit overwhelming!

Answer №1

Here is an interesting example showcasing the usage of the map function:

  • First, when you write map(), the result is the map function itself.
  • Next, calling map(String) returns a subFunction.
  • Following that, invoking subFunction() results in the return of the subFunction itself.
  • Lastly, by using subFunction([1, 2, 3]), we get the value of [1, 2, 3].map(String): ['1', '2', '3']

The resulting type from this operation is a string[], as each element in the final array comes from the invocation of String which always produces a string. The typings are designed to determine this without actually running the code.

In the source code for the exercises being worked on, there is a solution that has been enhanced to be more "functional" (creating 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[];
}

/**
 * If 2 arguments are passed: a new array is returned
 * after mapping the input with the specified mapper.
 *
 * If 1 argument is passed: a function is returned that takes
 * an input and returns a new array obtained by mapping the input
 * with the original mapper.
 *
 * If no arguments are passed: it returns itself.
 */
export const map = toFunctional(<I, O>(fn: (arg: I) => O, input: I[]) => input.map(fn)) as MapFunc;

Answer №2

Here is a straightforward 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 utilizes a helper type for the SubFunction.
  • Embraces modern TypeScript practices (no function except top-level, no arguments)
  • Utilizes function overload to handle differing return types.

Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The persistence of Ionic Native Storage seems to be lost upon app rebuilding

After using ionic 3 native storage to store a value with the line of code below: this.storage.set('myValue', this.value); I proceeded to rebuild the app and tried to retrieve that value, but it came back as null: this.storage.get('myValue ...

The 'import type' declaration cannot be parsed by the Babel parser

Whenever I attempt to utilize parser.parse("import type {Element} from 'react-devtools-shared/src/frontend/types';", {sourceType: "unambiguous"}); for parsing the statement, I come across an error stating Unexpected token, exp ...

Set the input of a component in Angular to determine its width

I am working on a basic angular component. Here is the code snippet: <div class="k-v-c" fxFlex fxLayout = "row" > <div class="k-c" fxFlex = "widthOfTable" > {{ key | translate }} </div> < div class="separator" > ...

Issues arising from TypeScript error regarding the absence of a property on an object

Having a STEPS_CONFIG object that contains various steps with different properties, including defaultValues, I encountered an issue while trying to access the defaultValues property from the currentStep object in TypeScript. The error message indicated tha ...

The function "overloading" of the union type is not functioning properly

I attempted to "overload" a function by defining it as a union function type in order to have the type of the input parameter dictate the type of the `data` property in the returned object. However, this resulted in an error: type FN1 = (a: string) => { ...

Should loaders be utilized in an Angular application?

Webpack configuration allows the use of various loaders, such as file-loader, html-loader, css-loader, json-loader, raw-loader, style-loader, to-string-loader, url-loader, and awesome-typescript-loader. Does Angular have built-in knowledge of loaders with ...

TypeORM - Establishing dual Foreign Keys within a single table that point to the identical Primary Key

Currently, I am working with TypeORM 0.3.10 on a project that uses Postgres. One issue I encountered is while trying to generate and execute a Migration using ts-node-commonjs. The problem arises when two Foreign Keys within the same table are referencing ...

TypeScript does not verify keys within array objects

I am dealing with an issue where my TypeScript does not flag errors when I break an object in an array. The column object is being used for a Knex query. type Test = { id: string; startDate: string; percentDebitCard: number, } const column = { ...

Developing a declaration for an unnamed function in a JavaScript file

module.exports = function (argument1, argument2) { return { myFunction } function myFunction () { ... } } What is the process for creating a TypeScript declaration file for this specific JavaScript file? ...

"Using Jest's mock to expect a different set of arguments passed with toHave

Currently, I am experimenting with the functionality within TripService: async createAndMapTrips<T extends ITripEntity>( driver: DriverEntity, entities: T[], intervalInMinutes: number = 10, ): Promise<[TripEntity[], T[ ...

Guidelines for crafting an intricate selector by utilizing mergeStyleSets and referencing a specific class

I'm currently in the process of developing a web application using ReactJS. In my project, I have the following code: const MyComponent = (props: { array: Array<Data> }) => { const styles = mergeStyleSets({ container: { ...

Ways to update HTML values using Events

I am attempting to retrieve a value using Events (ionic) once it listens for my 'onSMSArrive' event. I am able to successfully retrieve the value from the events. However, I am encountering an issue where the value is not updating in my app. Bel ...

Utilizing a map in conjunction with template literal types results in an error being thrown

I am currently working on implementing the documentation provided here. This is the code snippet I have: enum InputFieldName { ReactionsCheckbox = 'reactionsEnabled', SettingsCheckbox = 'settingsEnabled', } type PropEventSource ...

What is the process for retrieving the API configuration for my admin website to incorporate into the Signin Page?

My admin website has a configuration set up that dynamically updates changes made, including the API. However, I want to avoid hardcoding the base URL for flexibility. How can I achieve this? Please see my admin page with the config settings: https://i.st ...

Issue with binding background images to DIV elements in Angular 4 CSS

Here is a template example: <div [style.background-image]="profileImage" ></div> In the TypeScript file: We declare private profileImage: any; and use DomSanitizer for security. Fetching photo from service: We set this.profileImage using b ...

employing flush for lodash's throttle wrapper

When using TypeScript with JavaScript and Angular: I am trying to use the throttle decorator from lodash to limit an API call while a user is navigating around the page, ensuring that it fires before they leave the site. In my TypeScript constructor, I h ...

Problem with timing in token interceptor and authentication guard due to injected service

Currently, I am facing an issue where I need to retrieve URLs for the auth service hosted on AWS by reading a config.json file. In order to accomplish this, I created a config service that reads the config file and added it as a provider in app.module. Eve ...

ngx-datatable is unable to assign a new row model when using [rows]="rows | async"

I am currently working with Angular 2 (version 4.1.0), angularfire2, and ngx-datatable. Within my component, I have a datatable that renders an Observable based on Angularfire2. Users can filter the data in the name column using a similar implementation t ...

Ways to pass styling properties to a nested component

I am working on a component that includes an input field: <mat-form-field appearance="standard"> <mat-label >{{label}}<span>*</span></mat-label> <input [type]="type" <span matSuffix>{{suffix} ...

Issue with React Context: The type 'Dispatch<SetStateAction<GiftsType>>' cannot be assigned to type '(arr1: string[], arr2: string[]) => void'

I'm currently working on a project in React+TS where I need to create a context that takes two string arrays and updates an object's state with these arrays. I keep encountering a title typo error in the setChoices function inside the return stat ...