Clojure Macros
Macros are one of Clojure's most powerful features, allowing you to extend the language itself. They operate at compile-time, transforming code before evaluation.
Macro Basics
Key characteristics of macros:
- Receive unevaluated code as data structures
 - Return code that will be evaluated
 - Execute at compile time
 - Enable new language features and DSLs
 
Simple Macro Example
(defmacro unless [test body]
  (list 'if (list 'not test) body))
(macroexpand '(unless (> 3 2) (println "False")))
; => (if (not (> 3 2)) (println "False"))
(unless (> 3 2) (println "False")) ; => nil (no output)
                
                
                Syntax Quoting
Easier macro writing with syntax quoting (`), unquoting (~), and unquote-splicing (~@):
(defmacro unless [test body]
  `(if (not ~test) ~body))
(macroexpand '(unless (> 3 2) (println "False")))
; => (if (clojure.core/not (> 3 2)) (println "False"))
                
                
                Common Macro Patterns
Code Templating
(defmacro log [level & forms]
  `(when (>= ~level *log-level*)
     (println ~@(map #(list 'str %) forms))))
(log 3 "Error:" :message) ; Prints when *log-level* >= 3
                
                
                Control Structures
(defmacro do-until [& clauses]
  (let [test (last clauses)
        body (butlast clauses)]
    `(loop []
       ~@body
       (when ~test (recur)))))
(do-until
  (println "Looping")
  (> (rand) 0.9))
                
                
                Domain-Specific Languages
(defmacro defunit [name & conversions]
  `(defmacro ~(symbol (str name "->")) [val# unit#]
     (case unit#
       ~@(mapcat (fn [[u r]] [(keyword u) `(* ~val# ~r)])
            (partition 2 conversions))
       (throw (Exception. (str "Unknown unit: " unit#))))))
(defunit length
  "inch" 1
  "foot" 12
  "yard" 36
  "mile" 63360)
(length-> 2 "foot") ; => 24 (inches)
                
                
                Debugging Macros
macroexpand
(macroexpand '(unless true (println "Hi")))
; => (if (not true) (println "Hi"))
                
                
                macroexpand-1
(macroexpand-1 '(->> x (map inc) (filter even?)))
; => (filter even? (map inc x))
                
                
                clojure.walk/macroexpand-all
(require '[clojure.walk :as walk])
(walk/macroexpand-all '(doto x (.a) (.b)))
; => (let* [G__1234 x] (.a G__1234) (.b G__1234) G__1234)
                
                
                Advanced Macro Techniques
Generating Symbols
(defmacro with-resource [bindings & body]
  (let [resource (first bindings)
        close-fn (second bindings)]
    `(let [~resource ~(nth bindings 2)]
       (try
         ~@body
         (finally
           (~close-fn ~resource))))))
(with-resource [f .close (open-file "test.txt")]
  (println "Using file"))
                
                
                Compile-Time Computation
(defmacro compile-time-rand []
  (rand))
(defmacro runtime-rand []
  `(rand))
; Each call returns same value (computed at compile time)
(compile-time-rand) 
(compile-time-rand)
; Each call returns new random value
(runtime-rand)
(runtime-rand)
                
                
                Conditional Compilation
(defmacro feature-flag [flag & body]
  (if (System/getProperty flag)
    `(do ~@body)
    `(do (println "Feature disabled"))))
(feature-flag "new-ui" 
  (show-fancy-ui))
                
                
                Common Clojure Macros
-> and ->>
(-> 5 (+ 3) (* 2)) ; => 16
(->> (range 10) (map inc) (filter even?)) ; => (2 4 6 8 10)
                
                
                defprotocol and defrecord
(defprotocol Greeter
  (greet [this]))
(defrecord English []
  Greeter
  (greet [this] "Hello!"))
                
                
                with-open
(with-open [r (clojure.java.io/reader "file.txt")]
  (doseq [line (line-seq r)]
    (println line)))
                
                
                Macro Gotchas
- Variable capture: Use gensym or auto-gensym (#) to avoid
 - Double evaluation: Be careful with multiple evaluations of arguments
 - Order of expansion: Macros expand inside-out
 - Debugging difficulty: Use macroexpand to inspect
 
Variable Capture Example
(defmacro bad-macro [x]
  `(let [result ~x]
     (println result)))
(let [result 42]
  (bad-macro (inc result))) ; Error: result captured
                
                
                Fixed with gensym
(defmacro good-macro [x]
  (let [result (gensym "result")]
    `(let [~result ~x]
       (println ~result))))
                
                
                When to Use Macros
Good use cases for macros:
- Creating new control structures
 - Implementing domain-specific languages
 - Optimizing special cases at compile time
 - Wrapping boilerplate code
 
When to avoid macros:
- When a function would suffice
 - For runtime polymorphism
 - When clarity would suffer
 
Macro Writing Tips
- Write the expansion first, then the macro
 - Keep macros as simple as possible
 - Document macro usage clearly
 - Test with macroexpand
 - Avoid unnecessary macro nesting
 
Next Steps
Continue learning with:
- Clojure Concurrency - Working with state safely
 - Java Interop - Using Java libraries from Clojure
 - Spec - Declarative data validation