为什么这个clojure宏需要“〜”?

(道歉,如果这是另一个问题的重复,我搜索所有那些奇特的特殊字符没有产生任何东西。)

我正在阅读掌握Clojure宏并无法理解以下示例:

(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什么区别?

`'~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)))
=> {}

我在这里错过了什么?


我们首先确定宏里面的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]]

一些可选的和等同的写作方式

`'~k

是:

`(quote ~k) ;; expands the ' reader macro to the quote special form

(list 'quote k) ;; avoids syntax quote entirely

你认为这是非常正确的

最内层的unquote〜应该简单地回复最外层语法引用的效果

你的描述中唯一缺少的是你不能在语法引用的表达式之外拉quote ,因为quote是一种特殊的形式,并且改变了里面的内容。 否则, '`~k将等于'k - 并且正如你注意到的那样,它不是!

我会回应@ amalloy的一般建议,在宏/宏扩展的上下文之外,尝试在REPL中使用语法引用的东西是让您的头脑解决这些问题的最佳方式。

ps另外,我会做一个说明,我需要通过在未来的书籍版中解释更好的方式来解决这个困惑;)

链接地址: http://www.djcxy.com/p/65771.html

上一篇: Why does this clojure macro need `'~?

下一篇: Clojure macros: quoting and syntax quoting