~ / track E / deep theory

The algebraic hierarchy

Advanced

In 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
Stringsstr
Vectorsinto (or concat)
Mapsmerge

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
Stringsstr""
Vectorsinto[]
Mapsmerge{}

(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

AbstractionWhat it gives you
Semigroupcombine two
Monoidcombine two + empty
Functormap inside
Applicativecombine independent contexts
Monadsequence 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.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Real-world: where the hierarchy pays off

In production / open sourceWhat's really happening
reduce + commutative-monoid combine in Spark / FlinkMonoids let you split a reduce across machines and merge results in any order
Riemann's event streams, Onyx workflow combinatorsEach 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 deferredsFunctors and monads over asynchronous results — chain, let-flow are bind/applicative
Datadog / Prometheus metric aggregationCounters and gauges are monoids (sum, max); commutativity + associativity = safely shardable
Apache Beam Combine transformsRequire 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):

  1. (reduce str "" xs) over strings
  2. (map inc xs) over a vector
  3. (mapcat (fn [x] [x x]) xs) over a vector
  4. (reduce max xs) over a non-empty seq of numbers (no obvious identity)
 status: new