Clojure.core 资料来源:为什么 ~@(反引号拼接运算符)在内部使用带引号的双列表,而不是 ~(反引号运算符)

Clojure.core source: Why ~@ (unquote-splicing operator) with a quoted double list inside, instead of ~ (unquote operator)

前言

我一直在查看 clojure.core 中的源代码,没有特别的原因。

我开始阅读 defmacro ns,这里是删节的来源:

(defmacro ns
  "...docstring..."
  {:arglists '([name docstring? attr-map? references*])
   :added "1.0"}
  [name & references]
  (let [... 
        ; Argument processing here.
        name-metadata (meta name)]
    `(do
       (clojure.core/in-ns '~name)
       ~@(when name-metadata
           `((.resetMeta (clojure.lang.Namespace/find '~name) ~name-metadata)))
       (with-loading-context
        ~@(when gen-class-call (list gen-class-call))
        ~@(when (and (not= name 'clojure.core) (not-any? #(= :refer-clojure (first %)) references))
            `((clojure.core/refer '~'clojure.core)))
        ~@(map process-reference references))
        (if (.equals '~name 'clojure.core) 
          nil
          (do (dosync (commute @#'*loaded-libs* conj '~name)) nil)))))

仔细观察

然后尝试阅读它,我看到了一些 st运行ge 宏模式,特别是我们可以看看:

~@(when name-metadata
        `((.resetMeta (clojure.lang.Namespace/find '~name) ~name-metadata)))

clojure.core版本

这是从宏中提取的独立工作提取物:

(let [name-metadata 'name-metadata 
      name 'name]
  `(do
     ~@(when name-metadata
         `((.resetMeta (clojure.lang.Namespace/find '~name) ~name-metadata)))))

=> (do (.resetMeta (clojure.lang.Namespace/find (quote name)) name-metadata))

当我运行 this could我忍不住想知道为什么在`((.resetMeta点有一个双列表。

我的版本

我发现只要删除反引号拼接 (~@) 就不需要双列表了。这是一个独立的工作示例:

(let [name-metadata 'name-metadata 
      name 'name]
  `(do
     ~(when name-metadata
         `(.resetMeta (clojure.lang.Namespace/find '~name) ~name-metadata))))

=> (do (.resetMeta (clojure.lang.Namespace/find (quote name)) name-metadata))

我的问题

那么,为什么clojure.core会选择这种看似极端运行的做事方式呢?

我自己的想法

这是约定俗成的产物吗? 是否还有其他以更复杂的方式使用它的类似实例?

~ 总是发出一个表格; ~@ 可能根本不发射任何东西。因此有时人们使用 ~@ 有条件地拼接单个表达式:

;; always yields the form (foo x)
;; (with x replaced with its macro-expansion-time value):
`(foo ~x)`

;; results in (foo) is x is nil, (foo x) otherwise:
`(foo ~@(if x [x]))

这就是这里发生的事情:(.resetMeta …) 调用在 do 形式内发出,只有当 name-metadata 为真(非 false, 非nil).

在这种情况下,这并不重要——可以使用 ~,去掉额外的括号并接受没有名称元数据的 ns 形式的宏扩展会有一个额外的nildo 表格中。但是,为了更漂亮的扩展,使用 ~@ 并且仅在实际有用时发出一个表单来处理名称元数据是有意义的。