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.
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. |
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 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. |
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. |