Introduction to Functional Programming

Pure Functions

At the heart of functional programming are pure functions. A pure function always returns the same output for the same input and has no side effects. Let’s look at an example:

// Pure function
function add(a: number, b: number): number {
  return a + b;
}

// Impure function
let total = 0;
function addToTotal(value: number): number {
  total += value;
  return total;
}

The add function is pure because it always returns the same result for the same inputs and doesn’t modify any external state. On the other hand, addToTotal is impure because it modifies the external total variable.

Pure functions are easier to test, reason about, and parallelize. They also help in creating more predictable and maintainable code.

Immutability

Immutability is another core concept in functional programming. Instead of changing data in place, we create new data structures. This helps prevent unintended side effects and makes our code more predictable.

Let’s look at an example of working with arrays in a mutable vs immutable way:

// Mutable approach
const numbers = [1, 2, 3, 4, 5];
numbers.push(6); // Modifies the original array

// Immutable approach
const numbers = [1, 2, 3, 4, 5];
const newNumbers = [...numbers, 6]; // Creates a new array

In the immutable approach, we create a new array instead of modifying the existing one. This can be particularly useful in larger applications where unexpected mutations can lead to bugs.

Higher-Order Functions

Higher-order functions are functions that can take other functions as arguments or return functions. They are a powerful tool in functional programming. Let’s look at an example using the map function:

const numbers = [1, 2, 3, 4, 5];

// Using a higher-order function
const doubled = numbers.map((num) => num * 2);

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

Here, map is a higher-order function that takes another function as an argument. This allows for more flexible and reusable code.

Function Composition

Function composition is the process of combining two or more functions to produce a new function. This allows us to build complex operations from simpler ones. Let’s see an example:

const add5 = (x: number) => x + 5;
const multiply2 = (x: number) => x * 2;

// Function composition
const add5ThenMultiply2 = (x: number) => multiply2(add5(x));

console.log(add5ThenMultiply2(10)); // 30

// Using a compose utility
const compose = (...fns: ((arg: T) => T)[]) => 
  (x: T) => fns.reduceRight((acc, fn) => fn(acc), x);

const add5AndMultiply2 = compose(multiply2, add5);

console.log(add5AndMultiply2(10)); // 30

In this example, we first manually compose add5 and multiply2. Then, we create a compose utility function that allows us to compose any number of functions. This approach can lead to more readable and maintainable code, especially when dealing with complex transformations.

Recursion

Recursion is often used in functional programming instead of imperative loops. While TypeScript (and JavaScript) aren’t optimized for tail-call recursion, it’s still a useful technique. Here’s an example of calculating factorial:

// Imperative approach
function factorialImperative(n: number): number {
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
  return result;
}

// Recursive approach
function factorialRecursive(n: number): number {
  if (n <= 1) return 1;
  return n * factorialRecursive(n - 1);
}

console.log(factorialImperative(5)); // 120
console.log(factorialRecursive(5)); // 120

The recursive approach can often lead to more elegant and concise code, though it’s important to be aware of potential stack overflow issues with deep recursion in JavaScript/TypeScript.

Conclusion

Functional programming offers a different way of thinking about and structuring your code. By emphasizing pure functions, immutability, and declarative patterns, it can lead to more predictable, testable, and maintainable code. While TypeScript isn’t a purely functional language, it provides features that allow us to adopt many functional programming techniques.

As you continue learning functional programming, you might want to explore concepts like functors, monads, and lazy evaluation. Libraries like Ramda or fp-ts can also be great resources for applying functional programming concepts in TypeScript projects. You might also want to try Haskell, which is often crowned the king of functional programming languages.

Remember, functional programming is not about abandoning all imperative code, but about finding a balance and using the right tool for the job. Sticking to concepts from only one paradigm can make it harder to perform certain tasks.

関連記事

カテゴリー:

ブログ

情シス求人

  1. チームメンバーで作字やってみた#1

ページ上部へ戻る