Vraag Clojure defmacro verliest metadata


Ik probeer een kleine Clojure-macro te maken die defs een string met een type hint:

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

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

Toen ik macroexpand het is de typehint verloren gegaan:

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

Laat staan ​​de wijsheid van het type dat dit aangeeft.

Waarom verliest de macro de metadata? Hoe schrijf ik deze macro, of elke andere metadata?


21
2017-10-13 12:53


oorsprong


antwoorden:


^ is een lezermacro. defmacro krijgt het nooit te zien. De hint wordt op de lijst geplaatst (unquote name). Vergelijk bijvoorbeeld (meta ^String 'x) met (meta ' ^String x) om het effect te zien.

U moet de hint op het symbool zetten.

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

En het gebruik:

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}

32
2017-10-13 13:06



Metagegevens worden niet in een macro-uiting weergegeven, omdat het 'onzichtbaar' moet zijn.

Als de macro correct is (wat niet het geval is), zou je moeten kunnen callen (meta # 'db-host-optie) om de metadata op de var te inspecteren.

Merk op dat (def sym ...) metadata invoegt op de var die het van het symbool ontvangt. Maar ^ Tag ~ -naam stelt de metadata in op ~ naam (niet-citaatnaam), niet op het doorgegeven symbool gebonden aan naam. Het kan niets anders doen, omdat ^ Tag ... de verwerking wordt gedaan door de lezer, die al klaar is zodra de macro-uitbreiding start.

Je wilt zoiets

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

5
2017-10-13 13:09