Emman
Emman

Reputation: 4201

How to dispatch calculation to relevant function according to input's type?

When writing a function in TypeScript, is there a way to let the function infer what to do according to the input's type?

For example, let's say that I have a function that calculates the maximum value.

While these two functions (calcMaxArr() & calcMaxObj()) work perfectly well, I wonder whether I could unify them somehow under one function calcMax(). That is, I wonder whether calcMax() can infer, from the input's type, whether to defer/dispatch the calculation to calcMaxArr() or to calcMaxObj().

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

Is there a built-in feature in typescript for such kind of a task?


EDIT


I ran across this tweet that demonstrates a similar procedure but in python. For those who know python, this makes a useful analogy in my opinion.


EDIT 2


I've now learned that what I am asking about actually has a name: My calcMax() is a generic function, and one built-in implementation of such generic functions is seen in Common Lisp, for example.

Upvotes: 1

Views: 390

Answers (3)

Zheng Bowen
Zheng Bowen

Reputation: 409

Is function overload what you need?

https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads

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

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

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

Upvotes: 0

paolostyle
paolostyle

Reputation: 1988

Long story short: what you want to achieve is not possible. TypeScript gets compiled to JavaScript and all data about types is not available in the runtime, it's effectively removed. So you have to implement runtime checks yourself (solution by @Nalin Ranjan is just one way to do it), TypeScript can't do that for you.

However I can suggest one improvement to the solution provided by @Nalin Ranjan - use overloads. The provided solution has a big problem - the return type is always a union type, in other words:

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

This would fail because type of k is string | number and .length property is only available on strings.

To fix it you should, like I mentioned, use overloads, like this:

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 example above will work properly. In this particular scenario it might look verbose, but let's imagine you have another function, like:

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

Then you just have to look at signatures. Since both calcMaxSet and calcMaxArr return the same type you can add it to the existing overload, same would be true if calcMaxObj actually returned number (but then you wouldn't even need an overload):

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

Upvotes: 2

Nalin Ranjan
Nalin Ranjan

Reputation: 1782

Does this work for you...

const calcMax = (inputs: number[] | Record<string, number>): number | string => {
  if (Array.isArray(inputs)) {
    return calcMaxArr(inputs);
  }
  return calcMaxObj(inputs);
}

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

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

Illustration

"use strict";
const calcMax = (inputs) => {
  if (Array.isArray(inputs)) {
    return calcMaxArr(inputs);
  }
  return calcMaxObj(inputs);
};

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

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

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


WYSIWYG => WHAT YOU SHOW IS WHAT YOU GET

Upvotes: 1

Related Questions