宏高级技巧 #
一、宏卫生 #
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 宏设计原则 #
- 能用函数就不用宏
- 保持宏简单
- 提供清晰的错误信息
- 文档化宏的行为
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构建 | 创建领域语言 |
关键点:
- 保持宏卫生,使用Gensym
- 复杂逻辑用函数,宏只做代码转换
- 提供清晰的错误信息和文档
- 使用
macroexpand调试 - 宏是最后手段,优先使用函数
宏系列完成!接下来学习并发编程!
最后更新:2026-03-27