#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}