Introduction
A Functor is a container with a map method. It lets you transform the contained value without opening the container. This abstraction enables composable, safe data transformations.
Key Concepts
Functor: Container implementing map.
map: Transform value inside without extracting.
Identity Law: container.map(x => x) equals container.
Composition Law: container.map(f).map(g) equals container.map(x => g(f(x))).
Real World Context
Arrays, Promises, and Observables are all functors—they all have a map (or then) method. React's component model is functor-like: props go in, JSX comes out, and you can map transforms over the output. Understanding functors makes you fluent with any container-based API.
Deep Dive
The Box
javascriptconst Box = x => ({ map: f => Box(f(x)), fold: f => f(x), inspect: () => `Box(${x})` }); // Transform without extracting Box(2) .map(x => x + 2) // Box(4) .map(x => x * 3) // Box(12) .fold(x => x); // 12 (extract)
Why Use It?
javascript// Without Box - imperative const result = trim(toUpper(getValue(data))); // With Box - composable pipeline const result = Box(data) .map(getValue) .map(toUpper) .map(trim) .fold(x => x);
Arrays are Functors
javascript// Array.map follows functor laws [1, 2, 3].map(x => x); // [1, 2, 3] (identity) [1, 2, 3].map(x => x + 1).map(x => x * 2); // [4, 6, 8] [1, 2, 3].map(x => (x + 1) * 2); // [4, 6, 8] (composition)
Common Pitfalls
- Forgetting to fold/extract — Mapping transforms values inside the box but never extracts them. You need
foldto get the final value out. - Violating functor laws — If
map(x => x)doesn't return an identical container, your functor is broken. Always verify identity and composition laws. - Overusing Box for simple values — Wrapping a single value in Box just to call
.map()adds ceremony without benefit. Use Box when you have a pipeline of transforms.
Best Practices
- Use Box to linearize nested function calls —
Box(x).map(f).map(g).fold(h)is more readable thanh(g(f(x))). - Follow functor laws —
container.map(x => x)must equalcontainer, andcontainer.map(f).map(g)must equalcontainer.map(x => g(f(x))). - Think of Promise as a functor —
.then(fn)is essentially.map(fn). Understanding this connection makes async code more intuitive.
Summary
Functors have map. Map transforms without extracting. Box is the simplest functor. Arrays, Promises are functors. Laws ensure predictable behavior.
Code Examples
const Box = x => ({
map: f => Box(f(x)),
fold: f => f(x),
inspect: () => `Box(${x})`
});
// Linearize nested function calls
const result = Box(' Hello World ')
.map(s => s.trim())
.map(s => s.toUpperCase())
.map(s => s.split(' '))
.fold(x => x);
// ['HELLO', 'WORLD']
// Functor laws
Box(5).map(x => x); // Box(5) — identity law
Box(5).map(x => x + 1).map(x => x * 2); // Box(12)
Box(5).map(x => (x + 1) * 2); // Box(12) — composition law