syntaxclojureconventionsclojure-core

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


Preamble

I was looking throught the source code in clojure.core for no particular reason.

I started reading defmacro ns, here is the abridged source:

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

Looking Closer

And then trying to read it I saw some strange macro patterns, in particular we can look at:

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

The clojure.core version

Here is a standalone working extraction from the macro:

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

When I ran this could I couldn't help but wonder why there is a double list at the point `((.resetMeta.

My version

I found that by just removing the unquote-splicing (~@) the double list was unnecessary. Here is a working standalone example:

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

My Question

Thus, why does clojure.core choose this seemingly extraneous way of doing things?

My Own Thoughts

Is this an artifact of convention? Are there other similar instances where this is used in more complex ways?


Solution

  • ~ always emits a form; ~@ can potentially emit nothing at all. Thus sometimes one uses ~@ to splice in a single expression conditionally:

    ;; 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]))
    

    That's what's going on here: the (.resetMeta …) call is emitted within the do form that ns expands to only if name-metadata is truthy (non-false, non-nil).

    In this instance, it doesn't really matter – one could use ~, drop the extra brackets and accept that the macroexpansion of an ns form with no name metadata would have an extra nil in the do form. For the sake of a prettier expansion, though, it makes sense to use ~@ and only emit a form to handle name metadata when it is actually useful.