Clojure的宏

clojure

Clojure是运行在java虚拟机上的一种lisp的方言。说道lisp的话最先想到的应该是函数式编程,括号之类的词语,话说大家在读了《黑客与画家》这本书后肯定都会觉得lisp很厉害,都想来试试。而clojure作为lisp的一种方言,当然是包含了lisp的各种强大特点,而lisp的很强大很灵活的一个原因要归功于他的宏。说道宏的话,c里也是有宏的概念的,而lisp的宏其实本质和c里的宏都是一样的,都是做代码替换,但是lisp的宏应用起来更加强大。

先来感受下clojure宏的方便之处。比如有很多地方我们需要print代码和变量内容来差错,于是我们可以写这样一个宏。

(defmacro dbg[x] `(let [x# ~x] (println '~x "=" x#) x#))

当我们需要检查这段代码的时候

(defn tt [x] (+ x 1))

我们可以这样写

(defn tt [x] (dbg (+ x 1)))

当我们运行代码的时候我们能得到相应的显示结果

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

看到我们用defmacro定义了一个dbg的宏,这个宏的作用就是现实代码段和代码执行的结果,并将结果返回回去。在clojure的宏里我们主要会用到这么几个符号,首先是`表示syntax quote,’表示quote,~表示unquote,~@表示unquote splicing,详细说明下。如果某段代码前面加了’就表示这段代码被quote而不会去求值了,而`的syntax quote则表示会把相应的变量变成有namespace的形式,比如

user=> 'x
x
user=> `x
user/x

因为默认的namespace是user,所以写clojure的宏时会和自己开头定义的ns不同,要注意变化。而~和`是搭配使用的,~必须在`的后面,并且~的数量不能超过`的数量,~是用来将变量的值替换到相应位置,比如

user=> (def a 123)
#'user/a
user=> `(def b ~a)
(def user/b 123)

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

ser=> (def c [1 2 3])
#'user/c
user=> `(def d [~@c])
(def user/d [1 2 3])

那么几个符号我们都说清楚了,再说defmacro的作用就是在代码编译的时候,会把defmacro当作是函数运行一次,并且把这个的返回结果替换到原有的位置上去,就像这样

user=> (defmacro t1 [] (let [a1 (+ 1 1)] `(defn cc [] println ~a1)))
#'user/t1
user=> (t1)
#'user/cc
user=> (cc)
2

看上去宏和函数还是很相似的,为什么需要有宏这么个东西呢,首先我们需要注意的是,传给宏的代码是不会求值的,这点和函数非常不同,函数传的参数都是先求值再去做函数运算,看下面的例子

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,所以这里就需要用宏了。

还有一个宏和函数的重要的不同是宏是在编译代码的时候运行的,运行一次之后就会把宏的返回值替换到代码的相应位置了。所以宏的话其实更像是元编程一类的东西,用代码去生成代码。

参考资料:
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
http://orbbyrp.com/2012/06/lisp_first_step_macro.html
http://www.cnblogs.com/me-sa/archive/2013/03/18/clojure-macro.html
http://clojure.org/cheatsheet

您可能喜欢:
我猜您可能还喜欢:
, ,

发表回复