Browse Learn Clojure Foundations as a Java Developer

Treat Clojure Code as Data

Learn how Clojure forms are ordinary lists, symbols, vectors, maps, and literals, and why that makes macro expansion feel like data transformation.

Code as data means a Clojure expression can be read as a data structure before it is evaluated. That idea is also called homoiconicity, but the practical lesson is simpler: forms are values you can inspect and transform.

1'(+ 1 2)
2;; => (+ 1 2)
3
4(+ 1 2)
5;; => 3

The first expression is a list. The second expression is evaluated as a function call.

What a Form Contains

1(first '(+ 1 2))
2;; => +
3
4(rest '(+ 1 2))
5;; => (1 2)

The list has a first element and remaining elements like any other list. The evaluator gives that list meaning later.

Source text Data shape before evaluation
(+ 1 2) A list whose first item is symbol +.
[1 2 3] A vector literal.
{:id 42} A map literal.
(if ok? :yes :no) A list whose first item is symbol if.

Reader, Data, Evaluation

    flowchart TB
	    A["Text: (+ 1 2)"] --> B["Reader builds list data"]
	    B --> C["Macro expansion can transform the form"]
	    C --> D["Evaluator produces runtime value: 3"]

Macros operate between the reader and later evaluation. They receive forms and return forms.

Java Comparison

Java source is not normally available as ordinary runtime data. Java annotation processors and code generators work outside the normal expression model; reflection inspects already-loaded runtime structures. Clojure puts a smaller source representation directly in the language.

Java tool What it manipulates Clojure comparison
Reflection Runtime classes and methods. Later than macro expansion.
Annotation processing Source/AST during compilation. Similar generated-code review concern.
String templates Text that must be parsed later. Less structured than forms.
Clojure forms Lists, symbols, literals. Native data before evaluation.

Do Not Overuse eval

Because code is data, it is tempting to reach for eval. In application code, that is usually the wrong tool. Macros transform code during expansion. Functions transform data at runtime. eval mixes runtime data and code execution in a way that is harder to secure, test, and reason about.

Goal Prefer
Transform data A function.
Delay behavior A function argument or thunk.
Generate call-site syntax A macro.
Execute user-provided code Avoid unless you are explicitly building an evaluator.

Knowledge Check

### What does a quoted form such as `'(+ 1 2)` represent? - [x] A quoted form treated as data rather than evaluated. - [ ] The number `3`. - [ ] A Java bytecode instruction. - [ ] A namespace declaration. > **Explanation:** Quoting prevents evaluation and leaves the form available as data. ### Why does code as data matter for macros? - [x] Macros receive forms as data and return new forms. - [ ] Macros run after every runtime function call. - [ ] Macros convert Clojure to Java source strings. - [ ] Macros replace the reader. > **Explanation:** A macro is a transformation step over Clojure forms before the generated form is evaluated or compiled. ### When should you prefer a function over `eval`? - [x] When you are transforming ordinary runtime data. - [ ] When you need to execute arbitrary source text. - [ ] When you are writing a compiler. - [ ] When a macro must introduce syntax. > **Explanation:** Most runtime work is data transformation, not code execution. Functions keep that work testable and predictable.
Revised on Saturday, May 23, 2026