' ~ ` @ 这些符号在Clojure中的含义
`表示syntax quote,'表示quote,~表示unquote,~@表示unquote splicing,详细说明下。如果某段代码前面加了'就表示这段代码被quote而不会去求值了,而`的syntax quote则表示会把相应的变量变成有namespace的形式,而~和`是搭配使用的,~必须在`的后面,并且~的数量不能超过`的数量,~是用来将变量的值替换到相应位置
' 不对表达式求值
` ( syntax-quote) 会对表达式解析
user=> '(foo bar)
(foo bar)
user=> `(foo bar)
(user/foo user/bar)
~ 对宏体内表达式计算, ~@ 对 seq 进行展开
user=> (defmacro dbg[x] `(let [x# ~x] (println '~x "=" x#) x#)) user=> (def x 5) user=> (def lst '(a b c)) user=> `(fred x ~x lst ~@lst 7 8 :nine) (user/fred user/x 5 user/lst a b c 7 8 :nine) user=> `(abc ~(symbol (str "i" "s" \- "cool"))) (user/abc is-cool) user=> `(max ~@(shuffle (range 10))) (clojure.core/max 8 7 1 9 0 6 4 2 3 5)
相关实现逻辑在 https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java
' 的实现 dispatchMacros['\''] = new VarReader(); public static class VarReader extends AFn{ public Object invoke(Object reader, Object quote) { PushbackReader r = (PushbackReader) reader; Object o = read(r, true, null, true); return RT.list(THE_VAR, o); } } `的实现 macros['`'] = new SyntaxQuoteReader(); public static class SyntaxQuoteReader extends AFn { // 代码省略........ } ~的实现 macros['~'] = new UnquoteReader(); static class UnquoteReader extends AFn{ public Object invoke(Object reader, Object comma) { PushbackReader r = (PushbackReader) reader; int ch = read1(r); if(ch == -1) throw Util.runtimeException("EOF while reading character"); if(ch == '@') { Object o = read(r, true, null, true); return RT.list(UNQUOTE_SPLICING, o); } else { unread(r, ch); Object o = read(r, true, null, true); return RT.list(UNQUOTE, o); } } }
我们想创建一个unqualified symbol 的时候,就会在symbol的后面添加#符号.
user=> `(x#) (x__6__auto__) ;;;定义dbg user=> (defmacro dbg[x] `(let [x# ~x] (println '~x "=" x#) x#)) #'user/dbg user=> (defn pythag [x,y] (*(* x x) (* y y))) #'user/pythag user=> (pythag 5 6 ) 900 user=> (defn pythag [x,y] (dbg(* (dbg (* x x)) (dbg (* y y))))) #'user/pythag user=> (pythag 5 6 ) (* x x) = 25 (* y y) = 36 (* (dbg (* x x)) (dbg (* y y))) = 900 900
学习Macro,自我感觉比较好的学习方式就是看Clojure中宏的实现,尝试自己写一下.再看两个宏的例子:通过source函数查看->和 ->>的内部实现:
user=> ( -> 25 Math/sqrt int list) (5) user=> ( ->> 25 Math/sqrt int list) (5) user=> (source ->) (defmacro -> "Threads the expr through the forms. Inserts x as the second item in the first form, making a list of it if it is not a list already. If there are more forms, inserts the first form as the second item in second form, etc." {:added "1.0"} ([x] x) ([x form] (if (seq? form) (with-meta `(~(first form) ~x ~@(next form)) (meta form)) (list form x))) ([x form & more] `(-> (-> ~x ~form) ~@more))) nil user=> (source ->>) (defmacro ->> "Threads the expr through the forms. Inserts x as the last item in the first form, making a list of it if it is not a list already. If there are more forms, inserts the first form as the last item in second form, etc." {:added "1.1"} ([x form] (if (seq? form) (with-meta `(~(first form) ~@(next form) ~x) (meta form)) (list form x))) ([x form & more] `(->> (->> ~x ~form) ~@more))) nil user=>
传给宏的代码是不会求值的,这点和函数非常不同,函数传的参数都是先求值再去做函数运算,看下面的例子
user=> (defn aa [] (println "aa") 1) #'user/aa user=> (defn bb [] (println "bb") 2) #'user/bb user=> (defn cc [c a b] (if c a b)) #'user/cc user=> (defmacro dd [c a b] (if c a b)) #'user/dd user=> (cc true (aa) (bb)) aa bb 1 user=> (dd true (aa) (bb)) aa 1
因为函数的参数是先求值的,所以调用cc的时候bb也被运行,这不是我们所希望的,我们所希望的是像dd那样只去执行aa,而不去执行bb,所以这里就需要用宏了。
还有一个宏和函数的重要的不同是宏是在编译代码的时候运行的,运行一次之后就会把宏的返回值替换到代码的相应位置了。所以宏的话其实更像是元编程一类的东西,用代码去生成代码。