宏高级技巧 #

一、宏卫生 #

1.1 什么是宏卫生 #

宏卫生是指宏不会意外捕获或污染外部变量。Clojure的语法引用默认提供卫生保证。

clojure
(defmacro safe-let [bindings & body]
  `(let [~@bindings]
     ~@body))

(let [x 10]
  (safe-let [x 20]
    x))

1.2 卫生问题示例 #

clojure
(defmacro unsafe-swap [a b]
  `(let [temp ~a]
     (set! ~a ~b)
     (set! ~b temp)))

(let [temp 100
      x 1
      y 2]
  (unsafe-swap x y)
  temp)

1.3 使用Gensym #

clojure
(defmacro safe-swap [a b]
  (let [temp-sym (gensym "temp")]
    `(let [~temp-sym ~a]
       (set! ~a ~b)
       (set! ~b ~temp-sym))))

(let [temp 100
      x 1
      y 2]
  (safe-swap x y)
  [x y temp])

1.4 自动Gensym #

clojure
(defmacro safe-swap [a b]
  `(let [temp# ~a]
     (set! ~a ~b)
     (set! ~b temp#)))

(macroexpand-1 '(safe-swap x y))

# 后缀自动生成唯一符号。

二、复杂参数解析 #

2.1 解析参数列表 #

clojure
(defmacro defn-cached [name & body]
  (let [doc-string (when (string? (first body)) (first body))
        params (if doc-string (second body) (first body))
        fn-body (if doc-string (nnext body) (next body))]
    `(defn ~name ~@(when doc-string [doc-string]) ~params
       (let [cache# (atom {})]
         (if-let [cached# (@cache# [~@params])]
           cached#
           (let [result# (do ~@fn-body)]
             (swap! cache# assoc [~@params] result#)
             result#))))))

(defn-cached expensive-calc [x]
  (println "Computing...")
  (* x x))

(expensive-calc 5)

(expensive-calc 5)

2.2 多参数列表 #

clojure
(defmacro defmulti-impl [name & impls]
  (let [dispatch-fn (first impls)
        methods (next impls)]
    `(do
       (defmulti ~name ~dispatch-fn)
       ~@(for [[dispatch-val params & body] methods]
           `(defmethod ~name ~dispatch-val ~params
              ~@body)))))

(defmulti-impl area :shape
  [:circle [{:keys [radius]}]]
  (* Math/PI radius radius)
  [:rectangle [{:keys [width height]}]]
  (* width height))

(area {:shape :circle :radius 5})

(area {:shape :rectangle :width 10 :height 5})

2.3 键值参数 #

clojure
(defmacro with-options [name options & body]
  (let [opts (apply hash-map options)]
    `(let [~@(mapcat (fn [[k v]] [k v]) opts)]
       ~@body)))

(with-options my-fn
  {:timeout 5000
   :retries 3}
  [timeout retries])

三、宏组合 #

3.1 宏调用宏 #

clojure
(defmacro with-logging [name & body]
  `(do
     (println "Starting:" '~name)
     (let [result# (do ~@body)]
       (println "Finished:" '~name)
       result#)))

(defmacro with-timing [name & body]
  `(let [start# (System/nanoTime)
         result# (do ~@body)
         end# (System/nanoTime)]
     (println "Time for" '~name ":" (/ (double (- end# start#)) 1000000.0) "ms")
     result#))

(defmacro with-instrumentation [name & body]
  `(with-logging ~name
     (with-timing ~name
       ~@body)))

(with-instrumentation my-operation
  (Thread/sleep 100)
  :done)

3.2 宏返回宏 #

clojure
(defmacro deftracer [name]
  `(defmacro ~name [expr]
     `(let [result# ~expr]
        (println '~name ":" '~expr "=" result#)
        result#)))

(deftracer trace)

(trace (+ 1 2))

四、解析器宏 #

4.1 自定义解析 #

clojure
(defmacro let-if [bindings condition & body]
  (let [pairs (partition 2 bindings)]
    `(if ~condition
       (let [~@(mapcat identity pairs)]
         ~@body)
       (let [~@(mapcat (fn [[k v]] [k nil]) pairs)]
         ~@body))))

(let-if [x 10 y 20] true
  [x y])

(let-if [x 10 y 20] false
  [x y])

4.2 模式匹配宏 #

clojure
(defmacro match [expr & clauses]
  (let [g (gensym)]
    `(let [~g ~expr]
       (cond
         ~@(mapcat
             (fn [[pattern result]]
               (if (= pattern :else)
                 [true result]
                 [`(= ~g ~pattern) result]))
             (partition 2 clauses))))))

(match 1
  1 "one"
  2 "two"
  :else "other")

(match 5
  1 "one"
  2 "two"
  :else "other")

4.3 DSL宏 #

clojure
(defmacro defroute [name & routes]
  `(defn ~name [request#]
     (case (:path request#)
       ~@(mapcat
           (fn [[path handler]]
             [path `(~handler request#)])
           (partition 2 routes))
       {:status 404 :body "Not Found"})))

(defroute api-routes
  "/users" (fn [req] {:status 200 :body "Users"})
  "/posts" (fn [req] {:status 200 :body "Posts"}))

(api-routes {:path "/users"})

(api-routes {:path "/posts"})

(api-routes {:path "/unknown"})

五、高级技巧 #

5.1 编译期计算 #

clojure
(defmacro compile-time-inc [n]
  (inc n))

(compile-time-inc 5)

(macroexpand-1 '(compile-time-inc 5))

5.2 类型提示注入 #

clojure
(defmacro def-fast [name params & body]
  (let [hinted-params (mapv (fn [p]
                              (if (vector? p)
                                (with-meta (first p) {:tag (second p)})
                                p))
                            params)]
    `(defn ~name ~hinted-params
       ~@body)))

(def-fast add [[x Long] [y Long]]
  (+ x y))

5.3 条件编译 #

clojure
(defmacro when-compile [condition & body]
  (when condition
    `(do ~@body)))

(when-compile true
  (println "This is compiled in"))

(when-compile false
  (println "This is not compiled in"))

5.4 内联函数 #

clojure
(defmacro inline-map [f coll]
  (if (vector? coll)
    (mapv (fn [x] `(~f ~x)) coll)
    `(map ~f ~coll)))

(inline-map inc [1 2 3])

(macroexpand-1 '(inline-map inc [1 2 3]))

六、调试宏 #

6.1 宏展开追踪 #

clojure
(defmacro macro-trace [form]
  `(do
     (println "Input:" '~form)
     (println "Expand-1:" (quote ~(macroexpand-1 form)))
     (println "Expand:" (quote ~(macroexpand form)))
     ~form))

(defmacro my-when [test & body]
  `(if ~test (do ~@body)))

(macro-trace (my-when true (println "hi")))

6.2 宏断言 #

clojure
(defmacro assert-macro [expected actual]
  `(let [exp# ~expected
         act# ~actual]
     (when-not (= exp# act#)
       (throw (ex-info "Macro assertion failed"
                       {:expected exp# :actual act#})))))

(assert-macro
  '(if true (do (println "hi")))
  (macroexpand-1 '(my-when true (println "hi"))))

七、实践示例 #

7.1 状态机宏 #

clojure
(defmacro defstate [name & transitions]
  (let [states (set (map first transitions))]
    `(defn ~name [current# event#]
       (case [current# event#]
         ~@(mapcat
             (fn [[from to event]]
               [[from event] to])
             transitions)
         current#))))

(defstate door-state
  [:closed :open :open]
  [:open :close :closed])

(door-state :closed :open)

(door-state :open :close)

(door-state :closed :invalid)

7.2 验证DSL #

clojure
(defmacro validate [value & rules]
  (let [v (gensym "v")]
    `(let [~v ~value]
       (and
         ~@(for [[pred msg] (partition 2 rules)]
             `(or (~pred ~v)
                  (throw (ex-info ~msg {:value ~v}))))))))

(validate 5
  pos? "Must be positive"
  even? "Must be even"
  #(< % 10) "Must be less than 10")

(validate 6
  pos? "Must be positive"
  even? "Must be even"
  #(< % 10) "Must be less than 10")

7.3 查询构建器宏 #

clojure
(defmacro query [table & clauses]
  (let [where-clause (some #(when (= (first %) :where) %) clauses)
        select-clause (some #(when (= (first %) :select) %) clauses)]
    `{:table ~table
      :select ~(second select-clause)
      :where ~(second where-clause)}))

(query users
  :select [:id :name :email]
  :where {:active true})

八、最佳实践 #

8.1 宏设计原则 #

  1. 能用函数就不用宏
  2. 保持宏简单
  3. 提供清晰的错误信息
  4. 文档化宏的行为

8.2 错误处理 #

clojure
(defmacro def-enum [name & values]
  (when (empty? values)
    (throw (ex-info "def-enum requires at least one value" {})))
  `(do
     ~@(for [v values]
         `(def ~(symbol (str name "-" v)) ~v))
     (def ~name #{~@values})))

(def-enum status :active :inactive :pending)

status-active

status

8.3 文档化宏 #

clojure
(defmacro with-resource
  "Evaluates body with resource bound to expr, ensuring resource is closed.
   
   Example:
   (with-resource [r (FileInputStream. \"file.txt\")]
     (.read r))"
  [[sym expr] & body]
  `(let [~sym ~expr]
     (try
       ~@body
       (finally
         (.close ~sym)))))

九、总结 #

宏高级技巧要点:

技巧 用途
Gensym 避免变量捕获
参数解析 处理复杂参数
宏组合 构建复杂宏
编译期计算 优化性能
DSL构建 创建领域语言

关键点:

  1. 保持宏卫生,使用Gensym
  2. 复杂逻辑用函数,宏只做代码转换
  3. 提供清晰的错误信息和文档
  4. 使用 macroexpand 调试
  5. 宏是最后手段,优先使用函数

宏系列完成!接下来学习并发编程!

最后更新:2026-03-27