Macros

Rules of macro club: (1) Don't write macros. (2) Unless you must: for new control flow, binding forms, or eliminating real boilerplate. Macros aren't values: you can't map or apply a macro.

Common pitfalls: double evaluation of arguments (bind them once), symbol capture (use name#), and forgetting that macros run at compile time.

;; A macro transforms code at compile time
(defmacro unless [test & body]
  `(if ~test nil (do ~@body)))

(unless false (println "runs"))
(macroexpand-1 '(unless false (println "runs")))
;; => (if false nil (do (println "runs")))

;; Syntax-quote auto-qualifies symbols and supports auto-gensyms with #
(defmacro with-timing [& body]
  `(let [start# (System/nanoTime)
         result# (do ~@body)]
     (println "Elapsed:" (/ (- (System/nanoTime) start#) 1e6) "ms")
     result#))
(with-timing (Thread/sleep 50) :done)

;; Manual gensym
(defmacro my-or2 [a b]
  (let [v (gensym "v")]
    `(let [~v ~a] (if ~v ~v ~b))))

;; A def-style macro
(defmacro defhandler [name & body]
  `(defn ~name [request#]
     (try ~@body
          (catch Exception e# {:status 500 :body (ex-message e#)}))))

;; Using &form and &env (advanced)
(defmacro show-locals [] `(quote ~(keys &env)))
(let [a 1 b 2] (show-locals))         ; (a b)

;; Tools
(macroexpand '(-> x f g))             ; full expansion of the top form
(clojure.walk/macroexpand-all '(when x (-> y f)))