What is the best way to assign calculations to the appropriate function based on the type of input provided?

Is there a way in TypeScript to create a function that can automatically determine what action to take based on the type of input it receives?

For instance, let's consider a function that calculates the maximum value.

  • In scenario A, if the input is a numeric array (i.e., type: number[]), I want the function to return the max value. This can be achieved with:

    const calcMaxArr = (arr: number[]): number => {
      return Math.max(...arr) 
    }
    
  • In scenario B, if the input data is an object and I want to find the key corresponding to the largest value, the function should do this:

    const calcMaxObj = (obj: Record<string, number>): string => {
       return Object.keys(obj).reduce((a, b) => obj[a] > obj[b] ? a : b); 
    }
    

Although calcMaxArr() and calcMaxObj() work well individually, I am interested in combining them into one single function called calcMax(). The challenge is for calcMax() to intelligently decide whether to use calcMaxArr() or calcMaxObj() based on the input type.

If type: number[] -> calcMaxArr()
If type: Record<string, number> -> calcMaxObj()

Does TypeScript offer a feature that supports this kind of functionality?


EDIT


A tweet showcasing a similar concept in Python was brought to my attention. It might serve as a helpful analogy for those familiar with Python.


EDIT 2


I recently discovered that what I'm describing is essentially a generic function. Common Lisp, for example, offers built-in support for defining generic functions and methods.

Answer №1

