Macro第一部分-入门
Macro介绍
Lisp语言的一个特性是code is data,Macro是这一特性的终极体现,它允许程序员简洁方便的扩展自己的语言,是一种meta-programming的技巧。

Macro一般会用于:
- 语言扩展 (本文会展示,让clojure支持infix notation)
- 更简洁的代码 (第二部分我们看下如何使用Macro简化AntdD的调用)
- 封装重复模式 (第三部分我们会分析Reitit的Route和Ring的App)
Clojure很多的核心表达式都是Macro,比如when
(defmacro when ;defmacro 定义一个叫做"when"的Macro
"Evaluates test. If logical true, evaluates body in an implicit do.";; Doc 字符串
{:added "1.0"} ;; Meta Info
[test & body] ;; 第一个参数用来判断,后面的一组(一个或者多个表达式)
(list 'if test (cons 'do body))) ;; 返回一个list,这个list的实质是一个if表达式,把参数中body的表达式装在do里
使用上Macro和函数没有区别
(when (= 1 1 ) (println "1还真等于1呢!"))
macroexpand-1可以让我们看到when这个Macro做的工作: 展开为一个if语句,自动加上do
(macroexpand-1 '(when (= 1 1 ) (println "1还真等于1呢!")))
Macro是一类函数,以代码为输入,以代码为输出。而且只在编译阶段执行。
Lisp/clojure的函数可以在任何时候执行,即 1. 运行时 2. 编译时 3. 读入时执行。Macro就是一个在编译期间执行的函数,理解这一点对掌握宏尤其重要。
Macro的参数,在Macro执行前不会进行求值(eval),这一点也有别于函数,函数的参数如果是一个表达式,会首先求值再进入函数执行流程,而Macro的作为参数的表达式结构会原封不动传给Macro,供进一步操作。
Macro返回的结果会立即进行eval。读Macro的代码,发现多数情况是返回的是一个表达式(也有返回字符串,关键词,数字的情况,比较少)。但实际执行Macro返回的是是对这个list的求值(eval the-result-list)
Hello world
;; 在lumo环境中(本blog),namespace下的宏需要放到$macros中
(ns cljs.user$macros)
;; 最基础的宏
;; 注意返回值是个list
(defmacro hello [x]
(list str "hello " x) )
执行一下
(cljs.user/hello "world!")
macroexpand用来展开宏,返回一个未经过eval的原始结果,是一个非常常用的宏调试方法。
(macroexpand '(cljs.user/hello "world!"))
macroexpand的结果可以再次进行eval, 和直接执行这个宏的结果是一样的。
(eval (macroexpand-1 '(cljs.user/hello "world!")))
一个例子
lisp中list的第一个元素会被当成函数,这种被称为prefix notion,如果我们希望支持infix notation,我们可以写一个宏:
;; form是个表达式 (12 + 12)
(defmacro infix [form]
(list (second form) (first form) (nth form 2)))
看下结果:
(cljs.user/infix (15 + 20))
Macro是生成代码的一种方式,我们可以看看它具体是怎么加工、处理的代码
(macroexpand-1 '(cljs.user/infix (15 + 20)))
Macro的复杂性在于理解它的执行场景是两段式的(普通宏是这样的,如果宏生成宏的话,可以再增加一层。。。)
- 编译期间执行生成代码,此阶段要防止下阶段代码提前执行(引起来)
- 生成的代码要在执行期间结合执行环境(外部变量),要能避免冲突,比如说你生成的代码里有变量a,外部环境中也有个a,会造成冲突
这两段执行塞在同一段代码中,区分的方式就是引用/反引用,
改进版
(defmacro infix-better [form]
`(~(second form) ;注意 syntax-quote (`) unquote (~) !
~(first form)
~(nth form 2)))
上面这个例子中使用了syntax-quote和 unquote
当然,当前的版本没有考虑表达式嵌套层级的问题比如说(15 + (10 + 10))这种情况就应对不了,所以以下执行会出错
(cljs.user/infix (15 + (10 + 10)))
我们以递归的方式实现一版:
(defmacro r-infix [form]
(cond (not (seq? form))
form
(= 1 (count form))
`(r-infix ~(first form))
:else
(let [operator (second form)
first-arg (first form)
others (nth form 2)]
`(~operator
(r-infix ~first-arg)
(r-infix ~others)))))
展开:
(macroexpand-1 '(cljs.user/r-infix (15 + (10 + 10))))
求值:
(cljs.user/r-infix (15 + (10 + 10)))
但是对于多个并列表达式的情况,还是有问题的
(cljs.user/r-infix (10 + (2 * 3) + (4 * 5)))
递归
怎么算懂了递归:懂递归的时候
(defmacro r-infix [form]
(cond (not (seq? form))
form
(= 1 (count form))
`(r-infix ~(first form))
:else
(let [operator (second form)
first-arg (first form)
others (rest (rest form) )]
`(~operator
(r-infix ~first-arg)
(r-infix ~others)))))
大功告成
(cljs.user/r-infix (10 + (2 * 3) + (4 * 5)))