Clojure是一门Lisp方言,Lisp这个名称是"LISt Processor"的缩写。这里有一个深层次的含义:Lisp可以处理List数据,同时Lisp代码本身也是List数据。
与其它编程语言不同,Clojure(Lisp)的代码并不是由字符拼成的语法(关键字,空格,分号,大括号等,比如 private final String func1() {}
)组成。Clojure的代码本身就是一个list数据结构。
比如(+ 1 2)
这段代码,本身是一个list,三个元素分别是 +, 1, 2。
这样的设计,导致Clojure是一个非常简单的语言,整个语言语法,用一页纸就可以写完。除了少量的特殊形式,其它的所有代码都用统一的规则处理。
Clojure代码的执行过程,主要由编译和执行两个部分组成。在编译期,由Reader负责将代码处理解析为list数据结构,然后将其转化为JVM字节码,然后执行。
Clojure中的Reader相当于其它语言中的parser,Reader处理之后的结果,是一个数据结构(代码即数据)。数据结构会被执行:
可以被调用的List,叫表达式(expression),也叫形式(form)。有三种form:函数,宏,特殊形式(form)
函数是对值进行处理,将值变换为另一个值。Macro是对代码进行处理,将代码变换为另一段代码。
Clojure本身也很擅长处理list数据结构,那有没有可能用Clojure代码生成用来表达调用的list数据结构?用代码生成代码(用代码生成数据,用数据生成数据)?这就是Clojure中的宏(Macro)。
quote
是个特殊的函数,将这个函数作用于一个list上,Clojure将不执行这个list结构。
比如 (+ 1 1)
的执行结果是 2,而 (quote (+ 1 1))
的执行结果是 (+ 1 1)
。(eval (quote (+ 1 1)))
的结果是2
quote
可简写为 '
。 (= '(+ 1 1) (quote (+ 1 1)))
的执行结果是true
有一个特殊的quote,叫syntax quote,符号是 `。这个特殊的quote,与普通quote的最大区别。是它会将表达式中的函数,加入namespace前缀。
`(+ 1 1) ;; 等价于 '(clojure.core/+ 1 1)
在syntax quote的表达式(expr)中,可以用 ~
来表示某个表达式要计算(类似于javascript模板字符串里的${expr}
)。
`(+ 1 ~(+ 1 1)) ;; 等价于 '(clojure.core/+ 1 2)
(def quoted-fn `(+ 1 1))
(eval `(+ 2 ~quoted-fn)) ;; 4
syntax quote与unquote可重复执行在一个值上,执行的时候从左到右顺序执行比如:
(let [y '(- x)]
(println ``y) ;; (quote user/y)
(println `~y) ;; (- x)
(println ``~y)) ;; user/y,先对y quote两次,然后unquote一次
可以在syntax quote中,用 ~@
去掉一层 list (),比如:
`(+ 1 ~@'(1 1)) ;; 等价于`(clojure.core/+ 1 1 1)
定义macro,就是用defmacro
定义一个可执行体。在编译期执行并返回一个list,然后在执行期转换并运行。macro与function的一个非常重要的区别是函数在调用时对参数进行求值,Macro不对参数进行求值。
比如下面的例子:
(defn func-1 [v]
(println v)
(println v))
(defmacro macro-1 [v]
`(do (println ~v)
(println ~v)))
;; 执行结果是: step1, step2, step2
(func-1 (do (println "step1")
"step2"))
;; 执行结果是: step1, step2, step1, step2
(macro-1 (do (println "step1")
"step2"))
定义Macro的过程,就是定义一个生成代码结构的过程。一个Macro的返回值,一定是一个list,而且这个list是代码,可以执行。
infix
(defmacro infix
[[param1 op param2]]
`(~op ~param1 ~param2))
(infix (1 + 1)) ;; 相当于(+ 1 1)
unless
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
dbg
调试输出表达式
(defmacro dbg[x]
`(let [x# ~x]
(println "dbg:" '~x "=" x#)
x#))
doseq-indexed
自动给seq加一个索引变量
(defmacro doseq-indexed [index-sym [item-sym coll] & body]
`(doseq [[~item-sym ~index-sym]
(map vector ~coll (range))]
~@body))
(with-out-str
(doseq-indexed i [x [10 100 1000]]
(println "i: " i "x: " x)))