宏的核心作用
视作一个简单的工具
和其他语言不一样的地方,lisp的宏可以随意创建operator,如下面介绍的when。实现很简单,毫不费力。
参数不求值
由于micro接收的参数可以很复杂,而且不求值,因此可以接收list,在内部转换,加工后再执行,也可以接收完全不是lisp的语法,在内部转换成clojure能执行的list。
运行并打印表达式
利用参数不求值的特性,可以编写一个宏,先打印参数传递进来的表达式,然后再运行
user=> (defmacro print-eval [expression] (do (println expression) expression))#'user/print-eval
user=> (print-eval (+ 1 2))
(+ 1 2)
3
创建自己的DSL
如果将接收的参数固定为某种语法,这样就可以创建自己的DSL,而把clojure宏实现为DSL解析器。
宏与函数的区别
特点是允许在Clojure evaluate你的list之前,你能够像处理函数一样的处理它, 但是不evaluate。
如果你想把一个表达式作为参数传递给函数,并要求这个参数不求值,这是做不到的。比如下面的函数定义会报错:
user=> (def ignore-last-operand2 [function-call] (butlast function-call)) #_=> #_=> user=> CompilerException java.lang.RuntimeException: Too many arguments to def, compiling:(/tmp/form-init3734404425586418360.clj:1:1)
这种情况需要使用宏了。
user=> (defmacro ignore-last-operand [function-call] (butlast function-call)) #_=> #_=> #'user/ignore-last-operanduser=> (ignore-last-operand (+ 1 2 10))3
宏的语法
(宏名 描述字符串 {版本说明} [参数列表] (body1) (body2) (bodyN))
一个宏可以与多个body,这些body有点像C++里面的重载函数,根据参数数目的不同而决定运行哪一个body
比如0参数body可以写成这样
([] true)1个参数写成这样
([x] x)
多个参数这样
([x & next] `(let [and# ~x] (if and# (and ~@next) and#))))
注意,& next 是获取余下的的参数, 因为&左边出现了一个x, 所以next被绑定为余下的参数。