The algebraic hierarchy
AdvancedIn functional programming, the algebraic hierarchy is the family of abstractions — semigroup, monoid, functor, applicative, monad — that classify operations by the laws they satisfy. The names sound intimidating, but each one corresponds to a small pattern you already know. Knowing the hierarchy gives you a vocabulary for explaining why a piece of code is "composable" and how.
Semigroup: a binary operation that associates
A semigroup is a type plus an associative binary operation <>:
(a <> b) <> c = a <> (b <> c)
Examples in Clojure:
| Type | <> |
|---|---|
| Numbers | +, *, min, max |
| Strings | str |
| Vectors | into (or concat) |
| Maps | merge |
Associativity is what lets reduce work — it doesn't matter how you group
the combination, only the order.
Monoid: semigroup with an identity
A monoid adds an identity element mempty such that:
mempty <> x = x
x <> mempty = x
| Type | <> | mempty |
|---|---|---|
Numbers under + | + | 0 |
Numbers under * | * | 1 |
| Strings | str | "" |
| Vectors | into | [] |
| Maps | merge | {} |
(reduce + 0 xs) already encodes the integer-addition monoid: + is the
combine, 0 is the identity. Monoids are why "sum of an empty thing is zero"
makes sense.
Functor: something you can map over
A functor is a context F[A] plus a way to apply f : A → B inside
that context to get F[B]:
fmap : (A → B) → F[A] → F[B]
Clojure's map is fmap for sequences. The functor laws:
fmap identity x = x ;; identity is preserved
fmap f (fmap g x) = fmap (comp f g) x ;; composition is preserved
Anything with a "mappable inside" is a functor: options/maybes, futures,
parsers, lenses, IO actions in pure FP languages. Once you spot a functor,
you know map-style code will compose cleanly.
Applicative: combine independent effects
An applicative functor lets you lift a multi-arg function into the
context. Given F[A] and F[B], you can produce F[(A, B)] (or any
combination), without one effect depending on the other's result.
Think "fan in": (zip-with f xs ys) is an applicative pattern over
sequences. (pmap f xs) over futures is another — independent computations
combined when they're all ready.
Monad: sequence effects that depend on previous values
A monad adds flatMap (also called bind, >>=, or mapcat for
sequences). The key shape:
bind : F[A] → (A → F[B]) → F[B]
What makes this more than fmap? The next step's context depends on the
value of the previous step. That's how the Maybe monad short-circuits on
the first nil, how futures chain, how parsers consume input based on what
they saw. mapcat is the canonical monad operation on sequences.
The monad laws (left identity, right identity, associativity) are exactly the laws that make do-notation / for-comprehensions behave consistently.
A unified picture
| Abstraction | What it gives you |
|---|---|
| Semigroup | combine two |
| Monoid | combine two + empty |
| Functor | map inside |
| Applicative | combine independent contexts |
| Monad | sequence dependent contexts |
Each layer adds one new capability and a few laws. Clojure doesn't enforce these as a type hierarchy, but recognizing them sharpens your sense of when code is composable and when it isn't.
Try it in Clojure
The hierarchy is purely a pattern — Clojure's built-ins already implement every layer; you just need to spot it.
Real-world: where the hierarchy pays off
| In production / open source | What's really happening |
|---|---|
reduce + commutative-monoid combine in Spark / Flink | Monoids let you split a reduce across machines and merge results in any order |
| Riemann's event streams, Onyx workflow combinators | Each step is a functor map or monad bind on a stream of events |
HTTP middleware (ring) | A middleware is a monoidal composition: comp of (handler → handler) functions |
core.async pipelines and manifold deferreds | Functors and monads over asynchronous results — chain, let-flow are bind/applicative |
| Datadog / Prometheus metric aggregation | Counters and gauges are monoids (sum, max); commutativity + associativity = safely shardable |
Apache Beam Combine transforms | Require an associative+commutative + and an identity — they explicitly demand a commutative monoid |
The slogan: "Things that combine and have an identity scale horizontally." When you can express your aggregation as a monoid, distributed execution is nearly free.
Check yourself
? quiz
What is the *additional* capability that a monad gives you that an applicative does not?
Exercise
For each of the following Clojure values + operations, classify the strongest algebraic structure they satisfy (semigroup / monoid / functor / monad):
(reduce str "" xs)over strings(map inc xs)over a vector(mapcat (fn [x] [x x]) xs)over a vector(reduce max xs)over a non-empty seq of numbers (no obvious identity)