I'll be blunt: I don't understand what "use for syntax, not for semantics" means. This is why part of this answer will deal with lunaryon's answer, and the other part will be my attempt at answering the original question.
When the word "semantics" used in programming, it refers to the way the author of the language chose to interpret their language. This typically boils down to a set of formulas which transform grammar rules of the language into some other rules the reader is assumed to be familiar with. For example, Plotkin in his book Structural Approach to Operational Semantics uses logical derivation language to explain what abstract syntax evaluates to (what does it mean to run a program). In other words, if syntax is what constitutes the programming language on some level, then it has to have some semantics, otherwise it's, well... not a programming language.
But, let's for the moment forget the formal definitions, after all, it's important to understand the intention. So, it seems like lunaryon would encourage his readers to use macros when no new meaning is to be added to the program, but just some sort of abbreviation. To me this sounds strange and in stark contrast to how macros are actually used. Below are examples which clearly create new meanings:
defun and friends, which are an essential part of the language cannot be described in the same terms you would describe function calls.
setf the rules describing how functions work are inadequate to describe the effects of an expression like
(setf (aref x y) z).
with-output-to-string also changes the semantics of the code inside the macro. Similarly,
with-current-buffer and a bunch of other
dotimes and similar cannot be described in terms of functions.
ignore-errors changes the semantics of the code it wraps.
There's a whole bunch of macros defined in
eieio package and some other almost ubiquitous libraries used in Emacs Lisp which, while may look like function forms have different interpretations.
So, if anything, macros are the tool to introduce new semantics into a language. I cannot think of an alternative way of doing that (at least not in Emacs Lisp).
When I wouldn't use macros:
When they don't introduce any new constructs, with new semantics (i.e. the function would do the job just as good).
When this is just a sort of convenience (i.e. creating a macro named
mvb which expands into
cl-multiple-value-bind just to make it shorter).
When I expect a lot of error handling code to be hidden by the macro (as has been noted, macros are difficult to debug).
When I would strongly prefer macros over functions:
When domain-specific concepts are obscured by function calls. For example, if one needs to pass the same
context argument to a bunch of functions that need to be called in succession, I would prefer to wrap them in a macro, where the
context argument is hidden.
When generic code in function form is overly verbose (bare iteration constructs in Emacs Lisp are too verbose to my taste and needlessly expose the programmer to the low-level implementation details).
Below is the watered down version intended to give one an intuition as to what is meant by semantics in computer science.
Suppose you have an extremely simple programming language with just these syntax rules:
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
with this language we can construct "programs" like
But as long as we didn't provide semantic rules, these "programs" are meaningless.
Suppose we now equip our language with the (semantic) rules:
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
Now we can actually compute using this language. That is, we can take the syntactical representation, understand it abstractly (in other words, convert it into abstract syntax), then use semantic rules to manipulate this representation.
When we talk about Lisp family of languages, the basic semantic rules of function evaluation are roughly those of lambda-calculus:
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
Quoting and macro-expansion mechanisms are also known as meta-programming tools, i.e. they allow one to talk about the program, instead of just programming. They achieve this by creating new semantics using the "meta layer". The example of such a simple macro is:
(a . b)
(cons a b)
This is not actually a macro in Emacs Lisp, but it could have been. I chose only for simplicity sake. Notice that none of the semantic rules defined above would apply to this case since the closest candidate
(f x) interprets
f as a function, while
a isn't necessarily a function.