博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Clojure 宏 上篇
阅读量:5966 次
发布时间:2019-06-19

本文共 8936 字,大约阅读时间需要 29 分钟。

  开始接触Lisp宏是看Ansi Common Lisp的第十章,Lisp宏定义相关的话题都已经提到,有兴趣的可以看看.ACL的目前已经在Github上有中文译本 [ ],不要太担心Clojure与Lisp的语法差异,可以看下面这个对照表   . 
 
 
    Clojure 宏给人留下第一印象就是各种符号 ` ' ~ ~@ ,那就从这些符号怎么读开始吧
 
 

怎么读

 
user=> (defmacro foreach [[sym coll] & body]  `(loop [coll# ~coll]     (when-let [[~sym & xs#] (seq coll#)]       ~@body       (recur xs#))))#'user/foreachuser=>user=> (foreach [x [1 2 3]]  (println x))123niluser=>

 

  先看看 是怎么称呼这几个符号的:
 
  Syntax-quote (`, note, the "backquote" character), Unquote (~) and Unquote-splicing (~@) For all forms other than Symbols, Lists, Vectors, Sets and Maps, `x is the same as 'x.
 

怎么用

 
   知道了名字,下面就要看看符号的作用了:  Syntax-quote `防止宏内部的表达式求值,宏代码体内的代码替换到使用这个宏的地方.如果仅仅代码文本的替换,灵活性就有限了,我们使用 unquote符号~进行不宏代码体内的表达式;如果symbol代表的是一个seq,那么我们可以使用 Unquote-splicing (~@) 进行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)

 

 

` '的区别

 
Clojure  
' `区别在于 Syntax-quote (`)会进行symbol的解析
 
user=> '(foo bar)(foo bar)user=> `(foo bar)(user/foo user/bar)

 

 下面的代码中Syntax-quote 包含的代码中包含symbol x,而在当前的代码空间并没有user/x的定义,所以抛出了异常:
user=> (defmacro debug [x] `(println ">>" '~x ":" ~x  x))#'user/debuguser=> (let [a 10] (debug a))CompilerException java.lang.RuntimeException: No such var: user/x, compiling:(NO_SOURCE_PATH:72)user=>

 

 我们暂时把x随便换成一个数字23让代码可以执行,可以看到其它部分的代码都是正确的:
user=>  (defmacro debug [x] `(println ">>" '~x ":" ~x  23))#'user/debuguser=> (let [a 10] (debug a))>> a : 10 23niluser=>

 

 
 

syntax-quote 嵌套

 
   syntax-quote 将symbol解析成为fully-qulified symbol,所谓fully-qulified symbol 就是形如namespace/name或fully.qualified.Classname 如果是symbol是非名称空间限定的(non-namespace-qualified)且以#符号结尾,会解析成为name_uniqueid的形式比如x_123.
 
   这里遇到一个比较郁闷的问题就是syntax-quote嵌套,这个在Shell中测试的代码和直觉并不一致,我的问题是:
 
        user=> `'y 
(quote user/y) 
这个是可以理解的,'y等价(quote y),`'y 也就是`(quote y),结果是(quote user/y) 
 
但是``y 的结果和我预期的不一致: 
user=> ``y 
(quote user/y) 
 
我想的是`y 的结果是user/y,然后`user/y的结果是user/y,也就是说``y的结果应该是user/y 
 
 
   请教了豆瓣的友邻   得到解答:如果 syntex-quote 里面包含的是 resloved symbol ,那就简单的使用quote包围一下.这样对于上面的代码,``y结果是 (quote user/y),就可以理解了. 解答这种问题最好的方式就是看一下Reader的逻辑实现: 
 
' 的实现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);               }     }}

 

  通过下面这个几乎只会在试卷中出现的代码检查一下我们对符号嵌套的理解吧,平时没有人心理扭曲到写这种无聊的代码吧
 
user=>  (let [x 9, y '(- x)]  (println 0 y)  (println 1 `y)  (println 2 ``y)  (println 3 ```y)  ;(println 4 ~y)  (println 5 `~y)  (println 6 ``~y)  (println 7 ``~~y))0 (- x)1 user/y2 (quote user/y)3 (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list (quote user/y))))5 (- x)6 user/y7 (- x)niluser=>

 

  其实还可以更无聊一点,定义 x y 我们后面的测试就围绕这两个变量展开:
 
user=> (def x 12)#'user/xuser=> (def y 23)#'user/y user=> `y  user/yuser=> ``y  (quote user/y)user=> `'y(quote user/y)user=> ```y    (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list (quote user/y))))user=> ````y(clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/seq)) (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/concat)) (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/list)) (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list (quote quote)))))))) (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/list)) (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list (quote user/y)))))))))))))   下面开始折腾~运算符,首先看到~需要在`的情况下才有效,否则就会有下面这种错误 user=> ~y  IllegalStateException Attempting to call unbound fn: #'clojure.core/unquote  clojure.lang.Var$Unbound.throwArity (Var.java:43) user=> `~y23user=> `'y(quote user/y)user=> 'yyuser=> `'~y(quote 23)user=> `2323user=> `~~yIllegalStateException Attempting to call unbound fn: #'clojure.core/unquote  clojure.lang.Var$Unbound.throwArity (Var.java:43)user=> ``~~y23user=> `~'yyuser=> (= `y 'y)falseuser=> (= y 'y)falseuser=> 'yyuser=> (= 'y (quote y))trueuser=> (quote y)yuser=> ``~~y23user=> ``~yuser/yuser=> `~y23user=> (macexpand-1 ``~~y)CompilerException java.lang.RuntimeException: Unable to resolve symbol: macexpand-1 in this context, compiling:(NO_SOURCE_PATH:133)user=> (macroexpand-1 ``~~y)23user=> `'23(quote 23)user=>
 
 
 

宏展开

 
 "Clojure Programming" 书中有一个Clojure编译的流程图:
 
 
 
  我们可以使用macroexpand和macroexpand-1这样个辅助方法来查看宏展开的情况,下面是我们测试的代码:
  
 
user=>  (defmacro ya-defn [fn-name args & body]          `(defn ~fn-name ~args           (println "Calling ..." ~fn-name ~args)           ~@body))#'user/ya-defnuser=> (ya-defn add [a b]         (+ a b))#'user/adduser=> (add 2 3)Calling ... #
[2 3]5

 

   编译器进行宏展开,宏产出的代码成为原始程序的一部分.可以通过调用macroexpand-1 或者macroexpand 查看宏展开的结果,这两个函数的区别在于macroexpand会反复调用macroexpand-1进行宏展开,直到没有宏为止.下面的例子可以看到这个差别,注意为了清晰看到代码结构我在前面添加了pprint的函数调用.
 
user=> (macroexpand-1 '(ya-defn add [a b]  (+ a b)))(clojure.core/defn add [a b] (clojure.core/println "Calling ..." add [a b]) (+ a b))user=> (pprint (macroexpand-1 '(ya-defn add [a b]  (+ a b))))(clojure.core/defnadd[a b](clojure.core/println "Calling ..." add [a b])(+ a b))nil user=> (pprint (macroexpand '(ya-defn add [a b]  (+ a b))))(defadd(clojure.core/fn  ([a b] (clojure.core/println "Calling ..." add [a b]) (+ a b))))nil

 

  下面是函数macroexpand和macroexpand-1的源码实现,代码胜千言:
 
user=> (source macroexpand)(defn macroexpand  "Repeatedly calls macroexpand-1 on form until it no longer  represents a macro form, then returns it.  Note neither  macroexpand-1 nor macroexpand expand macros in subforms."  {:added "1.0"   :static true}  [form]    (let [ex (macroexpand-1 form)]      (if (identical? ex form)        form        (macroexpand ex))))niluser=> (source macroexpand-1)(defn macroexpand-1  "If form represents a macro form, returns its expansion,  else returns form."  {:added "1.0"   :static true}  [form]    (. clojure.lang.Compiler (macroexpand1 form)))niluser=>

 

 

auto_gensym机制

 
 我们想创建一个unqualified symbol 的时候,就会在symbol的后面添加#符号.
 
user=> `(x#)(x__6__auto__) ;;;定义dbguser=> (defmacro dbg[x] `(let [x# ~x] (println '~x "=" x#) x#))#'user/dbguser=> (defn pythag [x,y] (*(* x x) (* y y)))#'user/pythaguser=> (pythag  5 6 )900user=> (defn pythag [x,y] (dbg(* (dbg (* x x)) (dbg (* y y)))))#'user/pythaguser=> (pythag  5 6 )(* x x) = 25(* y y) = 36(* (dbg (* x x)) (dbg (* y y))) = 900900

 

 

从代码中学习

 
  学习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)))niluser=> (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)))niluser=>

   

附 一段很实用的宏:
user=> (source2 kw) 
(defmacro kw 
   "查询当前所有ns中含特定字符串的函数,如: (kw -index)" 
   [s] `(filter #(>= (.indexOf (str %) (name '~s)) 0) 
                (sort (keys (mapcat ns-publics (all-ns)))))) 
nil 
user=> (kw source) 
(*source-path* read-resource resource source source-fn source-fn2 source2) 
user=> (kw -index) 
(keep-indexed map-indexed safe-index)
 
来源: 
 
 
 
[0] 
 
[1] 
 
[2] 
 
[3]  
 
[4] 
 
[5] 
 
[6] 
 
 
最后小图一张:
 
 日本的侦探,名气最大的当然是金田一 -无论爷爷还是孙子.  by 青山刚昌
 
 

转载地址:http://odxax.baihongyu.com/

你可能感兴趣的文章
commons-fileupload 的详细介绍与使用
查看>>
Hessian HTTP POST访问时,Nginx返回411问题
查看>>
gradle学习(19)-log系统
查看>>
SpringAop通知
查看>>
推荐几本jquery书
查看>>
impala里面断言的用法
查看>>
JAVA来读取大文本文件
查看>>
《Genesis-3D游戏引擎系列教程-入门篇》九:发布到移动平台
查看>>
Service小结
查看>>
mysql的事物隔离机制?
查看>>
FreeMarker基本操作(二)
查看>>
数据结构与算法之KMP算法中Next数组代码原理分析
查看>>
WP Condition:wordpress的性能监测
查看>>
Creating Options Pages
查看>>
Eclipse中jsp、js文件编辑时,卡死现象解决汇总
查看>>
对于DOM的attribute和property的一些思考
查看>>
解决mysql“Access denied for user 'root'@'localhost'”
查看>>
elasticsearch-analysis-ik-1.10.0中文分词插件安装
查看>>
JDBC--调用函数与存储过程
查看>>
【Android笔记】WebView的使用
查看>>