宏基础 #
一、宏简介 #
宏是Clojure最强大的特性之一。宏允许你在编译期操作代码,实现代码转换和语法扩展。
1.1 为什么需要宏 #
clojure
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
(unless false
(println "This will print"))
(unless true
(println "This won't print"))
宏让你可以创建新的语法结构。
1.2 宏vs函数 #
| 特性 | 函数 | 宏 |
|---|---|---|
| 求值时机 | 运行时 | 编译时 |
| 参数求值 | 先求值 | 不求值 |
| 返回值 | 数据 | 代码 |
| 用途 | 计算 | 代码转换 |
1.3 代码即数据 #
clojure
(def code '(+ 1 2 3))
(eval code)
(class code)
(first code)
(rest code)
二、defmacro #
2.1 基本语法 #
clojure
(defmacro name [params]
body)
(defmacro double-it [x]
`(* 2 ~x))
(double-it 5)
(macroexpand-1 '(double-it 5))
2.2 简单宏示例 #
clojure
(defmacro when2 [condition & body]
`(if ~condition
(do ~@body)))
(when2 true
(println "Hello")
:done)
(macroexpand-1 '(when2 true (println "Hello")))
2.3 查看宏展开 #
clojure
(defmacro my-if [condition then else]
`(if ~condition
~then
~else))
(macroexpand-1 '(my-if true "yes" "no"))
(macroexpand '(my-if true "yes" "no"))
三、语法引用 #
3.1 反引号 ` #
clojure
`(+ 1 2 3)
`(+ 1 2 ~(+ 1 2))
`[a b c]
语法引用保留符号,不进行求值。
3.2 解引用 ~ #
clojure
(def x 10)
`(+ 1 ~x)
(let [y 20]
`(+ ~x ~y))
~ 对表达式求值并插入结果。
3.3 解引用拼接 ~@ #
clojure
(def items [1 2 3])
`[~@items]
`(list ~@items)
`[~items]
~@ 将序列展开并拼接。
3.4 自动生成符号 #
clojure
(defmacro swap!- [a b]
`(let [temp# ~a]
(set! ~a ~b)
(set! ~b temp#)))
(macroexpand-1 '(swap!- x y))
# 自动生成唯一符号,避免名称冲突。
四、宏模式 #
4.1 控制流宏 #
clojure
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
(unless false (println "Executed"))
(defmacro when-not [condition & body]
`(if (not ~condition)
(do ~@body)))
(when-not false (println "OK"))
4.2 定义宏 #
clojure
(defmacro defconst [name value]
`(def ^:const ~name ~value))
(defconst PI 3.14159)
PI
4.3 调试宏 #
clojure
(defmacro debug [expr]
`(let [result# ~expr]
(println "Debug:" '~expr "=" result#)
result#))
(debug (+ 1 2 3))
(debug (map inc [1 2 3]))
4.4 时间测量宏 #
clojure
(defmacro time-it [expr]
`(let [start# (System/nanoTime)
result# ~expr
end# (System/nanoTime)]
(println "Time:" (/ (double (- end# start#)) 1000000.0) "ms")
result#))
(time-it (reduce + (range 1000000)))
五、宏展开工具 #
5.1 macroexpand-1 #
clojure
(defmacro my-when [condition & body]
`(if ~condition (do ~@body)))
(macroexpand-1 '(my-when true (println "hi")))
只展开一层宏。
5.2 macroexpand #
clojure
(macroexpand '(my-when true (println "hi")))
递归展开所有宏。
5.3 调试技巧 #
clojure
(defn show-macro [form]
(println "Original:" form)
(println "Expand-1:" (macroexpand-1 form))
(println "Expand:" (macroexpand form)))
(show-macro '(when true (println "hi")))
六、常见陷阱 #
6.1 多次求值 #
clojure
(defmacro bad-double [x]
`(+ ~x ~x))
(let [rand-val (rand-int 10)]
(bad-double rand-val))
(defmacro good-double [x]
`(let [val# ~x]
(+ val# val#)))
(let [rand-val (rand-int 10)]
(good-double rand-val))
6.2 变量捕获 #
clojure
(defmacro bad-swap [a b]
`(let [temp ~a]
(def ~a ~b)
(def ~b temp)))
(def x 1)
(def temp 100)
(bad-swap x temp)
x
使用自动生成符号避免:
clojure
(defmacro good-swap [a b]
`(let [temp# ~a]
(set! ~a ~b)
(set! ~b temp#)))
6.3 求值顺序 #
clojure
(defmacro bad-let [bindings & body]
`(let [~@bindings]
~@body))
(macroexpand-1 '(bad-let [x 1 y 2] (+ x y)))
(defmacro good-let [bindings & body]
`(let ~(vec bindings)
~@body))
七、实践示例 #
7.1 线程宏 #
clojure
(defmacro -> [x & forms]
(loop [x x forms forms]
(if forms
(let [form (first forms)
threaded (if (seq? form)
`(~(first form) ~x ~@(rest form))
`(~form ~x))]
(recur threaded (next forms)))
x)))
(-> 1 inc inc inc)
(macroexpand-1 '(-> 1 inc inc inc))
7.2 条件线程 #
clojure
(defmacro cond-> [expr & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
steps (map (fn [[test step]]
`(if ~test
(-> ~g ~step)
~g))
(partition 2 clauses))]
`(let [~g ~expr
~@(interleave (repeat g) (butlast steps))]
~(if (empty? steps)
g
(last steps)))))
(cond-> 1
true inc
false (* 2)
true inc)
7.3 记录日志宏 #
clojure
(defmacro defn-logged [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
(println "Entering:" '~name)
(let [result# (do ~@fn-body)]
(println "Exiting:" '~name "with result:" result#)
result#))))
(defn-logged add [a b]
(+ a b))
(add 1 2)
7.4 测试宏 #
clojure
(defmacro testing [description & body]
`(do
(println "Testing:" ~description)
~@body))
(defmacro is [expr]
`(if ~expr
(println "PASS:" '~expr)
(println "FAIL:" '~expr)))
(testing "Math operations"
(is (= (+ 1 2) 3))
(is (= (* 2 3) 6)))
八、总结 #
宏基础要点:
| 概念 | 说明 |
|---|---|
defmacro |
定义宏 |
` |
语法引用 |
~ |
解引用 |
~@ |
解引用拼接 |
# |
自动生成符号 |
关键点:
- 宏在编译期操作代码
- 代码即数据,数据即代码
- 使用
macroexpand调试宏 - 避免多次求值和变量捕获
- 宏应该简洁,复杂逻辑用函数
下一步,让我们学习宏高级技巧!
最后更新:2026-03-27