Источник Clojure.core: Почему ~ @ (оператор unquote-splicing) с двойным списком в кавычках вместо ~ (оператор unquote) - PullRequest
0 голосов
/ 01 мая 2018

Преамбула

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

Мои мысли

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

1 Ответ

0 голосов
/ 01 мая 2018

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

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

...