Clojure defmacro теряет метаданные

#macros #clojure #type-hinting

#макросы #clojure #подсказка типа

Вопрос:

Я пытаюсь создать небольшой макрос Clojure, который def содержит строку с подсказкой типа:

 (defmacro def-string [name value]
  `(def ^String ~name ~value))

(def-string db-host-option "db-host")
  

Когда я macroexpand это делаю, подсказка типа теряется:

 (macroexpand '(def-string db-host-option "db-host"))
;=> (def db-host-option "db-host")
  

Не обращайте внимания на мудрость типа, намекающего на это.

Почему макрос теряет метаданные? Как мне написать этот макрос или любой, который включает метаданные?

Ответ №1:

^ это макрос для чтения. defmacro никогда не видит этого. Подсказка помещается в список (unquote name) . Сравните, например (meta ^String 'x) , с (meta ' ^String x) , чтобы увидеть эффект.

Вам нужно поместить подсказку на символ.

 (defmacro def-string
  [name value]
  `(def ~(vary-meta name assoc :tag `String) ~value))
  

И использование:

 user=> (def-string foo "bar")
#'user/foo
user=> (meta #'foo)
{:ns #<Namespace user>, :name foo, :file "NO_SOURCE_PATH", :line 5, :tag java.lang.String}
  

Комментарии:

1. Ах! Конечно, макросы считывателя оцениваются перед defmacros. Спасибо.

Ответ №2:

Метаданные не отображаются в макрорасширении, поскольку они должны быть «невидимыми».

Если макрос правильный (а это не так), вы должны иметь возможность вызвать (meta #’db-host-option) для проверки метаданных в переменной.

Обратите внимание, что (def sym …) вставляет метаданные в переменную, которую он получает от символа. Но ^Tag ~name устанавливает метаданные для ~name (имя без кавычек), а не для переданного символа, связанного с name . Он больше ничего не может сделать, поскольку обработка ^Tag … выполняется считывателем, который уже завершен после запуска расширения макроса.

Вы хотите что-то вроде

 (defmacro def-string [name value]
  `(def ~(with-meta name {:tag String}) ~value))


user> (def-string bar 1)
#'user/bar
user> (meta #'bar)
{:ns #<Namespace user>, :name bar, :file "NO_SOURCE_FILE", :line 1, :tag java.lang.String}