Functional JavaScript Summary

Functional JavaScript

Introducing Functional Programming with Underscore.js
by Michael Fogus 2013 258 pages
4.07
429 ratings

Key Takeaways

1. JavaScript supports functional programming through first-class functions

A first-class function is one that can go anywhere that any other value can go—there are few to no restrictions.

Functions as values. In JavaScript, functions can be assigned to variables, passed as arguments to other functions, returned from functions, and stored in data structures. This flexibility enables functional programming techniques. For example:

  • Assigning a function to a variable: var square = function(x) { return x * x; }
  • Passing a function as an argument: [1, 2, 3].map(function(x) { return x * 2; })
  • Returning a function: function makeAdder(x) { return function(y) { return x + y; }; }

This first-class nature of functions is the foundation for many functional programming patterns in JavaScript.

2. Higher-order functions are key to functional JavaScript

Higher-order functions that capture other functions are a very powerful technique for building abstractions.

Powerful abstractions. Higher-order functions either take functions as arguments or return functions as results. They enable powerful abstractions and code reuse. Common examples include:

  • map: Transform each element in a collection
  • reduce: Combine elements of a collection into a single value
  • filter: Select elements from a collection based on a predicate

These functions allow you to express complex operations succinctly and compose behavior. For instance:

javascript
const numbers = [1, 2, 3, 4, 5];
const evenSquares = numbers
.filter(x => x % 2 === 0)
.map(x => x * x);

This code clearly expresses the intent of selecting even numbers and squaring them, without explicitly managing loops or intermediate arrays.

3. Closures enable powerful functional techniques in JavaScript

A closure is a function that captures the external bindings (i.e., not its own arguments) contained in the scope in which it was defined for later use (even after that scope has completed).

Encapsulation and state. Closures allow functions to "remember" and access variables from their outer scope, even after that scope has finished executing. This enables:

  • Private state: Create variables that are accessible only through specific functions
  • Function factories: Generate specialized functions based on parameters
  • Partial application: Create new functions by fixing some arguments of existing functions

Example of a closure maintaining private state:

javascript
function counter() {
let count = 0;
return function() {
return ++count;
};
}

const increment = counter();
increment(); // 1
increment(); // 2

The count variable is not directly accessible, but the returned function can access and modify it.

4. Function composition allows building complex behaviors from simple parts

Functional programming is about pulling programs apart and reassembling them from the same parts, abstracted behind function boundaries.

Building complexity. Function composition is the process of combining two or more functions to create a new function. This allows you to:

  • Create complex behaviors from simple, reusable parts
  • Improve code readability by breaking down complex operations
  • Enhance maintainability by isolating and testing smaller units of functionality

Composition can be achieved through various means:

  • Manual composition: const f = x => h(g(x))
  • Utility functions: const compose = (f, g) => x => f(g(x))
  • Libraries like Ramda or Lodash/FP

Example of building a data processing pipeline through composition:

javascript
const processData = compose(
summarize,
filterOutliers,
normalize,
parseInput
);

This clearly expresses the steps of data processing without cluttering the code with implementation details.

5. Pure functions and immutability lead to more predictable code

Programming with pure functions may seem incredibly limiting. [...] However, when you exercise a libertarian view of state mutation, you're actually limiting your possibilities in composition, complicating your ability to reason through the effects of any given statement, and making it more difficult to test your code.

Predictability and testability. Pure functions always produce the same output for given inputs and have no side effects. This, combined with immutable data:

  • Simplifies reasoning about code behavior
  • Facilitates easier testing and debugging
  • Enables safe parallelization and memoization

Strategies for maintaining purity and immutability:

  • Use const for variables that shouldn't change
  • Create new objects/arrays instead of modifying existing ones
  • Employ libraries like Immutable.js for efficient immutable data structures

Example of a pure function:

javascript
function addToCart(cart, item) {
return [...cart, item];
}

This function doesn't modify the original cart, making it easier to track changes and predict behavior.

6. Recursion provides an alternative to loops in functional programming

Using recursion, state is managed via the function arguments, and change is modeled via the arguments from one recursive call to the next.

Elegant solutions. Recursion often leads to more elegant and concise solutions for problems involving hierarchical or repetitive structures. Benefits include:

  • Natural expression of some algorithms (e.g., tree traversal)
  • Avoidance of mutable state often associated with loops
  • Potential for compiler optimizations (tail-call optimization)

However, be aware of stack overflow risks in JavaScript, which lacks tail-call optimization in most environments. Techniques to mitigate this include:

  • Trampolining: Wrapping recursive calls in thunks
  • Continuation-passing style: Explicitly managing the call stack

Example of a recursive function to flatten a nested array:

javascript
function flatten(arr) {
return arr.reduce((flat, next) =>
flat.concat(Array.isArray(next) ? flatten(next) : next),
[]);
}

7. Functional programming facilitates data flow and transformation pipelines

Pipelines are meant to be pure—no data was harmed by running it through.

Clear data transformations. Functional programming encourages thinking in terms of data flowing through a series of transformations. This approach:

  • Improves code readability by clearly showing the steps of data processing
  • Enhances maintainability by separating concerns
  • Facilitates parallelization and optimization

Techniques for building pipelines:

  • Method chaining (e.g., with lodash)
  • Composition of pure functions
  • Specialized libraries like RxJS for asynchronous data streams

Example of a data processing pipeline:

javascript
const processOrders = pipe(
fetchOrders,
filterValidOrders,
calculateTotals,
generateReport
);

This clearly shows the steps involved in processing orders without getting bogged down in implementation details.

8. Mixin-based design offers a functional approach to object composition

Simple data is best. Specialized data types should be, well, special.

Flexible composition. Mixins provide a way to compose object behavior without relying on classical inheritance. This approach:

  • Allows for more flexible and modular code design
  • Avoids problems associated with deep inheritance hierarchies
  • Facilitates a more functional style of object-oriented programming

Implementing mixins in JavaScript:

  • Use Object.assign() to copy methods onto object prototypes
  • Employ higher-order functions to create factory functions that apply mixins

Example of creating an object with mixins:

javascript
const withLogging = (obj) => ({
...obj,
log: (msg) => console.log([${obj.name}]: ${msg})
});

const withValidator = (obj) => ({
...obj,
validate: () => { /* validation logic */ }
});

const createUser = (name) =>
withValidator(withLogging({ name, data: {} }));

This approach allows for flexible composition of object behavior without rigid class hierarchies.

Last updated:

Report Issue