clojure实战——引述相关’`~~@
clojure中,经常看到一些诸如’ ` ~ ~@ #’ 之类的符号,本博客专门针对其中与引述相关的三个符号(其实就是函数)进行详细说明。这三个符号(函数)在clojure.core中定义,因此可直接在repl中使用。
普通引述——’
‘:普通引述。
’ 等同于 quote,是它的简写版;返回参数的不求值的形式。
普通引述不允许反引述
示例:
(comment
(def a 1)
(def b 2)
'(+ a b)
;;=> 返回一个列表数据结构:(+ a b), a、b不会被求值
(+ a b)
;; => 3 ,a、b被求值,整个表达式被求值
)
语法引述——`
`:语法引述
语法引述将无命名空间的符号求值为其所在命名空间的符号(sysmbol)。
如果符号本身有命名空间限定,则语法引述也会将其求值为对应命名空间.
这种符号的默认空间化可以防止我们不会因为疏忽而重定义了一个已经定义过的值(宏卫生)。
语法引述也返回参数的不求值形式,只是对参数中所有符号求值为其所在命名空间的符号。
语法引述允许反引述
示例:
(comment
(in-ns 'user)
`foo
;; => user/foo
;; 新建一个命名空间,并在该命名空间进行以下操作
(ns aa (:require [user :as u]))
`map
;; => clojure.core/map
`u/foo
;; => user/foo
`foo
;; => aa/foo
`(+ (* 1 2) 3)
;; => (clojure.core/+ (clojure.core/* 1 2) 3)
)
反引述——~
~:反引述
语法引述返回不求值的形式,反引述则相当于告诉语法引述”你不要对我进行语法引述,是被求值的。”
在编写宏时,经常会对列表里的一些元素不进行求值,对另外一些元素进行求值,此时就可以结合语法引述与反引述写成简洁的代码:
语法引述整个列表,而对要进行求值的符号使用反引述。
反引述只能用于被语法引述或普通引述的符号或列表中。
反引述一个列表或vector会把整个形势都反引述。
示例:
(comment
(ns user)
(def foo 123)
(def foo1 234)
`~foo
;; => 123
;; ~foo 和 ~`foo均错误
`(map println [~foo foo1])
;; => (clojure.core/map clojure.core/println [123 clojure-async.symbols/foo1])
;; 其中map和println都被语法引述了,
`(map println ~[foo foo1])
;; => (clojure.core/map clojure.core/println [123 234])
;; 其中[]中的两个符号都被反引述了,因此会被求值。因为整个列表被语法引述,所以返回不被求值的列表结构
)
编接反引述——~@
~@:编接反引述
编接反引述的作用是:自动进行列表的链接,即将一个列表的内容解开,加入到第一个列表中去。
在编写宏时很常见。
编接反引述用于引述的的列表中。
编接反引述不会像反引述那样告诉编译要被求值。
示例:
(comment
(let [defs '((def x 123) (def y 234))]
`(do ~@defs))
;; => (do (def x 123) (def y 234))
;; 其中,defs本身是一个列表,(do ...)也是一个列表,~@defs将defs列表中的两个元素解开,加入到(do ...) 这个列表中。
(defmacro plus
[& nums]
`(+ ~@nums))
(def a 1)
(def b 2)
(def c 3)
(macroexpand-1 '(plus a b c))
;; => (clojure.core/+ a b c)
(plus a b c)
;; => 6
;; 当你在repl中执行这一句时,编译器先对宏进行求值,返回数据结构:(clojure.core/+ a b c)其中a、b、c都没被求值,
;; 在执行期间在对a、b/c求值,然后对个数据结构求值,返回6
)
小结
`, ~, ~@, 都是clojure标准库中的函数,只是以这种符号的形式暴露出来,便于使用。在编写宏时非常常见。