Browse Learn Clojure Foundations as a Java Developer

Expressions and S-Expressions

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 Statements vs Clojure Forms

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.

Prefix Form Structure

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.

Evaluation Rules You Need First

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

Code Lists vs Data Lists

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 Variables

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

Special Forms and Macros

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?”

Threading Macros Improve Readability

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.

Common Reading Mistakes

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

Practice

  1. Rewrite 1 + 2 * 3 as a Clojure expression and explain the evaluation tree.
  2. Evaluate (+ 1 2) and '(+ 1 2) and describe the difference.
  3. Convert a Java temporary-variable block into a let.
  4. Rewrite a nested string cleanup expression using ->.

Key Takeaways

  • Clojure programs are built from forms that evaluate to values.
  • Prefix notation makes the expression tree explicit.
  • Lists usually evaluate as calls, special forms, or macros; quoted lists are data.
  • let, if, and threading macros make expression-oriented code readable.
  • Reading Clojure well means predicting evaluation, not translating parentheses into Java grouping.

Quiz: Expressions and S-Expressions

### What is the usual role of the first element in a Clojure list form? - [x] It identifies the function, macro, or special form to use. - [ ] It is always ignored. - [ ] It must be a Java class name. - [ ] It declares the return type. > **Explanation:** In an evaluated list, the first position determines what kind of operation the form performs. ### What does `(+ 1 (* 2 3))` evaluate to? - [x] `7` - [ ] `9` - [ ] `6` - [ ] `(+ 1 6)` > **Explanation:** `(* 2 3)` evaluates to `6`, then `(+ 1 6)` evaluates to `7`. ### What does quote do in `'(+ 1 2)`? - [x] It returns the form as data instead of evaluating it. - [ ] It turns the form into Java bytecode immediately. - [ ] It calls the `+` function twice. - [ ] It converts the form into a vector. > **Explanation:** Quote prevents normal evaluation and returns the list structure as data. ### Which Clojure form is closest to local temporary bindings in Java? - [x] `let` - [ ] `println` - [ ] `contains?` - [ ] `defproject` > **Explanation:** `let` creates lexical names for values inside an expression. ### True or False: Clojure `if` returns a value. - [x] True - [ ] False > **Explanation:** `if` is expression-oriented, so the selected branch value becomes the value of the `if` form.
Revised on Saturday, May 23, 2026