Clojure.core来源:为什么〜@(unquote-splicing operator)里面有引用的双列表,而不是〜(unquote operator)

时间:2018-05-01 14:57:00

标签: syntax clojure core convention

序言

我正在查看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)))))

更贴近

然后尝试阅读它我看到了一些奇怪的宏模式,特别是我们可以看一下:

~@(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))

当我跑步时,我可以忍不住想知道为什么在`((.resetMeta点有一个双重列表。

我的版本

我发现通过删除unquote-splicing(~@),双重列表是不必要的。这是一个独立的工作示例:

(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选择这种看似无关的做事方式?

我自己的想法

这是常规的工件吗? 是否有其他类似的情况以更复杂的方式使用?

1 个答案:

答案 0 :(得分:4)

~总是会发出一张表格; ~@可能根本不会发出任何内容。因此,有时使用~@有条件地拼接单个表达式:

;; 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形式内发出的ns只有name-metadata扩展到真实的(非false,非 - nil)。

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