Esta pregunta está inspirada en una respuesta informativa de @tarsius. Creo que muchos tienen grandes experiencias para compartir otros. Espero que esta pregunta se convierta en una de las Grandes preguntas subjetivas y ayude a los programadores de Emacs.
Use macros para la sintaxis , pero nunca para la semántica.
Está bien usar una macro para un poco de azúcar sintáctica, pero es una mala idea poner la lógica en un cuerpo de macro independientemente de si está en la macro en sí o en la expansión. Si siente la necesidad de hacer eso, escriba una función y una "macro compañera" para el azúcar sintáctico. Simplemente hablando, si tienes un letmacro en tu macro estás buscando problemas.
¿Por qué?
Las macros tienen muchos inconvenientes:
Se expanden en el momento de la compilación y, por lo tanto, deben escribirse cuidadosamente para mantener la compatibilidad binaria en varias versiones. Por ejemplo, eliminar una función o variable utilizada en la expansión de una macro romperá el código de bytes de todos los paquetes dependientes que estaban usando esta macro. En otras palabras, si cambia la implementación de una macro de una manera incompatible, todos los paquetes dependientes deben volver a compilarse. Y la compatibilidad binaria no siempre es obvia ...
Debe tener cuidado de mantener un orden de evaluación intuitivo . Es muy fácil escribir una macro que evalúe un formulario con demasiada frecuencia, o en el momento equivocado, o en el lugar equivocado, etc.
Debe tener en cuenta la semántica de enlace : las macros heredan su contexto de enlace del sitio de expansión , lo que significa que no sabe a priori qué variables léxicas existen y si el enlace léxico está disponible. Una macro debe funcionar con ambas semánticas vinculantes.
En relación con esto, debe tener cuidado al escribir macros que crean cierres . Recuerde, obtiene los enlaces desde el sitio de expansión, no desde la definición léxica, así que esté atento a lo que cierra y cómo crea el cierre (recuerde, no sabe si el enlace léxico está activo en el sitio de expansión y si incluso tienes cierres disponibles).
Las notas sobre la unión léxica frente a la expansión macro son particularmente interesantes; gracias lunaryorn. (Nunca he permitido léxica vinculante para cualquier código que he escrito, así que no es un conflicto que alguna vez había pensado.)
PHILS
6
Desde mi experiencia leyendo, depurando y modificando mi código Elisp y el de otros, sugiero evitar las macros tanto como sea posible.
Lo obvio acerca de las macros es que no evalúan sus argumentos. Lo que significa que si tiene una expresión compleja envuelta en una macro, no tiene idea de si se evaluará o no, y en qué contexto, a menos que busque la definición de macro. Esto podría no ser un gran problema para usted, ya que conoce la definición de su propia macro (actualmente, probablemente no unos años más tarde).
Esta desventaja no se aplica a las funciones: todos los argumentos se evalúan antes de la llamada a la función. Lo que significa que puede desarrollar la complejidad de evaluación en una situación de depuración desconocida. Incluso puede omitir directamente las definiciones de las funciones desconocidas para usted, siempre que devuelvan datos directos y asuma que no tocan el estado global.
Para reformularlo de otra manera, las macros se convierten en parte del lenguaje. Si está leyendo un código con macros desconocidas, casi está leyendo un código en un idioma que no conoce.
Excepciones a las reservas anteriores:
Las macros estándar como push, pop, dolisthacen realmente mucho para usted, y se sabe que otros programadores. Básicamente son parte de la especificación del lenguaje. Pero es prácticamente imposible estandarizar su propia macro. No solo es difícil encontrar algo simple y útil como los ejemplos anteriores, sino que es aún más difícil convencer a cada programador para que lo aprenda.
Además, no me importan las macros como save-selected-window: almacenan y restauran el estado y evalúan sus argumentos de la misma manera progn. Bastante sencillo, en realidad simplifica el código.
Otro ejemplo de macros OK puede ser cosas como defhydrao use-package. Estas macros básicamente vienen con su propio mini idioma. Y todavía tratan datos simples o actúan como progn. Además, generalmente están en el nivel superior, por lo que no hay variables en la pila de llamadas de las que dependan los argumentos macro, lo que lo hace un poco más simple.
Un ejemplo de una mala macro, en mi opinión, es magit-section-actiony magit-section-caseen Magit. El primero ya fue eliminado, pero el segundo aún permanece.
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:
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.
"Azúcar sintáctico" no es realmente un término en informática. Es algo usado por los ingenieros para significar varias cosas. Entonces no tengo una respuesta definitiva. Ya que estamos hablando de semántica, entonces, la regla para interpretar la sintaxis de la forma: (x y)es más o menos "evaluar y, luego llamar a x con el resultado de la evaluación previa". Cuando escribe (defun x y), y no se evalúa ni tampoco x. El hecho de que pueda escribir un código con efecto equivalente utilizando una semántica diferente no niega que este código tenga semántica propia.
wvxvw
1
Re 'su segundo comentario: ¿Puede referirme a la definición de macro que está usando que dice lo que dice? Les acabo de dar un ejemplo donde se introduce una nueva regla semántica por medio de una macro. Al menos en el sentido preciso de CS. No creo que discutir sobre definiciones sea productivo, y también creo que las definiciones utilizadas en CS son la mejor opción para esta discusión.
wvxvw
1
Bueno ... resulta que tienes tu propia idea de lo que significa la palabra "semántica", lo cual es un poco irónico, si lo piensas :) Pero realmente, estas cosas tienen definiciones muy precisas, simplemente, bueno ... ignorar esos. El libro que vinculé en la respuesta es un libro de texto estándar sobre semántica como se enseña en el curso correspondiente de CS. Si acaba de leer el artículo correspondiente de Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29 , verá de qué se trata. El ejemplo es la regla para interpretar la (defun x y)aplicación de función vs.
wvxvw
Oh, bueno, no creo que esta sea mi forma de tener una discusión. Incluso fue un error intentar comenzar uno; Eliminé mis comentarios porque no tiene sentido todo esto. Siento haberte hecho perder el tiempo.
Desde mi experiencia leyendo, depurando y modificando mi código Elisp y el de otros, sugiero evitar las macros tanto como sea posible.
Lo obvio acerca de las macros es que no evalúan sus argumentos. Lo que significa que si tiene una expresión compleja envuelta en una macro, no tiene idea de si se evaluará o no, y en qué contexto, a menos que busque la definición de macro. Esto podría no ser un gran problema para usted, ya que conoce la definición de su propia macro (actualmente, probablemente no unos años más tarde).
Esta desventaja no se aplica a las funciones: todos los argumentos se evalúan antes de la llamada a la función. Lo que significa que puede desarrollar la complejidad de evaluación en una situación de depuración desconocida. Incluso puede omitir directamente las definiciones de las funciones desconocidas para usted, siempre que devuelvan datos directos y asuma que no tocan el estado global.
Para reformularlo de otra manera, las macros se convierten en parte del lenguaje. Si está leyendo un código con macros desconocidas, casi está leyendo un código en un idioma que no conoce.
Excepciones a las reservas anteriores:
Las macros estándar como
push
,pop
,dolist
hacen realmente mucho para usted, y se sabe que otros programadores. Básicamente son parte de la especificación del lenguaje. Pero es prácticamente imposible estandarizar su propia macro. No solo es difícil encontrar algo simple y útil como los ejemplos anteriores, sino que es aún más difícil convencer a cada programador para que lo aprenda.Además, no me importan las macros como
save-selected-window
: almacenan y restauran el estado y evalúan sus argumentos de la misma maneraprogn
. Bastante sencillo, en realidad simplifica el código.Otro ejemplo de macros OK puede ser cosas como
defhydra
ouse-package
. Estas macros básicamente vienen con su propio mini idioma. Y todavía tratan datos simples o actúan comoprogn
. Además, generalmente están en el nivel superior, por lo que no hay variables en la pila de llamadas de las que dependan los argumentos macro, lo que lo hace un poco más simple.Un ejemplo de una mala macro, en mi opinión, es
magit-section-action
ymagit-section-case
en Magit. El primero ya fue eliminado, pero el segundo aún permanece.fuente
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 otraswith-
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 expandecl-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 elcontext
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:
con este lenguaje podemos construir "programas" como
(1+0)+1
o0
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):
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:
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:
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)
interpretaf
como una función, mientrasa
que no es necesariamente una función.fuente
(x y)
es más o menos "evaluar y, luego llamar a x con el resultado de la evaluación previa". Cuando escribe(defun x y)
, y no se evalúa ni tampoco x. El hecho de que pueda escribir un código con efecto equivalente utilizando una semántica diferente no niega que este código tenga semántica propia.(defun x y)
aplicación de función vs.