我正在查看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选择这种看似无关的做事方式?
这是常规的工件吗? 是否有其他类似的情况以更复杂的方式使用?
答案 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
。但是,为了进行更漂亮的扩展,使用~@
并且仅在实际有用时发出一个表单来处理名称元数据是有意义的。