beginners 2025-11-13

I'm having difficulty understanding the popularity of Macro's in user-land code. The common advice seems to be that "it's best not to use Macro's when a function will do the job". Besides deferred evaluation and maybe reducing syntax (which is just prettifying library api imho), are there any other good reasons to use Macro's, or why they are so popular ?

i think you've covered it

1

what are you struggling with?

my main pain point is testing - if I have code like this:

(some-macro ..
   (my-code ..))
then I have to wrap that part in another function, so that I can with-redefs that block, since I can't with-redefs the (`some-macro ..` bit on it's own. The reason I asked the question is that, maybe there is something awesome that I'm missing out on, or not understanding the use-case for Macro's yet - and wanted to know why some code in the wild uses Macro's in places where functions can do the job - and my guess is it's because it's to make syntax lighter 🤷 but I'm not sure.

• Making syntax "lighter" (or more generally, what might be called 'rewrite macros') can also make code more readable/understandable at-a-glance, and IMO, that matters. I'd probably point at the threading macros for a good example of this. This isn't an additional reason to use macros, but an additional "reason for the reason", as it were. • There are also (in some situations) performance gains that can be had - an example of this would be trace logging that gets compiled away based on a log level, or pre-computing values that might not be known at authoring time, but can be known at compile time (maybe they're in the execution context, or maybe they're extensions of a pattern, e.g. a library use case). There are myriad talks, papers, etc... one that I sometimes point to for a couple of interesting use cases for macros is <https://www.youtube.com/watch?v=0JXhJyTo5V8>. These are in library settings, but I think more "focused" versions of some of these use cases could certainly arise in "application code". One of the reasons I think these uses are kind of cool is because they're mostly not 'rewrite macros'... significant analysis and design went into them, and IMO they're quite effective at "here's a little code that gives me a lot back" because of it.

the threading macro is an excellent example, thank you! 🙏 performance gains.. 🕵️ thank you very much for the additional info, I look to looking into it thanks3 🧑‍🎓

You can control evaluation. The code of your macro can decide which of its args to evaluate or not. This is simply not possible for functions: they receive all their arguments evaluated. Imagine writing cond based on nested ifs (if cond wouldn't exist, yet). By extension, the args to your macro don't have to be something that can be evaluated at all.

I am always suspicious of with redefs. Why do you need to redefine something? Smells like a design issue propagating up through this

➕ 5

I've never had the need to use with-redefsonce yet. I have also not created a Clojure macro in the last 8 years, I suppose. I consider macros as compile time templates to generate code. They have their uses, but normally most of the test effort in not in the macros themselves but in the code they wrap, IMHO.

A lot of Clojure itself is macros (defn, cond, doseq, for, and, etc.) and libraries like core.match and core.async rely on macros. If they are great for Clojure itself and libraries, why would "user-land code" be different? Why make a distinction? And as an example of something that can't be done with functions, I have a macro for debugging recursive functions, it's called with-indented-prints. If you wrap your defn with it, all prints in the function will have an indent that is as long as the current invocation's depth in the call stack, to visualize the calls better. You can't do that with functions.

1

Just an example. Imaging you want to have something like register-event and your desired syntax how to use it is (register-event [ev] (print ev)). Fair example, I think. But with functions it is impossible. You can try it with something like (defn register-event args body ...). You will see that you need a macro. If you really want this nice syntax you have no other choice. This is what I understand under "don't use macros unless you have to".

First rule of macro club... don't write your own macros (unless there is a very good reason and have a well thought out design) Writing macros for application code usually increases the complexity of the code base (especially if writing a lot of them). Every macro written adds to the learning curve and maintenance of a code base. So a macro should be worth all the effort. People will have to live with your macros for some time (or rewrite code when the macro isn't worth it any more) Using macros as part of a commonly used library can be more valuable than directly in application code. A well designed library mitigates some of the longer term costs of the macro(s) You should definitely use common macros, e.g. from the clojure.core library. These are well established macros that everyone knows (or are easy to lookup, with lots of examples).

Popularity rarely equates to value. I think people like taking about writing macros a lot more than the actual value they provide long term in the majority of ccases. It does take significant skill and experience to write really good macros for Clojure. It doesn't take much to write macros that are eventually regretted. There is at least one macro in clojure.core that the language designers regret adding, but its too late to change that now.

There is at least one macro in clojure.core that the language designers regret adding, but its too late to change that now.
A particular one? Which one? @jr0cket