Pourquoi les macros ne sont-elles pas incluses dans la plupart des langages de programmation modernes?

31

Je sais qu'ils sont mis en œuvre de manière extrêmement dangereuse en C / C ++. Ne peuvent-ils pas être mis en œuvre de manière plus sûre? Les inconvénients des macros sont-ils vraiment assez importants pour l'emporter sur la puissance massive qu'ils fournissent?

Casebash
la source
4
Quelle est exactement la puissance des macros qui ne peuvent pas être facilement obtenues d'une autre manière?
Chinmay Kanchi
2
En C #, il a fallu une extension de langage de base pour encapsuler la création d'une propriété simple et d'un champ de sauvegarde dans une seule déclaration. Et cela prend plus que des macros lexicales: comment créer une fonctionnalité correspondant au WithEventsmodificateur de Visual Basic ? Vous auriez besoin de quelque chose comme des macros sémantiques.
Jeffrey Hantin
3
Le problème avec les macros est que, comme tous les mécanismes puissants, cela permet à un programmeur de briser les hypothèses que d'autres ont faites ou feront. Les hypothèses sont la clé du raisonnement et sans la capacité de raisonnablement raisonner sur la logique, faire des progrès devient prohibitif.
dan_waterworth
2
@Chinmay: les macros génèrent du code. Il n'y a aucune fonctionnalité dans le langage Java avec ce pouvoir.
kevin cline
1
@Chinmay Kanchi: Les macros permettent d'exécuter (évaluer) du code au moment de la compilation plutôt qu'au moment de l'exécution.
Giorgio

Réponses:

57

