TypeScript Functional Programming

Functional programming (FP) is a programming paradigm where functions are treated as first-class citizens and focus on immutability, pure functions, and avoiding side effects. TypeScript, being a superset of JavaScript, supports functional programming principles, enabling developers to write clean, modular, and reusable code.

Core Principles of Functional Programming

Before we dive into TypeScript-specific implementations, let’s look at the fundamental principles of functional programming.

Pure Functions

A pure function is a function that:

  1. Returns the same output given the same input.
  2. Has no side effects (does not alter any external state).

Pure functions are predictable and easy to test, making them foundational in functional programming.

Example:

				
					// Pure function: Doesn't modify any external state, always returns the same result
function add(a: number, b: number): number {
    return a + b;
}

console.log(add(2, 3)); // Output: 5
console.log(add(2, 3)); // Output: 5 (always returns same result for same input)
				
			

Explanation:

  • The add function takes two numbers and returns their sum. It doesn’t modify any external variables or depend on them, making it a pure function.

Output:

				
					5
5
				
			

Immutability

Immutability refers to the concept where data cannot be changed once created. Instead of modifying an object, you create a new one with the desired changes. This helps to avoid unintended side effects and makes debugging easier.

Example:

				
					const originalArray = [1, 2, 3];

// Instead of modifying original array, we return a new array
const newArray = [...originalArray, 4];

console.log(originalArray); // Output: [1, 2, 3]
console.log(newArray);      // Output: [1, 2, 3, 4]
				
			

Explanation:

  • Instead of adding an element directly to the originalArray, we create a new array using the spread operator (...). This ensures immutability.

Output:

				
					[1, 2, 3]
[1, 2, 3, 4]

				
			

First-Class Functions

In TypeScript, functions are first-class citizens, meaning they can be:

  • Assigned to variables.
  • Passed as arguments to other functions.
  • Returned from functions.

This flexibility is essential for functional programming.

Example:

				
					const greet = (name: string) => `Hello, ${name}!`;

const sayHello = greet; // Assign function to a variable

console.log(sayHello("Alice")); // Output: Hello, Alice!
				
			

Explanation:

  • The greet function is assigned to sayHello, demonstrating that functions can be treated like any other variable.

Output:

				
					Hello, Alice!
				
			

Higher-Order Functions

A higher-order function is a function that:

  • Takes one or more functions as arguments, or
  • Returns a function as its result.

Higher-order functions are one of the most powerful concepts in functional programming, enabling abstraction over behavior.

Example: Passing Functions as Arguments

				
					function applyOperation(a: number, b: number, operation: (x: number, y: number) => number): number {
    return operation(a, b);
}

const multiply = (x: number, y: number) => x * y;

console.log(applyOperation(5, 3, multiply)); // Output: 15
				
			

Explanation:

  • applyOperation takes an operation (a function) as an argument and applies it to the two numbers a and b.
  • Here, multiply is passed as an argument, so applyOperation multiplies the two numbers.

Output:

				
					15
				
			

Example: Returning Functions

				
					function createAdder(a: number) {
    return function(b: number): number {
        return a + b;
    };
}

const addFive = createAdder(5);
console.log(addFive(10)); // Output: 15

				
			

Explanation:

  • createAdder returns a function that adds a specific number (a) to any number passed as b.
  • The returned function remembers the value of a from when createAdder was initially called (this is called a closure).

Output:

				
					15

				
			

Function Composition

Function composition is the process of combining multiple functions to produce a new function. It allows the output of one function to become the input for another.

Example:

				
					const double = (x: number) => x * 2;
const square = (x: number) => x * x;

function compose<T>(f: (x: T) => T, g: (x: T) => T): (x: T) => T {
    return (x: T) => f(g(x));
}

const doubleThenSquare = compose(square, double);

console.log(doubleThenSquare(3)); // Output: 36 (3 * 2 = 6, then 6 * 6 = 36)

				
			

Explanation:

  • compose takes two functions, f and g, and returns a new function that applies g to an argument, then applies f to the result.
  • In this case, we double the input and then square it.

Output:

				
					36

				
			

Currying

Currying is a technique where a function with multiple arguments is transformed into a series of functions that each take one argument. It allows functions to be reused in a more flexible way.

Example:

				
					function multiply(a: number): (b: number) => number {
    return (b: number) => a * b;
}

const multiplyByTwo = multiply(2);

console.log(multiplyByTwo(5)); // Output: 10
console.log(multiplyByTwo(8)); // Output: 16

				
			

Explanation:

  • The multiply function is curried: instead of taking both arguments at once, it returns a function that takes the second argument.
  • This allows for partial application, where you provide only some arguments at a time.

Output:

				
					10
16
				
			

Recursion in Functional Programming

Recursion is a fundamental concept in functional programming where a function calls itself to solve smaller instances of a problem. Recursion is often used as a replacement for iterative loops in functional programming.

Example: Factorial

				
					function factorial(n: number): number {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

console.log(factorial(5)); // Output: 120

				
			

Explanation:

  • The factorial function calls itself with a smaller argument (n - 1) until n is reduced to 1.
  • This example demonstrates how recursion replaces a loop.

Output:

				
					120

				
			

Map, Filter, and Reduce

Functional programming in TypeScript heavily relies on higher-order array functions like map, filter, and reduce. These functions allow for declarative data transformations without modifying the original data.

Map

The map function transforms each element in an array based on a provided function.

Example:

				
					const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2);

console.log(doubled); // Output: [2, 4, 6, 8]

				
			

Explanation:

  • The map function applies the function n * 2 to each element of the array and returns a new array with doubled values.

Output:

				
					[2, 4, 6, 8]

				
			

Filter

The filter function returns a new array with elements that pass a specific test.

Example:

				
					const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);

console.log(evens); // Output: [2, 4]
				
			

Explanation:

  • The filter function applies the condition n % 2 === 0 to filter out all odd numbers, leaving only the even numbers.

Output:

				
					[2, 4]
				
			

Reduce

The reduce function reduces an array to a single value by accumulating the result of applying a function to each element.

Example:

				
					const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, n) => acc + n, 0);

console.log(sum); // Output: 10

				
			

Explanation:

  • The reduce function starts with an initial accumulator (0 in this case) and adds each number in the array to it, eventually returning the total sum.

Output:

				
					10
				
			

We have explored the key concepts of functional programming in TypeScript, ranging from basic principles like pure functions and immutability to more advanced topics like higher-order functions, currying, function composition, and recursion. Happy Coding!❤️

Table of Contents