(道歉,如果这是另一个问题的重复,我搜索所有那些花哨的特殊字符并没有产生任何效果。)
我正在阅读Mastering Clojure Macros并且无法理解以下示例:
(defmacro inspect-caller-locals []
(->> (keys &env)
(map (fn [k] [`'~k k]))
(into {})))
=> #'user/inspect-caller-locals
(let [foo "bar" baz "quux"]
(inspect-caller-locals))
=> {foo "bar", baz "quux"}
以下内容与更简单的'k
之间有什么区别?
`'~k
据我了解,最里面的非引号~
应该简单地恢复最外层语法引用的效果,但是一个简短的实验表明它还有更多:
(defmacro inspect-caller-locals-simple []
(->> (keys &env)
(map (fn [k] ['k k]))
(into {})))
=> #'user/inspect-caller-locals-simple
(let [foo "bar" baz "quux"]
(inspect-caller-locals-simple))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: k in this context, compiling:(/tmp/form-init4400591386630133028.clj:2:3)
不幸的是,我通常的调查方法并不适用于此:
(macroexpand '(let [foo "bar" baz "quux"]
(inspect-caller-locals)))
=> (let* [foo "bar" baz "quux"] (inspect-caller-locals))
(let [foo "bar" baz "quux"]
(macroexpand '(inspect-caller-locals)))
=> {}
我在这里缺少什么?
答案 0 :(得分:4)
让我们首先确定宏内的k
:
(defmacro inspect-caller-locals []
(mapv (fn [k]
(println (class k)))
(keys &env))
nil)
(let [x 1]
(inspect-caller-locals))
;; Prints:
;; clojure.lang.Symbol
因此,函数内的每个k
都是符号。如果从宏返回一个符号(即从中生成代码),clojure将查找它引用的值并将其打印出来。例如,你可以这样做:
(defmacro inspect-caller-locals []
(mapv (fn [k]
[(quote x) k]) ;; not the "hard coded" x
(keys &env)))
(let [x 1]
(inspect-caller-locals))
;; Prints:
;; [[1 1]]
你想要的是实际的符号。问题(正如您所指出的)是quote
是一种特殊形式,无论您传递的是什么都不会生效。即,k将不会获得函数参数,但保留k
,这通常不是定义的:
(defmacro inspect-caller-locals []
(mapv (fn [k]
[(quote k) k])
(keys &env)))
(let [x 1]
(inspect-caller-locals))
;; => Error
(let [k 1]
(inspect-caller-locals))
;; Prints:
;; [[1 1]]
你以某种方式需要评估你传递给quote
的内容,但这不可能,因为那不是quote
所做的。其他功能,例如str
没有问题:
(defmacro inspect-caller-locals []
(mapv (fn [k]
[(str k) k])
(keys &env)))
(let [x 1]
(inspect-caller-locals))
;; Prints:
;; [["x" 1]]
诀窍是更深入一级并引用quote
本身,以便您可以将符号传递给它:
(defmacro inspect-caller-locals []
(mapv (fn [k]
[;; This will evaluate k first but then generate code that
;; wraps that symbol with a quote:
(list (quote quote) k)
;; Or equivalently and maybe easier to understand:
(list 'quote k)
k])
(keys &env)))
(let [x 1]
(inspect-caller-locals))
;; Prints:
;; [[x x 1]]
或者使用可以为您执行此操作的阅读器:
(defmacro inspect-caller-locals []
(mapv (fn [k]
[`(quote ~k)
`'~k
k])
(keys &env)))
(let [x 1]
(inspect-caller-locals))
;; Prints:
;; [[x x 1]]
因为毕竟:
(read-string "`'~k")
=> (clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list k)))
(defmacro inspect-caller-locals []
(mapv (fn [k]
[(clojure.core/seq (clojure.core/concat (clojure.core/list (quote quote)) (clojure.core/list k)))
k])
(keys &env)))
(let [x 1]
(inspect-caller-locals))
;; Prints:
;; [[x 1]]
答案 1 :(得分:1)
一些替代的,等效的写作方式
`'~k
是:
`(quote ~k) ;; expands the ' reader macro to the quote special form
(list 'quote k) ;; avoids syntax quote entirely
你认为这是非常正确的
最里面的unquote~应该简单地恢复最外层语法引用的效果
您的描述中唯一缺少的是,您无法将quote
拉出语法引用的表达式,因为quote
是一种特殊形式,会更改内部含义。除此以外,
'`~k
相当于'k
- 正如你所注意到的那样,它不是!{/ p>
我会回应@ amalloy的一般建议,在宏的/宏扩展的上下文中,在REPL,外部中尝试语法引用的东西,是了解这些事情的最好方法。
P.S。另外,我要记下,我需要通过在未来的书籍版本中更好地解释来解决这种困惑;)