I can "generate" a def with a macro.
(defmacro my-def [my-name]
`(def ~my-name 42))
(my-def a)
a; => 42
If I try to do something similar with a list
(defmacro my-defs [my-names]
`(do
~@(for [name# my-names]
`(def ~name# 42))))
(my-defs (a b c))
(macroexpand '(my-defs (a b c))); => (do (def a 42) (def b 42) (def c 42))
It works as long as I use a literal list as input. But as soon as I want to pass in a var
(def my-list '(a b c))
(macroexpand '(my-defs my-list)); => Don't know how to create ISeq from: clojure.lang.Symbol
I struggle to access the value of my-names
. I can't use ~my-names
as it is already used in a unquote-splice (~@
) and would lead to an "Attempt[...] to call unbound fn".
What am I missing?
Do I need to use (var-get (resolve my-names))
?
Do macros in these cases need to "detect" if the passed argument is a literal value or a var and act accordingly in order to work for both?
Or is it idiomatic to use eval
to avoid this?
Addressing @Alan Thompson's question "[...] why [do] you want to do this?": I have a specification (a deeply nested map) of "resources" and it would be rather handy to have a macro generate defs (records) for these resources in order to use them down the line. So I guess no reason out of the ordinary "It would DRY up things". :) At this time I found a way by wrapping my-names
in an eval
. The question that remains is: Is this idiomatic, or is there a better way?
generally you can't employ macro to generate code based on runtime value, still your task doesn't require macro in clojure, since you can dynamically intern vars in namespaces:
(defn intern-vals [data]
(doseq [[var-name var-val] data]
(intern *ns* var-name var-val)))
user> (intern-vals {'some-val 10 'other-val 20})
;;=> nil
user> some-val
;;=> 10
user> other-val
;;=> 20
notice that this function interns values in the namespace it gets called from, thanks to *ns*
dynamic var:
user> (ns a2)
a2> (user/intern-vals {'some-val "asd" 'other-val "xxx"})
;;=> nil
a2> some-val
;;=> "asd"
a2> user/some-val
;;=> 10