Quand utiliser la macro ou ne pas utiliser [fermé]


12

Quand dois-je utiliser la macro dans mon programme ou pas?

Cette question est inspirée d' une réponse informative de @tarsius. Je crois que beaucoup ont de grandes expériences pour partager les autres. J'espère que cette question deviendra l'une des grandes questions subjectives et aidera les programmeurs Emacs.

Réponses:


13

Règle d'or

Utilisez des macros pour la syntaxe , mais jamais pour la sémantique.

C'est bien d'utiliser une macro pour du sucre syntaxique, mais c'est une mauvaise idée de mettre de la logique dans un corps de macro, que ce soit dans la macro elle-même ou dans l'expansion. Si vous en ressentez le besoin, écrivez une fonction à la place et une "macro associée" pour le sucre syntaxique. Pour parler simplement, si vous avez un letdans votre macro, vous recherchez des problèmes.

Pourquoi?

Les macros ont énormément d'inconvénients:

  • Ils s'étendent au moment de la compilation et doivent donc être écrits avec soin pour maintenir la compatibilité binaire entre plusieurs versions. Par exemple, la suppression d'une fonction ou d'une variable utilisée dans l'expansion d'une macro rompra le code d'octet de tous les packages dépendants qui utilisaient cette macro. En d'autres termes, si vous modifiez l'implémentation d'une macro d'une manière incompatible, tous les packages dépendants doivent être recompilés. Et la compatibilité binaire n'est pas toujours évidente…
  • Vous devez prendre soin de maintenir un ordre d'évaluation intuitif . Il est trop facile d'écrire une macro qui évalue un formulaire trop souvent, ou au mauvais moment, ou au mauvais endroit, etc…
  • Vous devez faire attention à la sémantique de liaison : les macros héritent de leur contexte de liaison du site d'expansion , ce qui signifie que vous ne savez pas a priori quelles variables lexicales existent et si la liaison lexicale est même disponible. Une macro doit fonctionner avec les deux sémantiques de liaison.
  • À cet égard, vous devez faire attention lors de l'écriture de macros qui créent des fermetures . N'oubliez pas que vous obtenez les liaisons à partir du site d'extension, pas à partir de la définition lexicale, alors surveillez ce que vous fermez et comment vous créez la fermeture (rappelez-vous, vous ne savez pas si la liaison lexicale est active sur le site d'extension et si vous avez même des fermetures disponibles).

1
Les notes sur la liaison lexicale vs l'expansion macro sont particulièrement intéressantes; merci lunaryorn. (Je n'ai jamais activé la liaison lexicale pour le code que j'ai écrit, donc ce n'est pas un conflit auquel j'ai jamais pensé.)
phils

6

D'après mon expérience de lecture, de débogage et de modification de mon code Elisp et d'autres, je suggère d'éviter autant que possible les macros.

La chose évidente à propos des macros est qu'elles n'évaluent pas leurs arguments. Ce qui signifie que si vous avez une expression complexe enveloppée dans une macro, vous n'avez aucune idée si elle sera évaluée ou non, et dans quel contexte, sauf si vous recherchez la définition de macro. Ce n'est peut-être pas un gros problème pour vous, car vous connaissez la définition de votre propre macro (actuellement, probablement pas quelques années plus tard).

Cet inconvénient ne s'applique pas aux fonctions: tous les arguments sont évalués avant l'appel de la fonction. Ce qui signifie que vous pouvez augmenter la complexité de l'évaluation dans une situation de débogage inconnue. Vous pouvez même sauter directement les définitions des fonctions qui vous sont inconnues, tant qu'elles renvoient des données simples, et vous supposez qu'elles ne touchent pas l'état global.

Pour le reformuler autrement, les macros deviennent la partie du langage. Si vous lisez un code avec des macros inconnues, vous lisez presque un code dans une langue que vous ne connaissez pas.

Exceptions aux réserves ci-dessus:

Les macros standard aiment push, pop, dolistfont vraiment beaucoup pour vous, et sont connus pour d' autres programmeurs. Ils font essentiellement partie de la spécification linguistique. Mais il est pratiquement impossible de standardiser votre propre macro. Non seulement il est difficile de trouver quelque chose de simple et d'utile comme les exemples ci-dessus, mais il est encore plus difficile de convaincre chaque programmeur de l'apprendre.

