Practice Clojure syntax with Java-to-Clojure rewrites that use maps, vectors, sequence transformations, let bindings, REPL checks, and immutable update patterns.
This page turns the syntax chapter into hands-on rewrites. The goal is not to memorize isolated forms. The goal is to practice the habits that make Clojure readable: model data directly, transform immutable values, name intermediate results with let, and check small expressions in the REPL.
Work through these examples by typing them into a REPL. Clojure syntax becomes familiar faster when you see values evaluate.
Java often starts with a class:
1record User(long id, String email, String status) {}
Clojure can start with a map:
1(def user
2 {:user/id 42
3 :user/email "DEV@EXAMPLE.COM"
4 :user/status :active})
Now write a small normalization function:
1(require '[clojure.string :as str])
2
3(defn normalize-user [user]
4 (update user :user/email #(str/lower-case (str/trim %))))
5
6(normalize-user user)
7;; => {:user/id 42
8;; :user/email "dev@example.com"
9;; :user/status :active}
What to notice:
| Java habit | Clojure habit |
|---|---|
| Call setters or constructors | Return a new map |
| Hide fields behind getters | Use explicit keys |
| Mutate an object | Transform a value |
| Put behavior on the class | Write functions over data |
Try it yourself: extend normalize-user so missing :user/status becomes :pending.
Java loop:
1List<String> paidEmails = new ArrayList<>();
2for (Order order : orders) {
3 if (order.status().equals("PAID")) {
4 paidEmails.add(order.customerEmail().trim().toLowerCase());
5 }
6}
Clojure data:
1(def orders
2 [{:order/id 1 :order/status :paid :order/customer-email "A@EXAMPLE.COM "}
3 {:order/id 2 :order/status :open :order/customer-email "b@example.com"}
4 {:order/id 3 :order/status :paid :order/customer-email " C@EXAMPLE.COM"}])
Clojure pipeline:
1(defn paid-emails [orders]
2 (->> orders
3 (filter #(= :paid (:order/status %)))
4 (map :order/customer-email)
5 (map #(str/lower-case (str/trim %)))
6 (into [])))
7
8(paid-emails orders)
9;; => ["a@example.com" "c@example.com"]
The ->> macro threads the collection through the last argument position. That fits sequence functions such as filter, map, and reduce.
Try it yourself: return a set of paid emails instead of a vector.
let For Named StepsJava local variables often describe a calculation:
1int subtotal = quantity * unitPrice;
2int discount = Math.min(subtotal, couponCents);
3int total = subtotal - discount;
Clojure let does the same without reassignment:
1(defn line-total [quantity unit-price-cents coupon-cents]
2 (let [subtotal (* quantity unit-price-cents)
3 discount (min subtotal coupon-cents)]
4 (- subtotal discount)))
5
6(line-total 3 500 200)
7;; => 1300
Use let when names clarify a calculation. Do not use it to simulate mutable variables.
Try it yourself: add tax after the discount and name the tax calculation.
Clojure map updates return new values:
1(def invoice
2 {:invoice/id 1001
3 :invoice/customer {:customer/email "dev@example.com"}
4 :invoice/totals {:subtotal-cents 1000
5 :tax-cents 130}})
Update a nested value:
1(assoc-in invoice [:invoice/customer :customer/status] :active)
2;; => {:invoice/id 1001
3;; :invoice/customer {:customer/email "dev@example.com"
4;; :customer/status :active}
5;; :invoice/totals {:subtotal-cents 1000
6;; :tax-cents 130}}
Transform a nested value:
1(update-in invoice [:invoice/totals :subtotal-cents] + 500)
2;; => {:invoice/id 1001
3;; :invoice/customer {:customer/email "dev@example.com"}
4;; :invoice/totals {:subtotal-cents 1500
5;; :tax-cents 130}}
Try it yourself: write a function that adds a discount under [:invoice/totals :discount-cents].
When a form feels confusing, isolate it:
1(filter :order/paid? [{:order/paid? true}
2 {:order/paid? false}
3 {:order/id 3}])
4;; => ({:order/paid? true})
Then make the result concrete if needed:
1(into []
2 (filter :order/paid?)
3 [{:order/paid? true}
4 {:order/paid? false}
5 {:order/id 3}])
6;; => [{:order/paid? true}]
REPL practice should answer specific questions:
| Question | REPL habit |
|---|---|
| What does this return? | Evaluate the smallest form |
| Is this lazy or concrete? | Check the printed result and type when needed |
| Did this mutate the original? | Evaluate the original value again |
| Is this a good function shape? | Try two or three representative inputs |
Before moving on, make sure you can:
map returns a sequence and mapv returns a vectorlet to name intermediate valuesassoc, update, assoc-in, and update-in-> or ->> to clarify a pipeline