Read and write Clojure expressions by understanding prefix form structure, evaluation order, special forms, macro expansion, and the difference between code lists and data literals.
Clojure code is built from forms. A form is anything the reader can read and the evaluator can evaluate: a number, string, keyword, symbol, list, vector, map, set, or a reader form such as quote.
For Java engineers, the important shift is that Clojure code is expression-oriented. You usually compose values returned by forms instead of writing a sequence of statements that mutate local variables.
| Java shape | Clojure shape | What changes |
|---|---|---|
int x = 10; |
(def x 10) |
A definition is a form that returns a Var |
x + y |
(+ x y) |
Operators are functions in first position |
if (...) { ... } else { ... } |
(if condition then-value else-value) |
if returns a value |
method(arg1, arg2) |
(function arg1 arg2) |
Function position comes first |
| Statement block | Nested forms or let |
Local names and returned values are explicit |
The syntax is smaller than Java’s, but the reading habit is different: read the outer form, then recursively read its arguments.
A normal function call has this shape:
1(function argument-1 argument-2 argument-3)
Arithmetic follows the same rule:
1(+ 1 (* 2 3))
2;; => 7
Read it as a tree:
flowchart TD
A["+"] --> B["1"]
A --> C["*"]
C --> D["2"]
C --> E["3"]
Java makes precedence rules do some of this work:
1int result = 1 + 2 * 3;
Clojure makes the tree explicit. That is why deeply nested code can stay unambiguous even when many functions are composed.
| Form kind | Example | Evaluation behavior |
|---|---|---|
| Literal | 42, "Ada", :active |
Evaluates to itself |
| Symbol | customer |
Resolves to a local, Var, class, or special meaning in context |
| List | (+ 1 2) |
Evaluates as an invocation, special form, or macro form |
| Vector | [1 (+ 1 2)] |
Evaluates each element and returns a vector |
| Map | {:total (+ 10 5)} |
Evaluates keys and values, then returns a map |
| Set | #{:read :write} |
Evaluates members, then returns a set |
| Quoted form | '(+ 1 2) |
Returns the form as data instead of evaluating it |
Parentheses do not mean “group this expression” the way they often do in Java. In Clojure, a list in evaluation position usually means “call this.”
This evaluates a function call:
1(+ 1 2 3)
2;; => 6
This returns list data:
1'(+ 1 2 3)
2;; => (+ 1 2 3)
That difference matters because Clojure code is data-shaped. Macros, code-generation tools, and REPL exploration all rely on the fact that a code form can be read, inspected, transformed, and then evaluated.
let Replaces Many Temporary VariablesJava often uses a sequence of local assignments:
1int subtotal = quantity * unitPrice;
2int tax = Math.round(subtotal * taxRate);
3int total = subtotal + tax;
Clojure uses let for local names and returns the final form:
1(let [subtotal (* quantity unit-price)
2 tax (Math/round (* subtotal tax-rate))]
3 (+ subtotal tax))
let is not a mutable block. It creates lexical names for values. If you need a changed value, create a new name or return a transformed value.
Not every list is an ordinary function call.
| Category | Examples | Why it matters |
|---|---|---|
| Special forms | if, let, do, fn, quote, var |
Built into the evaluator because they control evaluation itself |
| Macros | when, cond, ->, ->>, defn |
Transform code before evaluation |
| Functions | map, filter, reduce, +, str |
Evaluate arguments, then call a function value |
Java engineers often look for syntax keywords. In Clojure, some syntax is ordinary functions, some is special forms, and much convenient syntax is macros. The practical reading question is: “Does this form evaluate all arguments normally, or does it control evaluation?”
Nested calls are explicit, but nesting can become hard to scan:
1(require '[clojure.string :as str])
2
3(str/lower-case (str/trim email))
Threading macros show the pipeline:
1(-> email
2 str/trim
3 str/lower-case)
That is still an expression. The threaded form returns a value and does not mutate email.
| Mistake | Better reading |
|---|---|
| “Parentheses are just grouping.” | Parentheses usually mean invocation or a special syntactic form |
| “The last line is only a statement.” | The last form’s value is usually the result |
“if is a statement.” |
if is an expression that returns one of two values |
“A list literal is written as (1 2 3).” |
Quote it as '(1 2 3) or build it with (list 1 2 3) |
| “Macros are runtime callbacks.” | Macros rewrite forms before runtime evaluation |
1 + 2 * 3 as a Clojure expression and explain the evaluation tree.(+ 1 2) and '(+ 1 2) and describe the difference.let.->.let, if, and threading macros make expression-oriented code readable.