En outre, cela ne me dérange pas des macros comme save-selected-window: elles stockent et restaurent l'état et évaluent leurs arguments de la même manière progn. Assez simple, rend le code plus simple.

Un autre exemple de macros OK peut être quelque chose comme defhydraou use-package. Ces macros sont livrées avec leur propre mini-langage. Et ils traitent toujours des données simples ou agissent comme progn. De plus, ils sont généralement au niveau supérieur, donc il n'y a pas de variables sur la pile d'appels pour que les arguments de macro dépendent, ce qui le rend un peu plus simple.

Un exemple d'une mauvaise macro, à mon avis, est magit-section-actionet magit-section-casedans Magit. Le premier a déjà été supprimé, mais le second demeure.


4

Je vais être franc: je ne comprends pas ce que signifie "utiliser pour la syntaxe, pas pour la sémantique". C'est pourquoi une partie de cette réponse traitera de la réponse de lunaryon , et l'autre partie sera ma tentative de répondre à la question d'origine.

Lorsque le mot «sémantique» utilisé dans la programmation, il se réfère à la façon dont l'auteur de la langue a choisi d'interpréter leur langue. Cela se résume généralement à un ensemble de formules qui transforment les règles de grammaire du langage en quelques autres règles que le lecteur est censé connaître. Par exemple, Plotkin dans son livre Structural Approach to Operational Semantics utilise un langage de dérivation logique pour expliquer à quoi correspond la syntaxe abstraite (qu'est-ce que cela signifie d'exécuter un programme). En d'autres termes, si la syntaxe est ce qui constitue le langage de programmation à un certain niveau, alors il doit avoir une certaine sémantique, sinon c'est, eh bien ... pas un langage de programmation.

Mais, oublions pour le moment les définitions formelles, après tout, il est important de comprendre l'intention. Ainsi, il semble que lunaryon encourage ses lecteurs à utiliser des macros lorsqu'aucune nouvelle signification ne doit être ajoutée au programme, mais simplement une sorte d'abréviation. Pour moi, cela semble étrange et contraste fortement avec la façon dont les macros sont réellement utilisées. Voici des exemples qui créent clairement de nouvelles significations:

  1. defun et les amis, qui sont une partie essentielle du langage, ne peuvent pas être décrits dans les mêmes termes que vous décririez les appels de fonction.

  2. setfles règles décrivant le fonctionnement des fonctions sont inadéquates pour décrire les effets d'une expression comme (setf (aref x y) z).

  3. with-output-to-stringmodifie également la sémantique du code à l'intérieur de la macro. De même, with-current-bufferet un tas d'autres with-macros.

  4. dotimes et similaire ne peut pas être décrit en termes de fonctions.

  5. ignore-errors change la sémantique du code qu'il encapsule.

  6. Il y a tout un tas de macros définies dans cl-libpackage, eieiopackage et quelques autres bibliothèques presque omniprésentes utilisées dans Emacs Lisp qui, bien qu'elles puissent ressembler à des formes de fonction, ont des interprétations différentes.

Donc, si quoi que ce soit, les macros sont l'outil pour introduire une nouvelle sémantique dans un langage. Je ne peux pas penser à une autre façon de faire cela (du moins pas dans Emacs Lisp).


Quand je n'utiliserais pas de macros:

  1. Quand ils n'introduisent pas de nouvelles constructions, avec une nouvelle sémantique (c'est-à-dire que la fonction ferait le travail tout aussi bien).

  2. Lorsque c'est juste une sorte de commodité (c'est-à-dire créer une macro nommée mvbqui se développe cl-multiple-value-bindjuste pour la raccourcir).

  3. Lorsque je m'attends à ce que beaucoup de codes de gestion des erreurs soient cachés par la macro (comme cela a été noté, les macros sont difficiles à déboguer).


Quand je préférerais fortement les macros aux fonctions:

  1. Lorsque les concepts spécifiques au domaine sont masqués par des appels de fonction. Par exemple, si l'on a besoin de passer le même contextargument à un tas de fonctions qui doivent être appelées successivement, je préférerais les envelopper dans une macro, où l' contextargument est caché.

  2. Lorsque le code générique sous forme de fonction est trop verbeux (les constructions d'itération nue dans Emacs Lisp sont trop verbeuses à mon goût et exposent inutilement le programmeur aux détails d'implémentation de bas niveau).


