CONCEPT 14 · ABSTRACTIONS

Functor — Map Everything

"Transform the value inside without changing the container."

functormapcontext
Plain English

A functor is any container or context that supports map — applying a function to the value(s) inside without changing the container's shape. Arrays, Promises, Option/Maybe, Result/Either — all are functors. The one rule: mapping the identity function must leave things unchanged. That's it.

Analogy

A sealed package with a modification port. You slide a function through the port, it transforms whatever's inside, and you get back a new sealed package of the same type with the result. The package never opens — the outer shape is preserved. A box of apples mapped with "peel" is still a box; it just contains peeled apples now.

You already know this
Array.map(x => x * 2) — transforms each element, array shape preservedPromise.then(val => val.toUpperCase()) — transforms the resolved value, still a PromiseOptional chaining: user?.name — safe transformation, preserves the "might be absent" contextReact: component is conceptually a functor — maps data (props) to UI without changing the typeSQL SELECT expressions: SELECT UPPER(name) FROM users — transforms values, same table shape
Code Example
JavaScript
// Every functor has map. It transforms the inside, preserves the shape.

// Array — shape (length) preserved
[1, 2, 3].map(x => x * 2);         // → [2, 4, 6] (still 3 elements)
[1, 2, 3].map(x => String(x));     // → ['1','2','3'] (still 3 elements)

// Option/Maybe — shape (present/absent) preserved
Some(5).map(x => x + 1);           // → Some(6)
None.map(x => x + 1);              // → None  (no crash, shape preserved)

// Promise — shape (async) preserved
Promise.resolve(5)
  .then(x => x * 2);               // → Promise<10>, still a Promise

// Result/Either — transforms success, leaves error untouched
Right(42).map(x => x.toString());  // → Right('42')
Left('error').map(x => x + 1);     // → Left('error'), unchanged

// THE FUNCTOR LAW:
// map(x)(identity) must equal x
// Mapping identity changes nothing — the structure is preserved
[1,2,3].map(x => x);               // → [1,2,3] ✓

// filter() is NOT a functor operation:
[1,2,3,4,5].filter(x => x > 3);   // → [4,5] — shape CHANGED (5→2 elements)
// Functor map must preserve shape. filter breaks this rule.
Apply when
Transform a value inside a context without unwrapping it: Option.map, Promise.then, array.map
When you have the simplest context-aware operation needed — prefer functor over applicative or monad when map is sufficient
Building abstractions: any custom container should implement map to be composable with the ecosystem
Understanding the hierarchy: Functor < Applicative < Monad. Start here before reaching for monad.
Check Your Understanding
Which of these is NOT a valid functor (map) operation?