宏基础 #

一、宏简介 #

宏是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 定义宏
` 语法引用
~ 解引用
~@ 解引用拼接
# 自动生成符号

关键点:

  1. 宏在编译期操作代码
  2. 代码即数据,数据即代码
  3. 使用 macroexpand 调试宏
  4. 避免多次求值和变量捕获
  5. 宏应该简洁,复杂逻辑用函数

下一步,让我们学习宏高级技巧!

最后更新:2026-03-27