~ / track C / clojure ecosystem

Category theory with cats

Advancedecosystem

cats (funcool/cats) is the library that makes the algebraic hierarchy usable in Clojure and ClojureScript. It provides protocol-backed implementations of Functor, Monad, Applicative, Monoid, and more — so the abstractions from the algebraic hierarchy become concrete types you reach for in real code. The runnable blocks on this page use a plain-Clojure stand-in because the in-browser REPL cannot load Clojars dependencies; the cats snippets are reference code you would run in a real project after adding [funcool/cats "2.4.2"] (or the current version) to your deps.

Run locally

The clojure fences below need a real Clojure/ClojureScript project with funcool/cats on the classpath. The in-browser REPL uses a plain-Clojure stand-in (the <Repl> blocks), so the cats snippets are reference code.

The problem: plumbing nil and errors

The moment a function can fail or return nothing, every caller must decide what to do with nil or an exception. Nested w‌h‌e‌n‌-‌l‌e‌t chains grow fast and the error path is rarely tested. What you want instead is a container that represents either a value or an absence, combined with lawful operations that thread the value through a pipeline and short-circuit cleanly on failure — without a single if nil in sight.

Functor: map inside a container

A Functor is any container you can fmap a function into without unpacking it. The simplest example is Maybe (called Just/Nothing): fmap inc on a Just 1 gives Just 2; on Nothing it stays Nothing.

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

With cats, fmap works over a real Maybe:

(require '[cats.core :as m]
         '[cats.monad.maybe :as maybe])
 
(m/fmap inc (maybe/just 1))   ;; => #<Just 2>
(m/fmap inc (maybe/nothing))  ;; => #<Nothing>

Monad: sequence dependent steps

fmap is not enough when each step depends on the previous result or can produce its own Nothing. That calls for bind (also written >>=): it unwraps the container, passes the value to a function that returns a new container, and short-circuits the whole chain if any step yields Nothing.

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

bind threads the unwrapped value and short-circuits on nothing. Cats packages this as mlet — Clojure's answer to Haskell's do-notation — which reads like a regular l‌e‌t but each binding is a monadic step:

(m/mlet [a (maybe/just 1)
         b (maybe/just (inc a))]
  (m/return (* b 2)))   ;; => #<Just 4>

m/return (also called pure) lifts a plain value back into the monad — here it wraps (* b 2) in a Just so the whole mlet expression produces a monadic value.

Either: typed success and failure

Maybe tells you whether something failed; Either tells you why. The Right branch carries a successful value; Left carries a typed failure. You compose Either pipelines with the same bind/mlet mechanics — the first Left short-circuits all subsequent bindings.

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

Either carries a typed failure on the left; mlet over Either short-circuits on the first Left — exactly the railway-oriented programming pattern. With cats:

(require '[cats.monad.either :as either])
 
(m/mlet [a (either/right 10)
         b (either/right (inc a))]
  (m/return (* a b)))   ;; => #<Right 110>

Applicative and Validation

Monadic mlet is sequential: each step can see the result of the one before it. Sometimes you want to run independent checks and collect all failures rather than stopping at the first. That is what Applicative gives you via alet, and it is why cats ships a Validation monad: the standard Either stops at the first Left; Validation accumulates them. The <$> and <*> operators are the Applicative analogues of fmap and a‌p‌p‌l‌y.

(require '[cats.applicative.validation :as v])
 
;; alet runs the bindings independently; with Validation, every failure is collected
(m/alet [name (v/fail [:name-required])
         age  (v/fail [:age-required])]
  (str name age))
;; => #<Fail [:name-required :age-required]>

Monoid and Foldable

mappend and mempty generalize the semigroup/monoid combination from the algebraic hierarchy to whatever type cats knows about: vectors, strings, sets, numbers with an appropriate identity. foldm lifts that idea to a monadic step, letting you fold a collection while threading an effect — useful for accumulating a result inside Either and bailing on the first problem:

(require '[cats.monad.either :as either])
(m/mappend [1 2] [3 4])   ;; => [1 2 3 4]
(m/foldm (fn [acc x] (if (neg? x) (either/left :neg) (either/right (+ acc x))))
         0
         [1 2 3])         ;; => #<Right 6>

When to reach for cats

Cats is powerful but deliberately niche: the Clojure community leans data-oriented, and most pipelines stay readable with plain maps and ex-info. See error handling for when that is enough and when a monadic container pays off. Cats pays off when you have heavy Maybe/Either composition across many steps, or when you are writing ClojureScript where nil-safety and typed errors matter most. If you find yourself writing the same i‌f‌-‌l‌e‌t chain three times, that is usually the signal to introduce a container. The source and full API are at github.com/funcool/cats and the guide at funcool.github.io/cats/latest/.

 status: new