Clojure defmacro теряет метаданные
Я пытаюсь создать небольшой макрос Clojure, который def
a String с подсказкой типа:
(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}
Ответ 2
Метаданные не отображаются в макроэкспонете, поскольку он должен быть "невидимым".
Если макрос верен (это не так), вы должны иметь возможность вызвать (meta # 'db-host-option), чтобы проверить метаданные на var.
Обратите внимание, что (def sym...) вставляет метаданные в var, которые он получает от символа. Но ^ Tag ~ name устанавливает метаданные в ~ имя (имя unquote), а не на переданный символ, привязанный к имени. Он ничего не может сделать с тех пор, как ^ Тег... обработка выполняется читателем, который уже завершен после начала макрорасширения.
Вы хотите что-то вроде
(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}