Seré directo: no entiendo qué significa "usar para sintaxis, no para semántica". Es por eso que parte de esta respuesta tratará con la respuesta de lunaryon , y la otra parte será mi intento de responder la pregunta original.
Cuando la palabra "semántica" utilizada en la programación, se refiere a la forma en que el autor del idioma eligió interpretar su idioma. Esto generalmente se reduce a un conjunto de fórmulas que transforman las reglas gramaticales del lenguaje en otras reglas con las que se supone que el lector está familiarizado. Por ejemplo, Plotkin en su libro Structural Approach to Operational Semantics usa un lenguaje de derivación lógica para explicar a qué se evalúa la sintaxis abstracta (qué significa ejecutar un programa). En otras palabras, si la sintaxis es lo que constituye el lenguaje de programación en algún nivel, entonces tiene que tener alguna semántica, de lo contrario es, bueno ... no un lenguaje de programación.
Pero, olvidemos por el momento las definiciones formales, después de todo, es importante entender la intención. Entonces, parece que lunaryon alentaría a sus lectores a usar macros cuando no se agregue un nuevo significado al programa, sino solo una especie de abreviatura. Para mí, esto suena extraño y en marcado contraste con cómo se usan realmente las macros. A continuación hay ejemplos que claramente crean nuevos significados:
defun y amigos, que son una parte esencial del lenguaje, no pueden describirse en los mismos términos que describiría las llamadas a funciones.
setflas reglas que describen cómo funcionan las funciones son inadecuadas para describir los efectos de una expresión como (setf (aref x y) z).
with-output-to-stringTambién cambia la semántica del código dentro de la macro. Del mismo modo, with-current-buffery un montón de otras with-macros.
dotimes y similares no se pueden describir en términos de funciones.
ignore-errors cambia la semántica del código que envuelve.
Hay un montón de macros definidas en cl-libpackage, eieiopackage y algunas otras bibliotecas casi ubicuas utilizadas en Emacs Lisp que, aunque pueden parecer formas de función, tienen diferentes interpretaciones.
Entonces, en todo caso, las macros son la herramienta para introducir nuevas semánticas en un lenguaje. No puedo pensar en una forma alternativa de hacerlo (al menos no en Emacs Lisp).
Cuando no usaría macros:
Cuando no introducen ninguna construcción nueva, con nueva semántica (es decir, la función haría el trabajo igual de bien).
Cuando esto es solo una especie de conveniencia (es decir, crear una macro nombrada mvbque se expande cl-multiple-value-bindpara hacerla más corta).
Cuando espero que la macro oculte una gran cantidad de código de manejo de errores (como se ha señalado, las macros son difíciles de depurar).
Cuando preferiría las macros a las funciones:
Cuando los conceptos específicos del dominio están oscurecidos por las llamadas a funciones. Por ejemplo, si uno necesita pasar el mismo contextargumento a un grupo de funciones que deben llamarse sucesivamente, preferiría envolverlas en una macro, donde el contextargumento está oculto.
Cuando el código genérico en forma de función es demasiado detallado (las construcciones de iteración desnuda en Emacs Lisp son demasiado detalladas para mi gusto y exponen innecesariamente al programador a los detalles de implementación de bajo nivel).
Ilustración
A continuación se muestra la versión diluida destinada a dar una idea de lo que se entiende por semántica en informática.
Supongamos que tiene un lenguaje de programación extremadamente simple con solo estas reglas de sintaxis:
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
con este lenguaje podemos construir "programas" como (1+0)+1o 0o ((0+0))etc.
Pero mientras no hayamos proporcionado reglas semánticas, estos "programas" no tienen sentido.
Supongamos que ahora equipamos nuestro lenguaje con las reglas (semánticas):
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
Ahora podemos calcular usando este lenguaje. Es decir, podemos tomar la representación sintáctica, entenderla de manera abstracta (en otras palabras, convertirla en sintaxis abstracta), luego usar reglas semánticas para manipular esta representación.
Cuando hablamos de la familia de lenguajes Lisp, las reglas semánticas básicas de evaluación de funciones son aproximadamente las del cálculo lambda:
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
Los mecanismos de cotización y macroexpansión también se conocen como herramientas de metaprogramación, es decir, permiten hablar sobre el programa, en lugar de solo programar. Logran esto creando nuevas semánticas utilizando la "meta capa". El ejemplo de una macro tan simple es:
(a . b)
-------
(cons a b)
Esto no es realmente una macro en Emacs Lisp, pero podría haber sido. Elegí solo por simplicidad. Tenga en cuenta que ninguna de las reglas semánticas definidas anteriormente se aplicaría a este caso ya que el candidato más cercano (f x)interpreta fcomo una función, mientras aque no es necesariamente una función.