Je pense que la raison principale est que les macros sont lexicales . Cela a plusieurs conséquences:

  • Le compilateur n'a aucun moyen de vérifier qu'une macro est sémantiquement fermée, c'est-à-dire qu'elle représente une «unité de sens» comme le fait une fonction. (Considérez #define TWO 1+1- qu'est-ce qui TWO*TWOéquivaut? 3.)

  • Les macros ne sont pas saisies comme les fonctions. Le compilateur ne peut pas vérifier que les paramètres et le type de retour ont un sens. Il ne peut que vérifier l'expression développée qui utilise la macro.

  • Si le code ne compile pas, le compilateur n'a aucun moyen de savoir si l'erreur se trouve dans la macro elle-même ou à l'endroit où la macro est utilisée. Le compilateur signalera soit le mauvais endroit la moitié du temps, soit il devra signaler les deux même si l'un d'eux est probablement correct. (Réfléchissez #define min(x,y) (((x)<(y))?(x):(y)): que doit faire le compilateur si les types de xet yne correspondent pas ou ne sont pas implémentés operator<?)

  • Les outils automatisés ne peuvent pas fonctionner avec eux de manière sémantiquement utile. En particulier, vous ne pouvez pas avoir des choses comme IntelliSense pour les macros qui fonctionnent comme des fonctions mais se développent en une expression. (Encore une fois, l' minexemple.)

  • Les effets secondaires d'une macro ne sont pas aussi explicites qu'ils le sont avec les fonctions, provoquant une confusion potentielle pour le programmeur. (Reprenons l' minexemple: dans un appel de fonction, vous savez que l'expression de xn'est évaluée qu'une seule fois, mais ici vous ne pouvez pas le savoir sans regarder la macro.)

Comme je l'ai dit, ce sont toutes des conséquences du fait que les macros sont lexicales. Lorsque vous essayez de les transformer en quelque chose de plus approprié, vous vous retrouvez avec des fonctions et des constantes.

Timwi
la source
32
Tous les macro-langages ne sont pas lexicaux. Par exemple, les macros Scheme sont syntaxiques et les modèles C ++ sont sémantiques. Les macro-systèmes lexicaux ont la particularité de pouvoir être cloués sur n'importe quel langage sans être conscient de sa syntaxe.
Jeffrey Hantin
8
@Jeffrey: Je suppose que mes «macros sont lexicales» est vraiment un raccourci pour «quand les gens se réfèrent aux macros dans un langage de programmation, ils pensent généralement aux macros lexicales». Il est regrettable que Scheme utilise ce terme chargé pour quelque chose qui est fondamentalement différent. Cependant, les modèles C ++ ne sont pas largement appelés macros, probablement précisément parce qu'ils ne sont pas entièrement lexicaux.
Timwi
25
Je pense que l'utilisation du terme macro par Scheme remonte à Lisp, ce qui signifie probablement qu'il est antérieur à la plupart des autres utilisations. IIRC le système C / C ++ était à l'origine appelé le préprocesseur .
Bevan
16
@Bevan a raison. Dire que les macros sont lexicales, c'est comme dire que les oiseaux ne peuvent pas voler parce que l'oiseau que vous connaissez le plus est un pingouin. Cela dit, la plupart (mais pas tous) des points que vous soulevez s'appliquent également aux macros syntaxiques, mais peut-être à un moindre degré.
Laurence Gonsalves
13

Mais oui, les macros peuvent être conçues et implémentées mieux qu'en C / C ++.

Le problème avec les macros est qu'elles sont en fait un mécanisme d' extension de syntaxe de langage qui réécrit votre code en quelque chose d'autre.

  • Dans le cas C / C ++, il n'y a pas de vérification d'intégrité fondamentale. Si vous faites attention, les choses vont bien. Si vous faites une erreur ou si vous abusez des macros, vous pouvez rencontrer de gros problèmes.

    Ajoutez à cela que beaucoup de choses simples que vous pouvez faire avec des macros (style C / C ++) peuvent être faites d'autres manières dans d'autres langues.

  • Dans d'autres langues telles que divers dialectes Lisp, les macros sont mieux intégrées à la syntaxe du langage principal, mais vous pouvez toujours rencontrer des problèmes avec les déclarations dans une macro "qui fuit". Ceci est résolu par des macros hygiéniques .


Bref contexte historique

Les macros (abréviation de macro-instructions) sont apparues pour la première fois dans le contexte du langage d'assemblage. Selon Wikipedia , les macros étaient disponibles dans certains assembleurs IBM dans les années 1950.

Le LISP d'origine n'avait pas de macros, mais elles ont été introduites pour la première fois dans MacLisp au milieu des années 1960: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -transformation-apparaître . http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf . Avant cela, "fexprs" offrait une fonctionnalité de type macro.

Les premières versions de C n'avaient pas de macros ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Ceux-ci ont été ajoutés vers 1972-73 via un préprocesseur. Avant cela, C ne supportait que #includeet #define.

Le macro-préprocesseur M4 est né vers 1977.

Des langages plus récents implémentent apparemment des macros où le modèle de fonctionnement est syntaxique plutôt que textuel.

Ainsi, lorsque quelqu'un parle de la primauté d'une définition particulière du terme «macro», il est important de noter que le sens a évolué au fil du temps.

Stephen C
la source
9
C ++ est doté (?) De DEUX systèmes de macros: l'expansion du préprocesseur et du modèle.
Jeffrey Hantin
Je pense que prétendre que l'expansion du modèle est une macro étire la définition de la macro. L'utilisation de votre définition rend la question d'origine vide de sens et tout simplement fausse, car la plupart des langues modernes ont en fait des macros (selon votre définition). Cependant, l'utilisation de la macro comme l'écrasante majorité des développeurs l'utiliserait en fait une bonne question.
Dunk
@Dunk, la définition d'une macro comme dans Lisp est antérieure à la compréhension stupide "moderne" comme dans le préprocesseur pathétique C.
SK-logic
@SK: vaut-il mieux être "techniquement" correct et obscurcir la conversation ou vaut-il mieux être compris?
Dunk
1
@Dunk, la terminologie n'appartient pas aux hordes ignorantes. Leur ignorance ne doit jamais être prise en considération, sinon cela conduira à abrutir tout le domaine de l'informatique. Si quelqu'un comprend la «macro» uniquement comme une référence à un préprocesseur C, ce n'est rien d'autre qu'une ignorance. Je doute qu'il y ait une proportion importante de personnes qui n'ont jamais entendu parler de Lisp ou même, pardonnez mon français, des "macros" VBA dans Office.
SK-logic
12

Comme le note Scott , les macros peuvent vous permettre de masquer la logique. Bien sûr, il en va de même des fonctions, des classes, des bibliothèques et de nombreux autres périphériques courants.

Mais un puissant système de macro peut aller plus loin, vous permettant de concevoir et d'utiliser une syntaxe et des structures que l'on ne trouve pas normalement dans le langage. Cela peut être un merveilleux outil en effet: des langages spécifiques à un domaine, des générateurs de code et bien plus, le tout dans le confort d'un environnement linguistique unique ...

Cependant, il peut être abusé. Cela peut rendre le code plus difficile à lire, à comprendre et à déboguer, augmenter le temps nécessaire aux nouveaux programmeurs pour se familiariser avec une base de code et entraîner des erreurs et des retards coûteux.

Ainsi pour les langages destinés à simplifier la programmation (comme Java ou Python), un tel système est un anathème.

Shog9
la source
3
Et puis Java a déraillé en ajoutant foreach, annotations, assertions, génériques par effacement, ...
Jeffrey Hantin
2
@Jeffrey: et n'oublions pas, beaucoup de générateurs de code tiers . À la réflexion, oublions-les.
Shog9
4
Un autre exemple de la façon dont Java était simpliste au lieu de simple.
Jeffrey Hantin du
3
@JeffreyHantin: Quel est le problème avec foreach?
Casebash
1
@Dunk Cette analogie pourrait être un tronçon ... mais je pense que l'extensibilité du langage est un peu comme le plutonium. Coûteux à mettre en œuvre, très difficile à usiner dans les formes souhaitées et remarquablement dangereux s'il est mal utilisé, mais aussi incroyablement puissant lorsqu'il est bien appliqué.
Jeffrey Hantin
6

Les macros peuvent être implémentées en toute sécurité dans certaines circonstances - en Lisp par exemple, les macros ne sont que des fonctions qui renvoient du code transformé sous forme de structure de données (expression s). Bien sûr, Lisp profite de manière significative du fait qu'il est homoiconique et du fait que "le code est une donnée".

Un exemple de la simplicité des macros est cet exemple Clojure qui spécifie une valeur par défaut à utiliser dans le cas d'une exception:

(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

Même en Lisps, le conseil général est de "ne pas utiliser de macros sauf si vous devez".

Si vous n'utilisez pas un langage homoiconique, les macros deviennent beaucoup plus délicates et les diverses autres options présentent toutes des pièges:

  • Macros basées sur du texte - par exemple le préprocesseur C - simples à implémenter mais très difficiles à utiliser correctement car vous devez générer la syntaxe source correcte sous forme textuelle, y compris toutes les bizarreries syntaxiques
  • DSLS basé sur des macros - par exemple le système de modèles C ++. Complexe, peut lui-même entraîner une syntaxe délicate, peut être extrêmement complexe pour les rédacteurs de compilateurs et d'outils à gérer correctement car il introduit une nouvelle complexité importante dans la syntaxe et la sémantique du langage.
  • API de manipulation d'AST / bytecode - par exemple la réflexion Java / génération de bytecode - théoriquement très flexible mais peut devenir très verbeuse: elle peut nécessiter beaucoup de code pour faire des choses assez simples. S'il faut dix lignes de code pour générer l'équivalent d'une fonction à trois lignes, alors vous n'avez pas beaucoup gagné avec vos efforts de méta-programmation ...

De plus, tout ce qu'une macro peut faire peut finalement être réalisé d'une autre manière dans un langage complet (même si cela signifie écrire beaucoup de passe-partout). À la suite de toute cette ruse, il n'est pas surprenant que de nombreuses langues décident que les macros ne valent pas vraiment tous les efforts à mettre en œuvre.

Mikera
la source
Pouah. Pas plus "le code est des données est une bonne chose" poubelle. Le code est du code, les données sont des données et ne pas séparer correctement les deux est un trou de sécurité. On pourrait penser que l'émergence de SQL Injection comme l'une des plus grandes classes de vulnérabilité existantes aurait discrédité l'idée de rendre le code et les données facilement interchangeables une fois pour toutes.
Mason Wheeler
10
@Mason - Je ne pense pas que vous compreniez le concept du code est des données. Tout le code source du programme C est également des données - il se trouve qu'il est simplement exprimé au format texte. Les lisps sont les mêmes, sauf qu'ils expriment le code dans des structures de données intermédiaires pratiques (expressions s) qui lui permettent d'être manipulé et transformé par des macros avant la compilation. Dans les deux cas, l'envoi d'une entrée non fiable au compilateur pourrait être un trou de sécurité - mais c'est difficile à faire accidentellement et ce serait votre faute si vous faites quelque chose de stupide, pas celui du compilateur.
mikera
La rouille est un autre système macro sûr et intéressant. Il a des règles / sémantiques strictes pour éviter les types de problèmes associés aux macros lexicales C / C ++, et utilise un DSL pour capturer des parties de l'expression d'entrée dans des variables de macro à insérer plus tard. Ce n'est pas aussi puissant que la capacité de Lisps d'appeler n'importe quelle fonction sur l'entrée, mais il fournit un large ensemble de macros de manipulation utiles qui peuvent être appelées à partir d'autres macros.
zstewart
Pouah. Pas plus de déchets de sécurité . Sinon, blâmez chaque système avec l'architecture von Neumann intégrée, par exemple chaque processeur IA-32 avec une capacité d'auto-modification du code. Je soupçonne si vous pouvez combler le trou physiquement ... Quoi qu'il en soit, vous devez faire face au fait en général: l'isomorphisme entre le code et les données est la nature du monde de plusieurs façons . Et c'est (probablement) vous, le programmeur, qui avez le devoir de maintenir l'invariance des exigences de sécurité, ce qui n'est pas la même chose que d'appliquer une ségrégation prématurée n'importe où.
FrankHB
4

Pour répondre à vos questions, pensez aux macros utilisées principalement (Avertissement: code compilé par le cerveau).

  • Macros utilisées pour définir les constantes symboliques #define X 100

Cela peut facilement être remplacé par: const int X = 100;

  • Macros utilisées pour définir (essentiellement) des fonctions agnostiques de type inline #define max(X,Y) (X>Y?X:Y)

Dans n'importe quel langage qui prend en charge la surcharge de fonctions, cela peut être émulé d'une manière beaucoup plus sûre pour les types en ayant des fonctions surchargées du type correct, ou, dans un langage qui prend en charge les génériques, par une fonction générique. La macro tentera avec plaisir de comparer tout ce qui inclut les pointeurs ou les chaînes, qui pourraient être compilés, mais ce n'est certainement pas ce que vous vouliez. D'un autre côté, si vous avez rendu les macros sécuritaires, elles n'offrent aucun avantage ni aucune commodité par rapport aux fonctions surchargées.

  • Macros utilisées pour spécifier des raccourcis vers des éléments souvent utilisés. #define p printf

Ceci est facilement remplacé par une fonction p()qui fait la même chose. Ceci est assez impliqué dans C (vous obligeant à utiliser la va_arg()famille de fonctions) mais dans de nombreux autres langages qui prennent en charge un nombre variable d'arguments de fonction, c'est beaucoup plus simple.

La prise en charge de ces fonctionnalités dans un langage plutôt que dans un langage macro spécial est plus simple, moins sujette aux erreurs et beaucoup moins déroutante pour les autres qui lisent le code. En fait, je ne peux pas penser à un cas d'utilisation unique pour les macros qui ne peuvent pas être facilement dupliquées d'une autre manière. Le seul endroit où les macros sont vraiment utiles est lorsqu'elles sont liées à des constructions de compilation conditionnelle comme #if(etc.).

Sur ce point, je ne discuterai pas avec vous, car je pense que les solutions non préprocesseurs pour la compilation conditionnelle dans les langages populaires sont extrêmement lourdes (comme l'injection de bytecode en Java). Mais des langages comme D ont proposé des solutions qui ne nécessitent pas de préprocesseur et ne sont pas plus encombrantes que l'utilisation de conditions de préprocesseur, tout en étant beaucoup moins sujettes aux erreurs.

Chinmay Kanchi
la source
1
si vous devez #define max, veuillez mettre des crochets autour des paramètres, donc il n'y a pas d'effets inattendus de la priorité de l'opérateur ... comme #define max (X, Y) ((X)> (Y)? (X) :( Y))
foo
Vous vous rendez compte que ce n'était qu'un exemple ... L'intention était d'illustrer.
Chinmay Kanchi
+1 pour ce traitement systématique de la question. J'aime ajouter que la compilation conditionnelle peut facilement devenir un cauchemar - je me souviens qu'il y avait un programme nommé "unifdef" (??) dont le but était de rendre visible ce qui restait après le post-traitement.
Ingo
7
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way: en C au moins, vous ne pouvez pas former d'identifiants (noms de variables) en utilisant la concaténation de jetons sans utiliser de macros.
Charles Salvia
Les macros permettent de garantir que certains codes et structures de données restent "parallèles". Par exemple, s'il existe un petit nombre de conditions avec des messages associés, et qu'il faudra les conserver dans un format concis, essayer d'utiliser un enumpour définir les conditions et un tableau de chaînes constant pour définir les messages pourrait entraîner des problèmes si le enum et le tableau se désynchronisent. L'utilisation d'une macro pour définir toutes les paires (énumération, chaîne), puis l'inclusion de cette macro deux fois avec d'autres définitions appropriées dans la portée à chaque fois permettrait de placer chaque valeur énumérée à côté de sa chaîne.
supercat
2

Le plus gros problème que j'ai vu avec les macros est que, lorsqu'elles sont fortement utilisées, elles peuvent rendre le code très difficile à lire et à maintenir car elles vous permettent de masquer la logique dans la macro qui peut ou peut ne pas être facile à trouver (et peut être triviale ou non). ).

Scott Dorman
la source
La macro ne doit-elle pas être documentée?
Casebash
@Casebash: Bien sûr, la macro doit être documentée comme tout autre code source ... mais dans la pratique, je l'ai rarement vu faire.
Scott Dorman
3
Avez-vous déjà débogué de la documentation? Ce n'est tout simplement pas suffisant lorsqu'il s'agit de code mal écrit.
JeffO
La POO a aussi certains de ces problèmes ...
aoeu256
2

Commençons par noter que les macros en C / C ++ sont très limitées, sujettes aux erreurs et pas vraiment utiles.

Les MACRO telles qu'implémentées dans LISP ou le langage assembleur z / OS peuvent être fiables et incroyablement utiles.

Mais à cause de l'abus des fonctionnalités limitées de C, ils ont acquis une mauvaise réputation. Donc, personne n'implémente plus de macros, au lieu de cela, vous obtenez des choses comme les modèles qui font certaines des choses simples que les macros faisaient et des choses comme les annotations de Java qui font certaines des macro de choses les plus complexes utilisées auparavant.

James Anderson
la source