Clojure defmacro теряет метаданные - PullRequest
22 голосов
/ 13 октября 2011

Я пытаюсь создать небольшой макрос 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")

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

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

Ответы [ 2 ]

33 голосов
/ 13 октября 2011

^ - это макрос для чтения.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}
5 голосов
/ 13 октября 2011

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

Если макрос верный (а это не так), вы должны быть в состоянии вызвать (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}
...