Functional Programming Patterns and Concepts
About 1522 wordsAbout 19 min
2025-08-05
This section explores functional programming paradigms in JavaScript, covering core concepts, patterns, and techniques for writing pure, composable code.
Functional Programming Fundamentals
Functional programming treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.
Core Principles
1. Pure Functions:
- Always return the same output for the same input
- Have no side effects (no mutation of external state)
- Are referentially transparent
// Pure function
const add = (a, b) => a + b;
// Impure function (has side effects)
let total = 0;
const addToTotal = (value) => {
total += value; // Mutates external state
return total;
};
// Pure function alternative
const addToTotalPure = (currentTotal, value) => currentTotal + value;
2. Immutability:
- Data cannot be changed after creation
- Instead of modifying, create new data structures
// Mutable approach
const mutableUser = { name: 'John', age: 30 };
mutableUser.age = 31; // Direct mutation
// Immutable approach
const immutableUser = { name: 'John', age: 30 };
const updatedUser = { ...immutableUser, age: 31 }; // New object
// Immutable array operations
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // Add element
const filteredNumbers = numbers.filter(n => n !== 2); // Remove element
3. First-Class and Higher-Order Functions:
- Functions can be assigned to variables, passed as arguments, and returned from other functions
- Higher-order functions take functions as arguments or return functions
// Functions as first-class citizens
const greet = (name) => `Hello, ${name}!`;
const sayHello = greet; // Assign function to variable
// Higher-order function
const withLogging = (fn) => {
return (...args) => {
console.log(`Calling ${fn.name} with args:`, args);
const result = fn(...args);
console.log(`Result:`, result);
return result;
};
};
const loggedAdd = withLogging(add);
loggedAdd(2, 3); // Logs the call and returns 5
Functional Data Structures
Immutable Collections
// Immutable stack implementation
class ImmutableStack {
constructor(items = []) {
this.items = items;
}
push(item) {
return new ImmutableStack([item, ...this.items]);
}
pop() {
if (this.isEmpty()) return this;
return new ImmutableStack(this.items.slice(1));
}
peek() {
return this.items[0];
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
}
// Usage
const stack = new ImmutableStack();
const stack1 = stack.push(1);
const stack2 = stack1.push(2);
const stack3 = stack2.pop();
console.log(stack1.size()); // 1
console.log(stack2.size()); // 2
console.log(stack3.size()); // 1
console.log(stack.size()); // 0 (original unchanged)
Persistent Data Structures
// Simple persistent map
class PersistentMap {
constructor(entries = {}) {
this.entries = { ...entries };
}
set(key, value) {
return new PersistentMap({ ...this.entries, [key]: value });
}
get(key) {
return this.entries[key];
}
delete(key) {
const newEntries = { ...this.entries };
delete newEntries[key];
return new PersistentMap(newEntries);
}
has(key) {
return key in this.entries;
}
keys() {
return Object.keys(this.entries);
}
}
// Usage
const map = new PersistentMap();
const map1 = map.set('name', 'John');
const map2 = map1.set('age', 30);
const map3 = map2.delete('age');
console.log(map1.get('name')); // John
console.log(map2.get('age')); // 30
console.log(map3.has('age')); // false
console.log(map.has('name')); // undefined
Function Composition
Basic Composition
// Simple composition
const compose = (f, g) => (x) => f(g(x));
// Example
const toUpperCase = (str) => str.toUpperCase();
const addExclamation = (str) => str + '!';
const greet = (name) => `Hello, ${name}`;
const excitedGreeting = compose(addExclamation, toUpperCase, greet);
console.log(excitedGreeting('John')); // "HELLO, JOHN!"
// Multiple function composition
const composeN = (...fns) => (x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
// Pipe (left-to-right composition)
const pipe = (...fns) => (x) =>
fns.reduce((acc, fn) => fn(acc), x);
const processData = pipe(
x => x * 2,
x => x + 1,
x => `Result: ${x}`
);
console.log(processData(5)); // "Result: 11"
Advanced Composition Patterns
// Point-free style (tacit programming)
const add = (a, b) => a + b;
const increment = (x) => add(1, x);
const decrement = (x) => add(-1, x);
// Curried functions for composition
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
};
const curriedAdd = curry((a, b, c) => a + b + c);
const add5 = curriedAdd(5);
const add5And3 = add5(3);
console.log(add5And3(2)); // 10
// Partial application
const partial = (fn, ...presetArgs) => {
return (...laterArgs) => fn(...presetArgs, ...laterArgs);
};
const multiply = (a, b) => a * b;
const double = partial(multiply, 2);
const triple = partial(multiply, 3);
Functors and Monads
Functors
// Maybe functor for handling potential null/undefined values
class Maybe {
static just(value) {
return new Maybe(value);
}
static nothing() {
return new Maybe(null);
}
constructor(value) {
this.value = value;
}
isJust() {
return this.value !== null && this.value !== undefined;
}
isNothing() {
return !this.isJust();
}
map(fn) {
return this.isJust() ? Maybe.just(fn(this.value)) : Maybe.nothing();
}
getOrElse(defaultValue) {
return this.isJust() ? this.value : defaultValue;
}
}
// Usage
const getUser = (id) => {
const users = { 1: { name: 'John' }, 2: { name: 'Jane' } };
return users[id] ? Maybe.just(users[id]) : Maybe.nothing();
};
const getUserName = (user) => user.name;
const toUpperCase = (str) => str.toUpperCase();
const userName = getUser(1)
.map(getUserName)
.map(toUpperCase)
.getOrElse('Unknown');
console.log(userName); // "JOHN"
const invalidUser = getUser(99)
.map(getUserName)
.map(toUpperCase)
.getOrElse('Unknown');
console.log(invalidUser); // "Unknown"
Either Monad
// Either monad for handling errors
class Either {
static right(value) {
return new Right(value);
}
static left(error) {
return new Left(error);
}
}
class Right extends Either {
constructor(value) {
super();
this.value = value;
}
isRight() {
return true;
}
isLeft() {
return false;
}
map(fn) {
return Either.right(fn(this.value));
}
fold(leftFn, rightFn) {
return rightFn(this.value);
}
}
class Left extends Either {
constructor(error) {
super();
this.error = error;
}
isRight() {
return false;
}
isLeft() {
return true;
}
map(fn) {
return this;
}
fold(leftFn, rightFn) {
return leftFn(this.error);
}
}
// Usage
const divide = (a, b) => {
return b === 0
? Either.left('Division by zero')
: Either.right(a / b);
};
const result = divide(10, 2)
.map(x => x * 2)
.map(x => x + 1)
.fold(
error => `Error: ${error}`,
value => `Result: ${value}`
);
console.log(result); // "Result: 11"
const errorResult = divide(10, 0)
.map(x => x * 2)
.fold(
error => `Error: ${error}`,
value => `Result: ${value}`
);
console.log(errorResult); // "Error: Division by zero"
Functional Design Patterns
1. Lens Pattern
// Lens for focusing on parts of data structures
const lens = {
// Create a lens for an object property
prop: (prop) => ({
get: (obj) => obj[prop],
set: (value) => (obj) => ({ ...obj, [prop]: value })
}),
// Create a lens for array index
index: (idx) => ({
get: (arr) => arr[idx],
set: (value) => (arr) => {
const newArr = [...arr];
newArr[idx] = value;
return newArr;
}
}),
// Compose lenses
compose: (outerLens, innerLens) => ({
get: (obj) => innerLens.get(outerLens.get(obj)),
set: (value) => (obj) => {
const outerObj = outerLens.get(obj);
const updatedInner = innerLens.set(value)(outerObj);
return outerLens.set(updatedInner)(obj);
}
})
};
// Usage
const user = {
name: 'John',
address: {
street: '123 Main St',
city: 'Anytown'
}
};
const addressLens = lens.prop('address');
const streetLens = lens.prop('street');
const addressStreetLens = lens.compose(addressLens, streetLens);
console.log(addressStreetLens.get(user)); // "123 Main St"
const updatedUser = addressStreetLens.set('456 Oak Ave')(user);
console.log(updatedUser.address.street); // "456 Oak Ave"
2. Trampoline Pattern
// Trampoline for handling deep recursion without stack overflow
const trampoline = (fn) => {
return (...args) => {
let result = fn(...args);
while (typeof result === 'function') {
result = result();
}
return result;
};
};
// Recursive function that uses trampoline
const factorial = (n, acc = 1) => {
if (n <= 1) return acc;
return () => factorial(n - 1, n * acc); // Return thunk
};
const safeFactorial = trampoline(factorial);
console.log(safeFactorial(5)); // 120
console.log(safeFactorial(10000)); // Works without stack overflow
3. State Monad
// State monad for managing state functionally
class State {
constructor(runState) {
this.runState = runState;
}
static get(s) {
return new State((state) => [s, state]);
}
static put(s) {
return new State((state) => [undefined, s]);
}
map(fn) {
return new State((state) => {
const [value, newState] = this.runState(state);
return [fn(value), newState];
});
}
flatMap(fn) {
return new State((state) => {
const [value, newState] = this.runState(state);
return fn(value).runState(newState);
});
}
run(initialState) {
return this.runState(initialState);
}
}
// Usage
const increment = () => new State((count) => [undefined, count + 1]);
const getCount = () => new State((count) => [count, count]);
const counterProgram = getCount()
.flatMap(count =>
increment().flatMap(() =>
increment().flatMap(() =>
getCount().map(newCount =>
`Count was ${count}, now ${newCount}`
)
)
)
);
const [result, finalState] = counterProgram.run(0);
console.log(result); // "Count was 0, now 2"
console.log(finalState); // 2
Functional Utilities and Helpers
Currying and Partial Application
// Advanced currying
const curryN = (fn, arity = fn.length) => {
return function curried(...args) {
if (args.length >= arity) {
return fn.apply(this, args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
};
// Partial application with placeholders
const partial = (fn, ...args) => {
const placeholders = args.map(arg => arg === '_' ? null : arg);
return (...nextArgs) => {
const combined = [];
let nextIndex = 0;
for (let i = 0; i < placeholders.length; i++) {
if (placeholders[i] === null) {
combined.push(nextArgs[nextIndex++]);
} else {
combined.push(placeholders[i]);
}
}
return fn(...combined, ...nextArgs.slice(nextIndex));
};
};
// Usage
const add = (a, b, c) => a + b + c;
const add5And3 = partial(add, 5, _, 3);
console.log(add5And3(2)); // 10
Function Composition Utilities
// Conditional composition
const composeWhen = (condition, ...fns) => {
const composed = composeN(...fns);
return (x) => condition(x) ? composed(x) : x;
};
// Parallel composition
const composeParallel = (...fns) => (x) =>
fns.map(fn => fn(x));
// Memoization
const memoize = (fn, keyGenerator = JSON.stringify) => {
const cache = new Map();
return (...args) => {
const key = keyGenerator(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
};
// Usage
const expensiveFunction = memoize((n) => {
console.log('Computing...');
return n * n;
});
console.log(expensiveFunction(5)); // Computing... 25
console.log(expensiveFunction(5)); // 25 (from cache)
Functional Data Processing
// Transducer for efficient data transformation
const transduce = (xf, reducer, initial, collection) => {
const transformedReducer = xf(reducer);
return collection.reduce(transformedReducer, initial);
};
// Map transducer
const mapTransducer = (fn) => (reducer) => (acc, x) =>
reducer(acc, fn(x));
// Filter transducer
const filterTransducer = (predicate) => (reducer) => (acc, x) =>
predicate(x) ? reducer(acc, x) : acc;
// Usage
const numbers = [1, 2, 3, 4, 5, 6];
const result = transduce(
compose(
mapTransducer(x => x * 2),
filterTransducer(x => x % 3 === 0)
),
(acc, x) => [...acc, x],
[],
numbers
);
console.log(result); // [6, 12]
These functional programming concepts provide powerful tools for writing more declarative, predictable, and maintainable JavaScript code while embracing pure language features.
Changelog
8/8/25, 4:17 AM
View All Changelog
2aa48
-web-deploy(Auto): Update base URL for web-pages branchon
Copyright
Copyright Ownership:WARREN Y.F. LONG
License under:Attribution-NonCommercial-NoDerivatives 4.0 International (CC-BY-NC-ND-4.0)