Well, lets start with a clear statement:
- Don’t be writing a macro if you don’t understand its value proposition.
Think of it like reflection in Java, its a tool of last resort, because of what you mentioned, macros don’t compose well with each other and with functions. And yea, you have to recompile code if you’ve changed the macro, a reloaded workflow would help in that regard.
Now, where it comes to the value proposition is in terms of syntax sugar. You need to think of order of evaluation.
Clojure uses an inside out, left to right, argument first, evaluation order.
(<function> arg1 arg2 arg3 ...)
When the above form is evaluated,
arg1 is evaluated first, then
arg3, etc. Afterwards, the values returned by the evaluation of each arg is passed to the
function for evaluation. This should be intuitive to everyone.
This in turn is the basic flow of Clojure, and most modern languages.
Now how do you control that flow? How can you modify this order of evaluation?
Other languages say you can’t. They give you premade alternative order controls, like
Lets look at
(if condition then else)
This is not the nornal evaluation order/flow we saw before.
Condition is evaluated first, and only one of
else will be evaluated, and we’re done. If it was evaluated normally it would evaluate
condition followed by
then followed by
else and finally evaluate
if giving it the results of
Now, you need some primitives to use in the beginning to start altering the flow, and in Clojure they’re given to you as
special forms. But this is nornally where other languages stop. In Clojure, you can make new evaluation order and flow altering macros.
This means you can build
You might think you’re not getting a lot of mileage out of macros, but that’s because clojure.core already wrote most of them for you
In my opinion, customizing the order of evaluation, or the flow of evaluation, is one of the best value proposition of macros.
Other notable uses:
- Rearranging code, such as ->, ->>, doto, etc.
- Adding dynamic state, such as with-open, time, with-in-str, etc.
- Wrapping things for you, such as delay, future, lazy-cat, etc.
- Building DSLs, such as ns
- And adding or removing code, such as comment, doc, assert, defn-, etc.