In our journey through TypeScript generics, we’ve covered the basics, interfaces, and classes. Now, it’s time to explore advanced concepts by combining generics with higher-order functions. These functions, which take other functions as arguments or return them, unlock a new level of flexibility and complexity.
Understanding Higher-Order Functions
Higher-order functions are functions that operate on other functions, either by taking functions as arguments or returning them as results. They are a fundamental concept in functional programming and can lead to cleaner, more modular, and more versatile code.
In TypeScript, higher-order functions can be made even more powerful when combined with generics. Let’s see how this works.
Creating a Generic Map Function
One common higher-order function is map
, which applies a given function to each element of an array and returns a new array with the results. With generics, we can create a generic map
function that works with various data types.
function customMap<T, U>(array: T[], mapper: (item: T) => U): U[] { const result: U[] = []; for (const item of array) { result.push(mapper(item)); } return result; } const numbers = [1, 2, 3, 4, 5]; const doubled = customMap(numbers, (num) => num * 2); // Returns [2, 4, 6, 8, 10] const words = ["apple", "banana", "cherry"]; const lengths = customMap(words, (word) => word.length); // Returns [5, 6, 6]
function customMap<T, U>(array: T[], mapper: (item: T) => U): U[] {
: This is a higher-order function namedcustomMap
. It takes two type parameters,T
andU
, representing the input and output data types. It also takes two parameters:array
, which is an array of typeT
, andmapper
, a callback function that takes an item of typeT
and returns an item of typeU
.const result: U[] = [];
: Inside the function, an empty arrayresult
of typeU
is created to store the mapped values.for (const item of array) { result.push(mapper(item)); }
: This loop iterates over each item in thearray
. For each item, themapper
function is called to transform it, and the result is pushed into theresult
array.return result;
: The function returns theresult
array, which now contains the mapped values of typeU
.
Usage of customMap
:
const numbers = [1, 2, 3, 4, 5];
: An arraynumbers
containing numbers is defined.const doubled = customMap(numbers, (num) => num * 2);
: ThecustomMap
function is called withnumbers
as the input array and a callback function(num) => num * 2
. This callback function doubles each number in the array. The result is assigned to thedoubled
variable, which will contain[2, 4, 6, 8, 10]
.const words = ["apple", "banana", "cherry"];
: An arraywords
containing strings is defined.const lengths = customMap(words, (word) => word.length);
: ThecustomMap
function is called withwords
as the input array and a callback function(word) => word.length
. This callback function returns the length of each word in the array. The result is assigned to thelengths
variable, which will contain[5, 6, 6]
.
In this example, the customMap
function is a higher-order function that uses generics to allow mapping of arrays with different data types. It provides flexibility and type safety, and it’s used to perform mapping operations on arrays of numbers and strings.
Applying Generics to Callback Functions
Higher-order functions often take callback functions as arguments. With generics, we can make sure that the callback function’s input and output types align with the higher-order function’s expectations.
function findFirst<T>(array: T[], predicate: (item: T) => boolean): T | undefined { for (const item of array) { if (predicate(item)) { return item; } } return undefined; } const numbers = [1, 3, 5, 7, 9]; const isEven = (num: number) => num % 2 === 0; const firstEven = findFirst(numbers, isEven); // Returns 1
In this example, findFirst
is a generic higher-order function that finds the first element in an array that satisfies a given predicate. The predicate
callback function ensures that the input matches the array’s data type and the output is a boolean.