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 with-
macros.
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 cl-lib
package, 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).
Illustration
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 (1+0)+1
or 0
or ((0+0))
etc.
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.