Suppose that we want to write a macro whose behavior depends on some kind of configuration. Specifically, in my case the configuration is just a boolean value indicating some optimization in the output code. Even more specifically, the transformation looks like this (denoting macroexpansion by ==>
):
(transform ...) ==> (transform'-opt ...) ==> (transform' ...) ==> ...
when the optimization takes place, otherwise transform'-opt
is omitted and transform'
is used in its place.
Using a parameter for the configuration would result in too much repetition, so it would be preferable to configure the macro in a local/with-...
style (and possibly in a global style too). By this I mean that, using something like (with-opt value expr*)
, during the macroexpansion of each expr
value
is used as the configuration of transform
unless this is changed by another with-opt
. The solution I have chosen so far is to use a dynamic var and binding
in the following way:
(require '[clojure.tools.analyzer.jvm :refer [macroexpand-all]])
(def ^:dynamic *opt* true)
(defmacro with-opt [value & body]
`(do ~@(binding [*opt* value] (mapv macroexpand-all body))))
(defmacro transform [...]
`(~(if *opt* `transform'-opt `transform') ...))
However, the use of dynamic vars and macroexpand-all
at macroexpansion looks somewhat unconventional to me. Other (also unconventional) options I have considered include using a regular var and with-redefs
, an atom, a volatile and also hiding the configuration inside closures like this:
(let [opt ...]
(defmacro with-opt ...)
(defmacro transform ...))
[Perhaps an imaginary generic way to avoid mutation and manual macroexpansion in such cases could be to introduce another implicit &config
parameter in macros and a new special form (with-macro-configs [(macro config)*] expr*)
that would specify the value used for this parameter for various macros.]
Is there any common practice for such a situation? What would your approach be and why? Thanks in advance.