【问题标题】:Why does this clojure macro need `'~?为什么这个clojure宏需要`'~?
【发布时间】:2016-06-23 17:58:08
【问题描述】:

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

我正在阅读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

据我了解,最里面的 unquote ~ 应该只是恢复最外面的语法引用 ` 的效果,但是一个简短的实验表明它还有更多:

(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)))
=> {}

我在这里错过了什么?

【问题讨论】:

  • 不要使用宏扩展,而是尝试评估您关心的宏部分,方法是在您的 repl 中运行:(let [k 'foo] [k 'k `'~k])

标签: clojure macros quote


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    一些替代的和等效的写作方式

    `'~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 中引用语法的东西,在宏/宏扩展的上下文之外,是了解这些东西的最佳方式。

    附言另外,我会指出,我需要通过在未来的书籍版本中更好地解释来解决这种混淆;)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-05
      • 1970-01-01
      • 2018-11-06
      • 2010-11-25
      • 2013-11-09
      相关资源
      最近更新 更多