Hi! Yesterday on IRC some of us were struggling with the usage of dynamically rebound vars inside macros which led to interesting compile vs run time issues. I've got some questions for the experts at the very bottom of this post - but before I'd like to explai this issue for the less experienced macrologists amongst us =)
Let's start with the definition of assert in clojure.core: (defmacro assert "Evaluates expr and throws an exception if it does not evaluate to logical true." [x] (when *assert* `(when-not ~x (throw (new AssertionError (str "Assert failed: " (pr-str '~x))))))) This way setting *assert* to false makes (assert anything) expand to nil and thus gets rid of all assert statements. This might be useful to speed up stable but time-critical code in "production mode" without having to manually remove all assertments. Similar behaviour might be interesting for debugging output. So lets say we want to have a macro in-production-mode which used *assert* to "turn off" assert statements: (in-production-mode (assert nil)) => nil But how can we define this macro? My first idea would have been this: (defmacro in-production-mode [& body] `(binding [*assert* false] ~...@body)) But this does not work - the inner assert is still being expanded - thus giving an exception when used for the example above. This is due to the difference between compile time and run time in Lisps. That is, the inner assert is already being macroexpanded at compile time while the binding only happens at run time when it's too late: compile time: (in-production-mode (assert false)) => (binding [*assert* false] (assert nil)) => *assert* still true here (binding [*assert* false] (when-not nil (throw ...))) So we want to make the rebinding happen during compilation. How about that: (defmacro in-production-mode [& body] (binding [*assert* false] `(do ~...@body))) Doesn't work either - let's look at the steps again: compile time: (in-production-mode (assert false)) => *assert* false at this point (do (assert nil)) => *assert* true again - we are no longer in the scope of the call toin-production-mode (do (when-not nil (throw ...))) Another idea would be to alter the root binding of *assert* instead. I guess this solution would work for your own vars, but unfortunately it is not possible to do this with *assert*: (def *assert* false) *assert* => true I guess users are not allowed to alter root bindings for vars in clojure.core at all - but it would be better to get an exception instead of this behaviour - I might submit a ticket for this. So, finally, I have come up with this: (defmacro in-production-mode [& body] (binding [*assert* false] `(do ~@(clojure.walk/macroexpand-all body)))) But I feel bad about explicitly calling macroexpand - smells like directly calling eval to me. So my questions are: - Is there a better way to define in-production-mode? - How was *assert* meant to be used? - Is it okay to call macroexpand-all on arbitrary code? Or can anything bad happen if the author of that code is not aware of this fact? Cheers and thanks for your answers, Benjamin -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en