Clojure macro

(defmacro dbg [x] `(let [x# ~x] (println '~x "=" x#) x#))
(defn tt [x] (dbg (+ x 1)))

;user> (tt 1)
;(+ x 1) = 2
;2

可以看到我们用defmacro定义了一个dbg的宏,这个宏的作用就是先实现代码喝代码执行的结果,并将结果 返回回去。在clojure的宏里我们主要会用到以下几个符号。

  • ’ (quote) :表示被quote不求值
  • ` (syntax quote) :把变量变成有namespace的形势
user> 'x
x
user> `x
user/x
  • ~ (unquote):⚠️~和点搭配使用时,~必须在其的后面,并且~的数量不能超过点的数量,~是用来将变量的值替换到相应位置,比如
user> (def a 123)
#'user/a
user> `(def b ~a)
(def user/b 123)

可以看到~a被替换为a的值123了,而~@的作用和~类似,不过~@不但会替换掉值而且会把括号去掉。

  • ~@ (unquote splicing)
user> (def c [1 2 3])
#'user/c
user> `(def d [~@c])
(def user/d [1 2 3])
(defmacro t1 [] (let [a1 (+ 1 1)] `(defn cc [] println ~a1)))

;user> (t1)
;#'user/cc
;user> (cc)
;2

传递给宏的代码是不会求值的,这点和函数非常不同,函数传的参数都是先做函数运算,如下例子所示:

(defn aa [] (println "aa") 1)
(defn bb [] (println "bb") 2)
(defn cc [c a b] (if c a b))
(defmacro dd [c a b] (if c a b))

;user> (cc true (aa) (bb))
;aa
;bb
;1
;user> (dd true (aa) (bb))
;aa
;1
;⚠️结果的区别,因为行数的参数是先求值的,所以调用cc的时候bb也被运行了,这不是我们希望看到的,我们希望看到的是像dd那样只执行aa函数,这时候我们就需要宏了。
;宏是在编译代码的时候运行的,运行一次之后就会把宏的返回值替换到代码的相应位置,所以宏更像是元编程一类的东西,用代码去生成代码。

Clojure宏