| name | clojure |
| description | Guide to plan/design/coding in Clojure. Use when working with Clojure projects, .clj files, deps.edn/project.clj configuration, or when the user asks about Clojure idioms, macros, or functional programming patterns. Covers REPL-driven development, data-oriented design, and core Clojure practices. |
Clojure
Core Principles
REPL-First Exploration
DO NOT use grep, Glob, or text search to explore Clojure code. Connect to a REPL and use namespace introspection. See "nREPL Workflow" section.
Data-Oriented Design
- Prefer plain data (maps, vectors, sets) over custom types
- Use keywords as keys:
{:name "Alice" :age 30}
- Data is immutable by default; embrace it
Pure Functions First
- Write pure functions that transform data
- Isolate side effects to system edges
- Compose small functions into larger workflows
Reuse Before Writing
Before creating a new function, search for existing code that does the same thing or something similar that could be generalized.
Requirements as Examples
Most requirements are examples of common scenarios - design for the general case. Some are truly one-off edge cases - implement narrowly. If unsure, ask.
Parallel Implementation with Subagents
Clojure's immutability and pure functions make parallel work safe by design - no shared mutable state means agents can't corrupt each other's work. Leverage this with jj workspaces for parallel feature implementation.
When to Parallelize
During planning, identify independent subtasks that can be implemented concurrently:
- Functions with no shared dependencies
- Separate namespaces or modules
- Tests and implementation (write in parallel)
- Frontend and backend changes
Workflow
- Plan phase: Break task into independent features
- Spawn subagents: Each in its own jj workspace
jj workspace add feature-a /tmp/workspace-feature-a
jj workspace add feature-b /tmp/workspace-feature-b
- Implement in parallel: Each agent works in isolation
- Merge: Bring workspaces back together
jj workspace forget feature-a feature-b
Planning Checklist
When planning a multi-part task, ask:
If yes to all → spawn parallel subagents with jj workspaces.
nREPL Workflow
Use clj-nrepl-eval for REPL-driven development.
Setup
clj-nrepl-eval --discover-ports
clj -Sdeps '{:deps {nrepl/nrepl {:mvn/version "RELEASE"}}}' -M -m nrepl.cmdline
clj-nrepl-eval -p <port> "(require '[clojure.repl :refer [doc source dir]])"
Explore Code
clj-nrepl-eval -p <port> "(map ns-name (all-ns))"
clj-nrepl-eval -p <port> "(dir myapp.core)"
clj-nrepl-eval -p <port> "(doc myapp.core/some-fn)"
clj-nrepl-eval -p <port> "(source myapp.core/some-fn)"
Understand Data
clj-nrepl-eval -p <port> "(keys some-map)"
clj-nrepl-eval -p <port> "(type result)"
clj-nrepl-eval -p <port> "(first large-coll)"
Development Cycle
- Edit code in source files
- Reload:
(require '[myapp.core :as core] :reload)
- Test:
(core/my-function arg)
- Iterate
Delimiter Repair
Fix unbalanced parens: clj-paren-repair <file.clj>
Idiomatic Patterns
Threading Macros
(-> person :address :city str/upper-case) ; object-first
(->> orders (filter :paid?) (map :total) (reduce +)) ; collection-last
(some-> user :profile :avatar :url) ; nil-safe
Destructuring
(defn greet [{:keys [first-name last-name]}]
(str "Hello, " first-name " " last-name))
(defn connect [{:keys [host port] :or {port 8080}}]
(str host ":" port))
(let [[head & tail] items]
(process head tail))
Polymorphism
;; Multimethods for open dispatch
(defmulti process-event :type)
(defmethod process-event :click [e] ...)
(defmethod process-event :default [e] ...)
;; Protocols for type-based dispatch
(defprotocol Persistable
(save! [this])
(load! [this id]))
Cross-Platform (.cljc)
Prefer .cljc Over .clj/.cljs
Use .cljc by default. Use reader conditionals for platform differences:
#?(:clj (java.util.UUID/randomUUID)
:cljs (random-uuid))
Macros in .cljc Files
A namespace cannot use its own macros during initial load in ClojureScript.
;; BAD: macro used in same namespace
(defmacro register! [type spec] `(swap! registry* assoc ~type ~spec))
(register! :foo {...}) ; FAILS in ClojureScript!
;; GOOD: convert to function if only doing runtime ops
(defn register! [type spec] (swap! registry* assoc type spec))
;; OR: move calls to separate namespace that requires this one
Self-referential macros pattern:
(ns mylib.util
#?(:cljs (:require-macros [mylib.util]))) ; Required for CLJS
(defmacro my-macro [...] ...)
API Design
Minimal Public Surface
- Public API namespace (e.g.,
mylib.core) with stable guarantees
- Implementation in internal namespaces (e.g.,
mylib.internal.*)
- Users CAN access internals but those MAY change
Backward-Compatible Extensions
Use multi-arity for optional arguments:
(defmacro my-macro
([required-arg] `(my-macro ~required-arg {}))
([required-arg opts] ...))
Options Pass-Through
Public wrappers forward options to internal implementations.
Testing
Unit Tests: RCT (inline)
Place ^:rct/test block immediately after the function:
(defn add [a b]
(+ a b))
^:rct/test
(comment
(add 5 3) ;=> 8
(add 0 0) ;=> 0)
Syntax: ;=> equality, ;=>> predicate/matcho, ;throws=>> exceptions
Integration Tests: test/ namespace
(ns myapp.integration-test
(:require [clojure.test :refer [deftest is testing use-fixtures]]))
(use-fixtures :each (fn [f] (db/with-test-conn f)))
(deftest api-integration-test
(testing "end-to-end flow"
(is (= expected (myapp.api/handler request)))))
Run Tests
bb test
bb test <component>
Code Style
- Naming: kebab-case, predicates end with ?, side-effects end with !
- Threading: prefer -> ->> cond-> cond->>
- Conditionals: use
when-let, if-let
- Docstrings: every public var has a concise docstring
Namespace Dependencies
A namespace can only require:
- Its children (e.g.,
foo.bar → foo.bar.baz)
- External libraries (e.g.,
clojure.walk)
- Exception: one
util namespace with pure functions only
;; ALLOWED
foo.bar → foo.bar.baz ; child
foo.bar → clojure.walk ; external
;; NOT ALLOWED
foo.bar.a → foo.bar.b ; sibling (causes cycles)
Error Handling
(throw (ex-info "Invalid input" {:field :email :value x}))
(try
(process data)
(catch ExceptionInfo e
(handle-error (:field (ex-data e)))))
Post-Task Review
After completing any non-trivial coding task, review for simplification:
- Delete - unused vars, dead branches, unnecessary nil checks
- Inline - single-use helpers that obscure rather than clarify
- Flatten - nested maps → flat maps, custom types → plain data
- Combine - multiple passes → single pass, separate conditions →
cond
- Remove abstractions - don't generalize for hypothetical futures
Ask: "If I were reading this for the first time, what would confuse me?"
Capture Findings
Add NOTE: comments for non-obvious behavior:
;; NOTE: api-pull returns symbol keys, not keywords
(get response 'all)