💻 What is Functional Programming
Functional Programming is a paradigm of building computer programs using expressions and functions without mutating state and data.
Introduction
Functional programming (FP) is a declarative programming paradigm where problems are solved by composing pure functions. Functions take input values and produce output values without being affected by the program's state. This style emphasizes "what to solve" rather than "how to solve," using expressions instead of statements. Functional programming excels at mathematical computations and avoids concepts like shared state and mutable data, which are common in object-oriented programming.
FP is rooted in lambda calculus and has influenced many modern languages, including JavaScript, Haskell, Scala, and Clojure. In JavaScript, FP techniques are increasingly popular due to their ability to produce more predictable, testable, and maintainable code.
Functional Programming Concepts
First-class and Higher-order Functions
Functions are treated as first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned from other functions. A higher-order function is a function that takes one or more functions as arguments or returns a function as its result.
const withLog = (fn) => {
return (...args) => {
console.log(`calling ${fn.name}`)
return fn(...args)
}
}
Pure vs. Impure Functions
Pure functions:
- Always return the same output for the same input.
- Have no side effects (do not modify external state, files, databases, or global variables).
const add = (a, b) => a + b
Impure functions may depend on or modify external state, making them harder to test and reason about.
Immutability
Variables, once created, are not modified. This ensures that program state remains predictable and consistent. Instead of changing existing data, you create new copies with the desired changes.
const obj = { a: 2 }
const newObj = Object.assign({}, obj)
newObj.a = 3
// obj.a is still 2
// ES6 spread syntax
const newObj2 = { ...obj, a: 5 }
Recursion
Functional programming avoids traditional looping constructs like while
or for
and instead uses recursion. Recursive functions call themselves until reaching a base case.
const range = (a, b) => (a > b ? [] : [a, ...range(a + 1, b)])
Array Methods: Map, Filter & Reduce
-
Map: Transforms each element in an array.
;[1, 2, 3].map((x) => x * 2) // [2, 4, 6]
-
Filter: Returns a new array containing elements that match a condition.
;[1, 2, 3, 4].filter((x) => x % 2 === 0) // [2, 4]
-
Reduce: Applies a reducer function to each element, accumulating a single result.
const sum = (acc, item) => acc + item[(1, 2, 3)].reduce(sum, 0) // 6
Currying
Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument.
const add = (a) => (b) => a + b
add(3)(4) // 7
Currying enables memoization and partial application of arguments.
Partial Application
Partial application fixes a number of arguments to a function, producing another function of smaller arity.
const multiply = (a, b) => a * b
const double = multiply.bind(null, 2)
double(5) // 10
Composition
Function composition combines multiple functions, passing the output of one as the input to another. This enables building complex operations from simple, reusable functions.
const range = (a, b) => (a > b ? [] : [a, ...range(a + 1, b)])
const multiplyArr = (arr) => arr.reduce((p, a) => p * a, 1)
const factorial = (n) => multiplyArr(range(1, n))
factorial(5) // 120
A generic compose function:
const compose =
(...fns) =>
(x) =>
fns.reduceRight((v, f) => f(v), x)
Referential Transparency
An expression is referentially transparent if it can be replaced with its value without changing the program's behavior. Pure functions and immutability help achieve this property, making code easier to reason about and refactor.
Benefits of Functional Programming
- Predictability: Pure functions and immutability reduce bugs caused by hidden state and side effects.
- Testability: Functions are easier to test in isolation.
- Reusability: Small, composable functions can be reused across your codebase.
- Concurrency: Immutability makes it easier to write concurrent or parallel code without race conditions.
When to Use Functional Programming
- Data transformation pipelines (e.g., processing arrays, streams)
- Mathematical computations
- UI rendering (React and other frameworks use FP concepts)
- Any scenario where predictability and maintainability are priorities
Conclusion
Functional programming is a robust and well-researched paradigm for building reliable software. With modern JavaScript (ESNext), functional programming techniques are more accessible and powerful than ever. By embracing pure functions, immutability, and composition, you can write code that is easier to test, maintain, and reason about.
For further reading, see Professor Frisby’s Mostly Adequate Guide to Functional Programming and MDN: Functional programming in JavaScript.