Prereqs: ๐ Ch1 (Category), ๐ Ch2 (Types and Functions). 7 min.
A functor is a structure-preserving map between categories. It maps objects to objects and morphisms to morphisms, so that composition and identity are preserved: fmap id = id and fmap (g . f) = fmap g . fmap f. Every container type you already use — list, optional, function — is a functor. fmap is the operation that makes it one.
What is a functor?
A functor maps one category to another. It sends each object to an object and each morphism to a morphism, preserving the wiring: if h = g . f in the source category, then F(h) = F(g) . F(f) in the target. In programming, both categories are the same (types and functions), so a functor is a type constructor plus an fmap that lifts functions inside the container. Functors also show up in unexpected places: software licenses are functors from a derivation category to an obligations category, preserving the composition of derivative works.
Scheme
; A functor has two parts:; 1. A type constructor: wraps values (list, maybe, etc.); 2. fmap: lifts a function to work inside the wrapper;; fmap :: (a -> b) -> F(a) -> F(b); "Give me a function on plain values,; I'll give you a function on wrapped values."; The simplest functor: a box holding one value
(define (box v) (list 'box v))
(define (unbox b) (cadr b))
(define (fmap-box f b)
(box (f (unbox b))))
(define b (box 5))
(display "box: ") (display b) (newline)
(display "fmap add1: ") (display (fmap-box add1 b)) (newline)
(display "fmap square: ") (display (fmap-box (lambda (x) (* x x)) b))
Python
# Simplest functor: a box holding one valueclass Box:
def __init__(self, v): self.v = v
def __repr__(self): returnf"Box({self.v})"def fmap_box(f, b):
return Box(f(b.v))
b = Box(5)
print(f"box: {b}")
print(f"fmap add1: {fmap_box(lambda x: x+1, b)}")
print(f"fmap square: {fmap_box(lambda x: x*x, b)}")
The Maybe functor
Maybe either holds a value (just) or nothing. Its fmap applies the function when there's a value and passes through nothing unchanged. This is how you transform values that might not exist, without checking for null at every step.
Scheme
; Maybe: 'nothing or (list 'just value)
(define nothing 'nothing)
(define (just v) (list 'just v))
(define (just? m) (and (pair? m) (eq? (car m) 'just)))
(define (from-just m) (cadr m))
; fmap for Maybe
(define (fmap-maybe f m)
(if (just? m)
(just (f (from-just m)))
nothing))
(display "fmap add1 (just 3): ")
(display (fmap-maybe add1 (just 3))) (newline)
(display "fmap add1 nothing: ")
(display (fmap-maybe add1 nothing)) (newline)
; Chain two fmaps
(define result
(fmap-maybe (lambda (x) (* x x))
(fmap-maybe add1 (just 4))))
(display "fmap square (fmap add1 (just 4)): ")
(display result)
; (just 25) โ add1 gives 5, square gives 25
Python
# Maybe functor in Python (using None for nothing)def fmap_maybe(f, m):
if m isNone:
returnNonereturn f(m)
print(f"fmap add1 (just 3): {fmap_maybe(lambda x: x+1, 3)}")
print(f"fmap add1 nothing: {fmap_maybe(lambda x: x+1, None)}")
# Chain two fmaps
result = fmap_maybe(lambda x: x*x, fmap_maybe(lambda x: x+1, 4))
print(f"fmap square (fmap add1 (just 4)): {result}")
# 25
The List functor
Lists are functors. fmap for lists is map: apply a function to every element, preserving the list structure. This is the functor most programmers already use without knowing it.
Scheme
; List functor: fmap = map; Applies f to every element, preserves structure
(define (fmap-list f xs)
(if (null? xs) '()
(cons (f (car xs))
(fmap-list f (cdr xs)))))
(display "fmap add1 '(1 2 3): ")
(display (fmap-list add1 '(123))) (newline)
(display "fmap square '(1 2 3 4): ")
(display (fmap-list (lambda (x) (* x x)) '(1234))) (newline)
; fmap on empty list = empty list (nothing to transform)
(display "fmap add1 '(): ")
(display (fmap-list add1 '()))
Python
# List functor: fmap = mapdef fmap_list(f, xs):
return [f(x) for x in xs]
print(f"fmap add1 [1,2,3]: {fmap_list(lambda x: x+1, [1,2,3])}")
print(f"fmap square [1,2,3,4]: {fmap_list(lambda x: x*x, [1,2,3,4])}")
print(f"fmap add1 []: {fmap_list(lambda x: x+1, [])}")
The functor laws
Two laws make a functor a functor. Identity: fmap id = id. Mapping the do-nothing function changes nothing. Composition: fmap (g . f) = fmap g . fmap f. Mapping a composed function equals composing the maps. If either law fails, the mapping distorts structure and isn't a functor.
Scheme
; Functor laws โ verify for List and Maybe
(define (fmap-list f xs)
(if (null? xs) '()
(cons (f (car xs)) (fmap-list f (cdr xs)))))
(define (id x) x)
(define (compose f g) (lambda (x) (f (g x))))
; --- Law 1: fmap id = id ---
(define xs '(1234))
(display "List identity: ")
(display (equal? (fmap-list id xs) xs)) (newline)
; --- Law 2: fmap (g . f) = fmap g . fmap f ---
(define (f x) (+ x 1))
(define (g x) (* x x))
(define lhs (fmap-list (compose g f) xs))
(define rhs (fmap-list g (fmap-list f xs)))
(display "List composition: ")
(display (equal? lhs rhs)) (newline)
(display " lhs: ") (display lhs) (newline)
(display " rhs: ") (display rhs) (newline)
; --- Maybe ---
(define nothing 'nothing)
(define (just v) (list 'just v))
(define (just? m) (and (pair? m) (eq? (car m) 'just)))
(define (from-just m) (cadr m))
(define (fmap-maybe fn m)
(if (just? m) (just (fn (from-just m))) nothing))
(display "Maybe id (just 5): ")
(display (equal? (fmap-maybe id (just 5)) (just 5))) (newline)
(display "Maybe id nothing: ")
(display (equal? (fmap-maybe id nothing) nothing)) (newline)
(define lhs2 (fmap-maybe (compose g f) (just 3)))
(define rhs2 (fmap-maybe g (fmap-maybe f (just 3))))
(display "Maybe composition: ")
(display (equal? lhs2 rhs2))
; (just 16) โ add1 gives 4, square gives 16
Python
# Functor laws verified for list and Maybedef fmap_list(f, xs): return [f(x) for x in xs]
def fmap_maybe(f, m): returnNoneif m isNoneelse f(m)
identity = lambda x: x
f = lambda x: x + 1
g = lambda x: x * x
xs = [1, 2, 3, 4]
# Law 1: fmap id = idprint(f"List identity: {fmap_list(identity, xs) == xs}")
# Law 2: fmap (g . f) = fmap g . fmap f
lhs = fmap_list(lambda x: g(f(x)), xs)
rhs = fmap_list(g, fmap_list(f, xs))
print(f"List composition: {lhs == rhs} {lhs}")
# Maybeprint(f"Maybe id (just 5): {fmap_maybe(identity, 5) == 5}")
print(f"Maybe id nothing: {fmap_maybe(identity, None) is None}")
lhs2 = fmap_maybe(lambda x: g(f(x)), 3)
rhs2 = fmap_maybe(g, fmap_maybe(f, 3))
print(f"Maybe composition: {lhs2 == rhs2} {lhs2}")
The Reader functor
Functions from a fixed type r are a functor. The type constructor maps a to r -> a. And fmap is just function composition: to transform the output of a function, compose with the transformer. fmap f g = f . g. This is the Reader functor.
Scheme
; Reader functor: functions from a fixed type r; Type constructor: a -> (r -> a); fmap f g = compose f g = (lambda (x) (f (g x)));; "To transform the output of a function, compose."
(define (fmap-reader f g)
(lambda (x) (f (g x))))
; g : number -> number (double)
(define (double x) (* x 2))
; f : number -> string (show)
(define (show x) (number->string x))
; fmap show double : number -> string
(define show-double (fmap-reader show double))
(display "show-double 5: ")
(display (show-double 5)) (newline)
; "10"
(display "show-double 21: ")
(display (show-double 21)) (newline)
; "42"; Verify functor laws
(define (id x) x)
(define (compose f g) (lambda (x) (f (g x))))
; Identity: fmap id g = g
(display "identity: ")
(display (= ((fmap-reader id double) 7) (double 7))) (newline)
; Composition: fmap (f . g) h = fmap f (fmap g h)
(define (add1 x) (+ x 1))
(define (square x) (* x x))
(define lhs (fmap-reader (compose square add1) double))
(define rhs (fmap-reader square (fmap-reader add1 double)))
(display "composition: ")
(display (= (lhs 3) (rhs 3)))
; Both give 49: double(3)=6, add1=7, square=49
Notation reference
Blog / Haskell
Scheme
Meaning
fmap :: (a -> b) -> f a -> f b
(fmap-list f xs), (fmap-maybe f m)
Lift function into functor
fmap id = id
(equal? (fmap-list id xs) xs)
Identity law
fmap (g . f) = fmap g . fmap f
(equal? (fmap-list (compose g f) xs) ...)
Composition law
Maybe a = Nothing | Just a
'nothing | (just v)
Optional value
[a] (List a)
'(1 2 3)
List type
(->) r a
(lambda (r) ...)
Reader: function from r
Neighbors
Milewski chapters
๐ Chapter 4 โ Kleisli Categories (monads use functors under the hood)
The blog post presents functors in Haskell (typeclasses, algebraic data types) and C++ (templates, std::transform). This page uses Scheme tagged lists for Maybe and native lists for the List functor. The Reader functor is just function composition in both languages. The Const functor from the blog post is omitted here: it maps every type to the same wrapped constant and discards the function argument entirely. It matters for lenses and Yoneda, not for building intuition about what a functor does.
Ready for the real thing? Read the blog post. Start with the Haskell typeclass definition, then try the equational proofs of the functor laws.