You should follow the so-called CALL-WITH style when it
applies. This style is explained at length in
http://random-state.net/log/3390120648.html. The general
principle is that the macro is strictly limited to
processing the syntax, and as much of the semantics as
possible is kept in normal functions. Therefore, a macro
WITH-FOO is often limited to generating a call to an
auxiliary function CALL-WITH-FOO with arguments deduced
from the macro arguments. Macro &body arguments are
typically wrapped into a lambda expression of which they
become the body, which is passed as one of the arguments of
the auxiliary function.
One of the problems here is that the function does get an anonymous function passed. That means the function can provide dynamic scope via constructs like CATCH, LET with dynamic bindings, UNWIND-PROTECT, and so on. But it can't rewrite any code in the passed function.
> A good Lisp programmer would never write such inefficient code. Why create a local function here when you can easily write a macro that expands into a loop with expr as its body?
The nice thing about the call-with style is that it makes it easier to redefine your macro's functionality without recompiling all the call sites.
From Google's Common Lisp style guide ( https://google.github.io/styleguide/lispguide.xml#Macros ):