My favorite example for this is the lame idiom you see in Java code:
if (log.isDebugEnabled()) {
log.debug("expensive" + debug + message);
}
This is "better" than just log.debug(...) because with the latter, your expensive log message argument needs to be evaluated even if debug is disabled.
However, in a language w/ macros, you just say:
(debug (str "expensive" debug message))
and these considerations are already taken care of for you:
The point was that "debug" and "message" parts in the example were arbitrarily complex expressions which were computationally expensive to evaluate. Your wrapping of log debug would still require evaluating them to get the message string unconditionally, even when debug logging is not enabled.
Of course, with support for first-class functions, you could do something like:
func debug(produceMessage ()->string) {
if log.isDebugEnabled() {
log.debug(produceMessage());
}
}
The downside there is you can still end up allocating a closure. Whereas an expanded macro shouldn't cost anything. (A sufficiently smart compiler might be able to optimize the closure allocation.)
A sufficiently modern language (like, saaaaay, D2) could also give you a type like "closure you don't intend to escape" (let's call this a "scoped closure", or if you will, "scope string delegate()"), and eschew allocation entirely without requiring optimization.
For completeness of the argument, this particular problem is solved in the Java world with string formats. With the slf4j interface, that would be:
log.debug("expensive %s %s", debug, message)
The message is not actually formatted into one string unless the DEBUG trace level is enabled. Of course, you are still passing the arguments around, but with object references that's a negligable difference.
I still appreciate the solid example of a problem macros are good at solving, though. Two ways around one problem.
It's not solved, because method arguments are evaluated eagerly. It means that message argument may not be an expensive expression, because it will be calculated independently whether debug is enabled or not. In case of macros arguments of this method could be evaluated lazily.
Calling log.debug() with and argument of `false` is a no-op? That sounds like someone bending the language to fit an idiom, because it doesn't sound like a sane API except that it enables this use case.
It is insane in an eager-evaluated calling context. You're not wrong. But in Lisp it's not insane at all to let a macro consume a parameter and yield a no-op. Think of how this plays with a JIT and having code that can dynamically switch between dev/QA/production behavior and performance profiles, just for one example.
That does something similar, sure, but what it in fact is is a macro. As snikeris was saying, you couldn't write anything quite like that #define in a macroless language like Java.
However, in a language w/ macros, you just say:
and these considerations are already taken care of for you:https://github.com/clojure/tools.logging/blob/master/src/mai...