I am trying to create a little Clojure macro that def
s a String with a type hint:
(defmacro def-string [name value]
`(def ^String ~name ~value))
(def-string db-host-option "db-host")
When I macroexpand
it, the type hint is lost:
(macroexpand '(def-string db-host-option "db-host"))
;=> (def db-host-option "db-host")
Never mind the wisdom of type hinting this.
Why is the macro losing the metadata? How do I write this macro, or any that includes metadata?
^
is a reader macro. defmacro
never gets to see it. The hint is put on the list (unquote name)
. Compare for example (meta ^String 'x)
with (meta ' ^String x)
to see the effect.
You need to put the hint on the symbol.
(defmacro def-string
[name value]
`(def ~(vary-meta name assoc :tag `String) ~value))
And the usage:
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}
Metadata doesn't show up in a macroexpand since it's supposed to be "invisible".
If the macro is correct (which it isn't) you should be able to call (meta #'db-host-option) to inspect the meta data on the var.
Note that (def sym ...) inserts metadata on the var that it receives from the symbol. But ^Tag ~name sets the meta data on ~name (unquote name), not on the passed in symbol bound to name. It can't do anything else since ^Tag ... processing is done by the reader, which is already finished once macro expansion starts.
You want something like
(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}