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 arg2
, 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 if
, case
, for
, while
, ||
, &&
, etc.
Lets look at if
.
(if condition then else)
This is not the nornal evaluation order/flow we saw before. Condition
is evaluated first, and only one of then
or 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 condition
, then
and else
.
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 cond
, case
, when
, if-let
, condp
, if-not
, for
, while
, doseq
, dotimes
, etc.
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.