Reputation: 4201
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.
In scenario A, the input is a numeric array (i.e., type: number[]
), and I want it to return the max value. So I could do:
const calcMaxArr = (arr: number[]): number => {
return Math.max(...arr) // https://stackoverflow.com/a/39106546/6105259
}
In scenario B I also want to calculate the max value, but this time my input data is an object, and I want to return the key that corresponds to the largest value. In this case I could do:
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
}
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
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
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
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
}
"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