it-swarm-es.com

¿Por qué no se incluyen macros en la mayoría de los lenguajes de programación modernos?

Sé que se implementan de manera extremadamente insegura en C/C++. ¿No se pueden implementar de una manera más segura? ¿Las desventajas de las macros son realmente lo suficientemente malas como para superar la potencia masiva que proporcionan?

33
Casebash

Creo que la razón principal es que las macros son léxico. Esto tiene varias consecuencias:

  • El compilador no tiene forma de verificar que una macro está semánticamente cerrada, es decir, que representa una "unidad de significado" como lo hace una función. (Considere #define TWO 1+1: ¿A qué equivale TWO*TWO? 3.)

  • Las macros no se escriben como las funciones. El compilador no puede verificar que los parámetros y el tipo de retorno tengan sentido. Solo puede verificar la expresión expandida que usa la macro.

  • Si el código no se compila, el compilador no tiene forma de saber si el error está en la macro o en el lugar donde se usa la macro. El compilador informará el lugar equivocado la mitad del tiempo, o tiene que informar ambos, aunque uno de ellos probablemente esté bien. (Considere #define min(x,y) (((x)<(y))?(x):(y)): ¿Qué debe hacer el compilador si los tipos de x y y no coinciden o no implementan operator<?)

  • Las herramientas automatizadas no pueden trabajar con ellas de formas semánticamente útiles. En particular, no puede tener cosas como IntelliSense para macros que funcionan como funciones pero se expanden a una expresión. (De nuevo, el ejemplo min).

  • Los efectos secundarios de una macro no son tan explícitos como lo son con las funciones, causando una posible confusión para el programador. (Considere nuevamente el ejemplo min: en una llamada de función, sabe que la expresión para x se evalúa solo una vez, pero aquí no se puede saber sin mirar la macro).

Como dije, todas estas son consecuencias del hecho de que las macros son léxicas. Cuando intentas convertirlos en algo más apropiado, terminas con funciones y constantes.

58
Timwi

Pero sí, las macros pueden ser diseñadas e implementadas mejor que en C/C++.

El problema con las macros es que son efectivamente un mecanismo de extensión de sintaxis del lenguaje que reescribe su código en otra cosa.

  • En el caso de C/C++, no existe una comprobación de cordura fundamental. Si tienes cuidado, las cosas están bien. Si comete un error, o si usa en exceso las macros, puede meterse en grandes problemas.

    Agregue a esto que muchas cosas simples que puede hacer con macros (estilo C/C++) se pueden hacer de otras maneras en otros idiomas.

  • En otros idiomas, como varios dialectos LISP, las macros están mejor integradas con la sintaxis del lenguaje principal, pero aún puede tener problemas con las declaraciones en una macro "fuga". Esto se aborda mediante macros higiénicas .


Breve contexto histórico

Las macros (abreviatura de macroinstrucciones) aparecieron por primera vez en el contexto del lenguaje ensamblador. Según Wikipedia , las macros estaban disponibles en algunos ensambladores de IBM en la década de 1950.

El LISP original no tenía macros, pero se introdujeron por primera vez en MacLisp a mediados de la década de 1960: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user -definido-código-transformación-aparece . http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-LISP.pdf . Antes de eso, "fexprs" proporcionaba una funcionalidad tipo macro.

Las primeras versiones de C no tenían macros ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Estos se agregaron alrededor de 1972-73 a través de un preprocesador. Antes de eso, C solo admitía #include y #define.

El macroprocesador M4 se originó en alrededor de 1977.

Los lenguajes más recientes aparentemente implementan macros donde el modelo de operación es sintáctico en lugar de textual.

Entonces, cuando alguien habla sobre la primacía de una definición particular del término "macro", es importante tener en cuenta que el significado ha evolucionado con el tiempo.

13
Stephen C

Las macros pueden como señala Scott , le permiten ocultar la lógica. Por supuesto, también lo hacen funciones, clases, bibliotecas y muchos otros dispositivos comunes.

Pero un poderoso sistema macro puede ir más allá, permitiéndole diseñar y utilizar sintaxis y estructuras que normalmente no se encuentran en el lenguaje. De hecho, esta puede ser una herramienta maravillosa: lenguajes específicos de dominio, generadores de código y más, todo dentro de la comodidad de un entorno de un solo idioma ...

Sin embargo, puede ser abusado. Puede hacer que el código sea más difícil de leer, comprender y depurar, aumentar el tiempo necesario para que los nuevos programadores se familiaricen con una base de código y generar errores y demoras costosas.

Entonces, para los lenguajes destinados a simplificar la programación (como Java o Python), dicho sistema es un anatema.

12
Shog9

Las macros se pueden implementar de manera muy segura en algunas circunstancias: en LISP, por ejemplo, las macros son solo funciones que devuelven código transformado como una estructura de datos (expresión-s). Por supuesto, LISP se beneficia significativamente del hecho de que es homoiconic y del hecho de que "el código es información".

Un ejemplo de lo fácil que pueden ser las macros es este ejemplo de Clojure que especifica un valor predeterminado para usar en el caso de una excepción:

(defmacro on-error [default-value code]
  `(try ~code (catch Exception ~'e ~default-value)))

(on-error 0 (+ nil nil))               ;; would normally throw NullPointerException
=> 0                                   ;l; but we get the default value

Sin embargo, incluso en Lisps, el consejo general es "no use macros a menos que sea necesario".

Si no está utilizando un lenguaje homoicónico, las macros se vuelven mucho más complicadas y las otras opciones tienen algunas dificultades:

  • Macros basadas en texto - p. El preprocesador C: fácil de implementar pero muy difícil de usar correctamente, ya que necesita generar la sintaxis de origen correcta en forma de texto, incluidas las peculiaridades sintácticas
  • DSLS basado en macros - p. El sistema de plantillas C++. El complejo, en sí mismo puede dar lugar a una sintaxis complicada, puede ser extremadamente complejo para que los compiladores y los escritores de herramientas lo manejen correctamente, ya que introduce una nueva complejidad significativa en la sintaxis y la semántica del lenguaje.
  • API de manipulación AST/bytecode - p. Java generación de reflexión/bytecode - teóricamente muy flexible pero puede ser muy detallado: puede requerir mucho código para hacer cosas bastante simples. Si se necesitan diez líneas de código para generar el equivalente de un función de tres líneas, entonces no ha ganado mucho con sus esfuerzos de metaprogramación ...

Además, todo lo que puede hacer una macro en última instancia se puede lograr de alguna otra manera en un lenguaje completo (incluso si esto significa escribir muchas repeticiones). Como resultado de todo este truco, no es sorprendente que muchos lenguajes decidan que las macros realmente no valen la pena.

6
mikera

Para responder a sus preguntas, piense para qué macros se utilizan predominantemente (Advertencia: código compilado por el cerebro).

  • Las macros solían definir constantes simbólicas #define X 100

Esto se puede reemplazar fácilmente con: const int X = 100;

  • Las macros solían definir (esencialmente) funciones agnósticas de tipo en línea #define max(X,Y) (X>Y?X:Y)

En cualquier lenguaje que admita la sobrecarga de funciones, esto se puede emular de una manera mucho más segura mediante la sobrecarga de funciones del tipo correcto o, en un lenguaje que admita genéricos, mediante una función genérica. La macro felizmente intentará comparar cualquier cosa incluyendo punteros o cadenas, que pueden compilarse, pero es casi seguro que no es lo que deseaba. Por otro lado, si hizo que las macros sean seguras, no ofrecen beneficios ni conveniencia sobre las funciones sobrecargadas.

  • Las macros solían especificar accesos directos a elementos de uso frecuente. #define p printf

Esto se reemplaza fácilmente por una función p() que hace lo mismo. Esto está bastante involucrado en C (que requiere el uso de la familia de funciones va_arg()) pero en muchos otros lenguajes que admiten números variables de argumentos de función, es mucho más simple.

El soporte de estas características dentro un idioma en lugar de un lenguaje macro especial es más simple, menos propenso a errores y mucho menos confuso para otros que leen el código. De hecho, no puedo pensar en un single caso de uso para macros que no se pueden duplicar fácilmente de otra manera. El solo lugar donde las macros son realmente útiles es cuando están vinculadas a construcciones de compilación condicional como #if (Etc.).

En ese punto, no discutiré con usted, ya que creo que las soluciones no preprocesadoras para la compilación condicional en lenguajes populares son extremadamente engorrosas (como la inyección de código de bytes en Java). Pero lenguajes como D han creado soluciones que no requieren un preprocesador y no son más engorrosos que el uso de condicionales de preprocesador, aunque son mucho menos propensos a errores.

5
Chinmay Kanchi

Para empezar, tenga en cuenta que los MACRO en C/C++ son muy limitados, propensos a errores y no son realmente tan útiles.

Los MACRO implementados en, por ejemplo, LISP, o el lenguaje ensamblador de z/OS pueden ser confiables e increíblemente útiles.

Pero debido al abuso de la funcionalidad limitada en C, tienen una mala reputación. Así que ya nadie implementa macros, en su lugar obtienes cosas como Plantillas que hacen algunas de las cosas simples que solían hacer las macros y cosas como las anotaciones de Java que hacen algunas de las cosas más complejas que solían hacer las macros.

2
James Anderson

El mayor problema que he visto con las macros es que cuando se usan mucho pueden hacer que el código sea muy difícil de leer y mantener, ya que le permiten ocultar la lógica en la macro que puede o no ser fácil de encontrar (y puede o no ser trivial ).

2
Scott Dorman