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.
setf
las 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-string
También cambia la semántica del código dentro de la macro. Del mismo modo, with-current-buffer
y 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-lib
package, eieio
package 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 mvb
que se expande cl-multiple-value-bind
para 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 context
argumento a un grupo de funciones que deben llamarse sucesivamente, preferiría envolverlas en una macro, donde el context
argumento 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)+1
o 0
o ((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 f
como una función, mientras a
que no es necesariamente una función.