Illustration

Ci-dessous est la version édulcorée destinée à donner une intuition sur ce que l'on entend par sémantique en informatique.

Supposons que vous ayez un langage de programmation extrêmement simple avec juste ces règles de syntaxe:

variable := 1 | 0
operation := +
expression := variable | (expression operation expression)

avec ce langage, nous pouvons construire des "programmes" comme (1+0)+1ou 0ou ((0+0))etc.

Mais tant que nous n'avons pas fourni de règles sémantiques, ces "programmes" n'ont aucun sens.

Supposons que nous équipions maintenant notre langage des règles (sémantiques):

0+0  0+1  1+0  1+1  (exp)
---, ---, ---, ---, -----
 0   1+0   1    0    exp

Maintenant, nous pouvons réellement calculer en utilisant ce langage. Autrement dit, nous pouvons prendre la représentation syntaxique, la comprendre de manière abstraite (en d'autres termes, la convertir en syntaxe abstraite), puis utiliser des règles sémantiques pour manipuler cette représentation.

Lorsque nous parlons de la famille de langages Lisp, les règles sémantiques de base de l'évaluation des fonctions sont à peu près celles du lambda-calcul:

(f x)  (lambda x y)   x
-----, ------------, ---
 fx        ^x.y       x

Et les mécanismes macro citant-extension sont également connus comme outils de méta-programmation, à savoir qu'ils permettent de parler au sujet du programme, au lieu de la programmation juste. Ils y parviennent en créant une nouvelle sémantique à l'aide de la "méta-couche". L'exemple d'une macro aussi simple est:

(a . b)
-------
(cons a b)

Ce n'est pas réellement une macro dans Emacs Lisp, mais cela aurait pu l'être. Je n'ai choisi que pour des raisons de simplicité. Notez qu'aucune des règles sémantiques définies ci-dessus ne s'appliquerait à ce cas puisque le candidat le plus proche (f x)interprète fcomme une fonction, alors qu'il an'est pas nécessairement une fonction.


1
Le «sucre syntaxique» n'est pas vraiment un terme en informatique. C'est quelque chose que les ingénieurs utilisent pour signifier diverses choses. Je n'ai donc pas de réponse définitive. Puisque nous parlons de sémantique, la règle d'interprétation de la syntaxe de la forme (x y)est donc à peu près "évaluer y, puis appeler x avec le résultat de l'évaluation précédente". Lorsque vous écrivez (defun x y), y n'est pas évalué et x non plus. Que vous puissiez écrire un code avec un effet équivalent en utilisant une sémantique différente ne nie pas que ce morceau de code ait sa propre sémantique.
wvxvw

1
Concernant votre deuxième commentaire: pouvez-vous me renvoyer à la définition de macro que vous utilisez qui dit ce que vous prétendez? Je viens de vous donner un exemple où une nouvelle règle sémantique est introduite au moyen d'une macro. Au moins dans le sens précis de CS. Je ne pense pas que l'argumentation sur les définitions soit productive, et je pense également que les définitions utilisées dans CS sont la meilleure correspondance pour cette discussion.
wvxvw

1
Eh bien ... il se trouve que vous avez votre propre idée de ce que signifie le mot "sémantique", ce qui est un peu ironique, si vous y réfléchissez :) Mais vraiment, ces choses ont des définitions très précises, vous venez, eh bien .. ignorez-les. Le livre que j'ai lié dans la réponse est un manuel standard sur la sémantique enseigné dans le cours correspondant CS. Si vous venez de lire l'article correspondant du Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29, vous verrez de quoi il s'agit réellement. L'exemple est la règle d'interprétation de l' (defun x y)application vs fonction.
wvxvw

Eh bien, je ne pense pas que c'est ma façon d'avoir une discussion. C'était même une erreur d'essayer d'en démarrer une; J'ai supprimé mes commentaires car cela ne sert à rien. Je suis désolé d'avoir perdu votre temps.
lunaryorn
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.