Источник Clojure.core: почему ~@ (оператор сращивания без кавычек) с заключенным в кавычки двойным списком внутри, а не ~ (оператор без кавычек)

Преамбула

Я просматривал исходный код в 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 есть двойной список.

Моя версия

Я обнаружил, что просто удалив сращивание без кавычек (~@), двойной список стал ненужным. Вот работающий автономный пример:

(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 выбирает этот, казалось бы, посторонний способ ведения дел?

Мои собственные мысли

Это артефакт условности? Есть ли другие подобные случаи, когда это используется более сложными способами?


person Jessie Ross    schedule 01.05.2018    source источник
comment
Вы можете задать этот вопрос в списке рассылки Clojure: groups.google.com/forum /#!форум/clojure   -  person Alan Thompson    schedule 01.05.2018


Ответы (1)


~ всегда выдает форму; ~@ потенциально может вообще ничего не излучать. Таким образом, иногда используется ~@ для условного соединения в одном выражении:

;; 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. Однако ради более красивого расширения имеет смысл использовать ~@ и генерировать форму для обработки метаданных имени только тогда, когда это действительно полезно.

person Michał Marczyk    schedule 01.05.2018