Chapter 3: Advanced Functions and Methods
Chapter 3: Advanced Functions and Methods
3.1 Higher-Order Functions
Higher-order functions are functions that either take other functions as arguments, return functions, or both. They enable powerful functional programming patterns.
Functions as Arguments:
// Array methods are higher-order functions
const numbers = [1, 2, 3, 4, 5];
// map() - transforms each element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter() - selects elements based on condition
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce() - accumulates values
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// forEach() - executes function for each element
numbers.forEach(n => console.log(n));
Functions as Return Values:
function createValidator(rule) {
return function(value) {
return rule(value);
};
}
const isEmail = createValidator(value => value.includes('@'));
const isPositive = createValidator(value => value > 0);
console.log(isEmail('test@example.com')); // true
console.log(isPositive(5)); // true
Custom Higher-Order Functions:
function withLogging(fn) {
return function(...args) {
console.log(`Calling function with args:`, args);
const result = fn(...args);
console.log(`Function returned:`, result);
return result;
};
}
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);
// Output:
// Calling function with args: [2, 3]
// Function returned: 5
3.2 Currying and Partial Application
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
// Curried function
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Multi-argument currying
const add = (a) => (b) => (c) => a + b + c;
const add5 = add(5);
const add5And10 = add5(10);
console.log(add5And10(15)); // 30
Practical Currying Example:
// Curried API request function
const apiRequest = (method) => (url) => (data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
};
const get = apiRequest('GET');
const post = apiRequest('POST');
const put = apiRequest('PUT');
const getUsers = get('/api/users');
const createUser = post('/api/users');
Partial Application:
Partial application is similar to currying but allows fixing some arguments while leaving others to be provided later.
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}
const sayHello = partial(greet, 'Hello');
const sayHelloToJohn = partial(greet, 'Hello', 'John');
console.log(sayHello('Alice', '!')); // "Hello, Alice!"
console.log(sayHelloToJohn('!')); // "Hello, John!"
3.3 Function Composition
Function composition is combining multiple functions to create a new function. The output of one function becomes the input of the next.
const compose = (...fns) => (value) =>
fns.reduceRight((acc, fn) => fn(acc), value);
const pipe = (...fns) => (value) =>
fns.reduce((acc, fn) => fn(acc), value);
// Example functions
const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;
const square = x => x * x;
// Compose: right to left
const composed = compose(square, multiplyByTwo, addOne);
console.log(composed(3)); // ((3 + 1) * 2)² = 64
// Pipe: left to right
const piped = pipe(addOne, multiplyByTwo, square);
console.log(piped(3)); // ((3 + 1) * 2)² = 64
3.4 Memoization
Memoization is an optimization technique that caches the results of expensive function calls.
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit!');
return cache.get(key);
}
console.log('Computing...');
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Expensive function
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(40)); // Computes once
console.log(memoizedFib(40)); // Uses cache