In short: achieving what you desire is not feasible. When TypeScript is compiled to JavaScript, all type information is stripped away and not available at runtime. Therefore, you will need to incorporate runtime checks on your own (as illustrated by @Nalin Ranjan's solution), as TypeScript cannot handle this for you.

Nevertheless, I propose an enhancement to the solution provided by @Nalin Ranjan - utilize overloads. The initial solution faces a major issue where the return type always ends up being a union type, indicating:

let k = calcMax({twelve: 12, ten: 10});
console.log(k.length); // error even though we expect string

This would result in failure because the type of k becomes string | number, and the .length property is exclusive to strings only.

To rectify this, as mentioned earlier, implementing overloads like the following would be beneficial:

function calcMax(arr: number[]): number;
function calcMax(obj: Record<string, number>): string;

function calcMax(arg: any): any {
    if (Array.isArray(arg)) {
        return calcMaxArr(arg);
    }
    return calcMaxObj(arg);
}

Now, the previous example will work correctly. While it might seem verbose in this specific scenario, consider another function like:

const calcMaxSet = (set: Set<number>): number => { 
  return 0; // implementation doesn't matter
}

All you have to do is examine the signatures. Since both calcMaxSet and calcMaxArr produce the same type, you can append it to the existing overload. This holds true if calcMaxObj ultimately returns number (although overdrafts would be unnecessary in that case):

function calcMax(arr: number[] | Set<number>): number;
function calcMax(obj: Record<string, number>): string;

function calcMax(arg: any): any {
    if (Array.isArray(arg)) {
        return calcMaxArr(arg);
    } else if (arg instanceof Set) { // runtime check
        return calcMaxSet(arg);
    }
    return calcMaxObj(arg);
}

let k = calcMax(new Set([1,2,3]));
k.toFixed(); // k is a number so this is legal

Answer №2

Is this solution suitable for your needs?

const determineMaximum = (inputs: number[] | Record<string, number>): number | string => {
  if (Array.isArray(inputs)) {
    return findMaxInArray(inputs);
  }
  return findMaxInObject(inputs);
}

const findMaxInArray = (arr: number[]): number => {
  return Math.max(...arr) // https://stackoverflow.com/a/39106546/6105259
}

const findMaxInObject = (obj: Record<string, number>): string => {
  return Object.keys(obj).reduce((a, b) => obj[a] > obj[b] ? a : b); //https://stackoverflow.com/a/27376421/6105259
}

Visualization

"use strict";
const determineMaximum = (inputs) => {
  if (Array.isArray(inputs)) {
    return findMaxInArray(inputs);
  }
  return findMaxInObject(inputs);
};

const findMaxInArray = (arr) => {
  return Math.max(...arr); // https://stackoverflow.com/a/39106546/6105259
};

const findMaxInObject = (obj) => {
  return Object.keys(obj).reduce((a, b) => obj[a] > obj[b] ? a : b); //https://stackoverflow.com/a/27376421/6105259
};

console.log(determineMaximum([10, 8, 12, 3]));
console.log(determineMaximum({
  "10": 10,
  "8": 8,
  "12": 12,
  "3": 3
}));


WYSIWYG => WHAT YOU SHOW IS WHAT YOU GET

Answer №3

Do you require function overload for your solution?

Explore more about function overloads here

const findMinMaxArr = (arr: number[]): number => {
  return Math.min(...arr) // Read more at https://stackoverflow.com/a/39106546/6105259
}

const findMinMaxObj = (obj: Record<string, number>): string => {
   return Object.keys(obj).reduce((a, b) => obj[a] > obj[b] ? a : b); //Refer to this post: https://stackoverflow.com/a/27376421/6105259
}

function findMinMax(arr: number[]): number;
function findMinMax(obj: Record<string, number>): string;
function findMinMax(input: number[] | Record<string, number>) {
  if(Array.isArray(input)) {
    return findMinMaxArr(input);
  }
  else {
    return findMinMaxObj(input);
  }
}

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

Is it possible to retrieve and display an object from an API using its unique ID?

I created a straightforward application. The main page displays a list of movies fetched using an API, and upon clicking on a particular movie, it leads to a new page with detailed information about that movie. On the details page, another API call is ma ...

How to automatically scroll to the most recently added element in an *ngFor loop using Angular 2

In my web page, I have a dynamic list rendered using an ngFor loop. Users can add or remove elements from this list by clicking on a button. What I want to achieve is to automatically scroll the browser view to the latest element added when a user clicks o ...

Google's reCAPTCHA issue: systemjs not found

Currently, I am attempting to integrate Google's reCAPTCHA into an Angular application by following a helpful tutorial found here. However, I have encountered a problem as the systemjs.config.js file seems to be missing from my Angular CLI project. An ...

Once an element is focused, the selection range's getClientRects method will result in a list that is void of any elements

Utilizing this code snippet to target the specified element const selectEnd = (element: HTMLElement, position: number) => { element.focus(); const range = document.createRange(); const sel = window.getSelection(); if (position !== -1) { ...

Join the nested Observables array

I have an array that holds objects, each containing two properties where one is an observable value. let myArray = [{def: 'name1', value: EventEmitter_}, {def: 'name2', value: EventEmitter_}] My goal is to subscribe to the observables ...

The error message "index.ts is not a module when imported in Typescript"

I've encountered an issue while attempting to import index.ts within a subfolder that contains other imports. Unfortunately, I keep receiving a TypeScript error. Check out the full repository here: https://github.com/Shavindra/webpack-react-sw (5,32 ...

Express did not cause Jest to exit one second after completing the test run

I'm currently utilizing JEST to conduct unit tests on my express routes. When I run the yarn test, all of my test cases pass successfully. However, an error occurs: Jest did not exit one second after the test run has completed. This typically indic ...

Meeting the operator does not limit the variable type

Encountering issues with type narrowing using the satisfies operator. The goal is to specify that the name property of br should be of type "br": type RichTextCustomElement = { name: string; Button: React.ComponentType<any>; El ...

Retrieve the property values of `T` using a string key through the `in

Having trouble accessing a property in an object using a string index, where the interface is defined with in keyof. Consider the following code snippet: interface IFilm { name: string; author: string; } type IExtra<T extends {}> = { [i ...

You must provide a secret or key in order to use the JwtStrategy

I have encountered the following error and I am unsure of its cause. Can you assist me? ERROR [ExceptionHandler] JwtStrategy requires a secret or key TypeError: JwtStrategy requires a secret or key at new JwtStrategy (C:\Users\wapg2\OneDriv ...

React: Implementing a Method to Reset the Value of a React-Select upon Selection Change

In my layout, I have two side-by-side components that serve as drop-down lists using the React-Select library: <SelectField options={props.directories} placeholder="Directory" onChange={props.onDirectoriesChange} value={props.directoryCodeName ...

Vue.js is unable to recognize this type when used with TypeScript

In my code snippet, I am trying to set a new value for this.msg but it results in an error saying Type '"asdasd"' is not assignable to type 'Function'. This issue persists both in Visual Studio and during webpack build. It seems like Ty ...

Comparing tsconfig.json and tsconfig.build.json: what sets them apart?

Guides like those found at 1 and 2 often recommend having two separate files - tsconfig.json and tsconfig.build.json - at the root level of an NPM monorepo for TypeScript projects. What are the distinctions between these files? Is it possible to consolida ...

Instructions on how to post an array by its ID when the value changes in the form, correspond with the ID

Whenever I change the value in the radio button within a form popup, I want to trigger this action. Below is the corresponding HTML code: <ng-container cdkColumnDef="injected"> <mat-header-cell *cdkHeaderCellD ...

Is there a way to prevent passing the mouseover event to children elements while still allowing the parent element to respond to the event across its entire area?

I am working with a dynamically generated list that contains complex components which need to perform actions on mouseover. With Angular, I attempted to implement this functionality by using (mouseover)="onhover($event)" and (mouseout)="onhover($event)" o ...

R code transformed into a powerful function

Seeking advice on how to efficiently run a long script involving data manipulation and estimation multiple times with different sets of inputs, similar to a function setup. The script generates plots and saves estimates to a csv file, with the focus on fun ...

Can you explain the significance of the additional pipeline in the type declaration in TypeScript?

Just recently, I defined a type as follows- interface SomeType { property: { a: number; b: string; } | undefined; } However, upon saving the type, vscode (or maybe prettier) changes it to- interface SomeType { property: | { a: nu ...

Troubleshooting a NextJs/TS problem with importing ESM modules

Click here for the Code Sandbox I'm currently in the process of transitioning some code to NextJS 11 / Webpack 5, including certain modules that now exclusively support ECMAScript Modules (esm). Prior to the upgrade, I could easily export all files ...

What design should be used for a class with two interconnected attributes?

I am looking to develop a customizable macro item that can be edited by the user. Each macro item will have a defined type and event value. There are three types of items: pressKey, releaseKey, and delayInMs. For pressKeyEvent and releaseKeyEvent, I wan ...

Unable to execute Nodemon in a TypeScript node project build on Windows

My Node project is created using Typescript. However, I am encountering an issue where there are three scripts in the package.json file, but when I try to run them, it does not work as expected. The strange thing is that the project runs perfectly fine on ...