En lisant les essais de Paul Graham sur les langages de programmation, on pourrait penser que les macros Lisp sont la seule voie à suivre. En tant que développeur occupé, travaillant sur d'autres plateformes, je n'ai pas eu le privilège d'utiliser des macros Lisp. En tant que personne qui veut comprendre le buzz, veuillez expliquer ce qui rend cette fonctionnalité si puissante.
Veuillez également relier cela à quelque chose que je comprendrais dans les mondes du développement Python, Java, C # ou C.
macros
lisp
homoiconicity
mentholé
la source
la source
Réponses:
Pour donner la réponse courte, des macros sont utilisées pour définir des extensions de syntaxe de langage pour Common Lisp ou DSL (Domain Specific Languages). Ces langages sont intégrés directement dans le code Lisp existant. Désormais, les DSL peuvent avoir une syntaxe similaire à Lisp (comme l' interpréteur Prolog de Peter Norvig pour Common Lisp) ou complètement différente (par exemple Infix Notation Math for Clojure).
Voici un exemple plus concret:
Python a des compréhensions de liste intégrées dans le langage. Cela donne une syntaxe simple pour un cas courant. La ligne
donne une liste contenant tous les nombres pairs entre 0 et 9. De retour dans le Python 1.5 jours il n'y avait pas une telle syntaxe; vous utiliseriez quelque chose de plus comme ceci:
Ce sont deux fonctionnellement équivalents. Invoquons notre suspension de l'incrédulité et prétendons que Lisp a une macro de boucle très limitée qui ne fait que l'itération et aucun moyen facile de faire l'équivalent des listes de compréhension.
En lisp, vous pouvez écrire ce qui suit. Je dois noter que cet exemple artificiel est choisi pour être identique au code Python et non un bon exemple de code Lisp.
Avant d'aller plus loin, je devrais mieux expliquer ce qu'est une macro. Il s'agit d'une transformation effectuée code par code. C'est-à-dire qu'un morceau de code, lu par l'interpréteur (ou le compilateur), qui prend le code en argument, manipule et renvoie le résultat, qui est ensuite exécuté sur place.
Bien sûr, c'est beaucoup de dactylographie et les programmeurs sont paresseux. Nous pourrions donc définir DSL pour faire des compréhensions de liste. En fait, nous utilisons déjà une macro (la macro de boucle).
Lisp définit quelques formes de syntaxe spéciales. La citation (
'
) indique que le jeton suivant est un littéral. La quasiquote ou backtick (`
) indique que le jeton suivant est un littéral avec des échappements. Les échappements sont indiqués par l'opérateur virgule. Le littéral'(1 2 3)
est l'équivalent de Python[1, 2, 3]
. Vous pouvez l'assigner à une autre variable ou l'utiliser sur place. Vous pouvez penser`(1 2 ,x)
à l'équivalent de Python[1, 2, x]
oùx
est une variable précédemment définie. Cette notation de liste fait partie de la magie qui entre dans les macros. La deuxième partie est le lecteur Lisp qui substitue intelligemment les macros au code mais qui est mieux illustré ci-dessous:On peut donc définir une macro appelée
lcomp
(abréviation de list comprehension). Sa syntaxe sera exactement comme le python que nous avons utilisé dans l'exemple[x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
Maintenant, nous pouvons exécuter sur la ligne de commande:
Assez bien, hein? Maintenant ça ne s'arrête pas là. Vous avez un mécanisme, ou un pinceau, si vous voulez. Vous pouvez avoir n'importe quelle syntaxe que vous pourriez souhaiter. Comme la
with
syntaxe Python ou C # . Ou la syntaxe LINQ de .NET. En fin de compte, c'est ce qui attire les gens vers Lisp - une flexibilité ultime.la source
(loop for x from 0 below 10 when (evenp x) collect x)
, plus d' exemples ici . Mais en effet, la boucle n'est "qu'une macro" (je l'ai en fait ré-implémentée il y a quelque temps )Vous trouverez ici un débat complet sur la macro lisp .
Un sous-ensemble intéressant de cet article:
la source
Les macros Lisp courantes étendent essentiellement les "primitives syntaxiques" de votre code.
Par exemple, en C, la construction switch / case ne fonctionne qu'avec des types intégraux et si vous souhaitez l'utiliser pour des flottants ou des chaînes, vous vous retrouvez avec des instructions if imbriquées et des comparaisons explicites. Il n'y a également aucun moyen d'écrire une macro C pour faire le travail à votre place.
Mais, comme une macro lisp est (essentiellement) un programme lisp qui prend des extraits de code en entrée et retourne du code pour remplacer l '"invocation" de la macro, vous pouvez étendre votre répertoire "primitives" autant que vous le souhaitez, finissant généralement par avec un programme plus lisible.
Pour faire de même en C, vous devez écrire un pré-processeur personnalisé qui mange votre source initiale (pas tout à fait-C) et crache quelque chose qu'un compilateur C peut comprendre. Ce n'est pas une mauvaise façon de procéder, mais ce n'est pas nécessairement la plus facile.
la source
Les macros Lisp vous permettent de décider quand (le cas échéant) une partie ou une expression sera évaluée. Pour mettre un exemple simple, pensez aux C:
Ce que cela dit, c'est: évaluer
expr1
, et, si c'est vrai, évaluerexpr2
, etc.Essayez maintenant d'en faire
&&
une fonction ... c'est vrai, vous ne pouvez pas. Appeler quelque chose comme:Évaluera les trois
exprs
avant de donner une réponse, que ceexpr1
soit faux ou non!Avec les macros lisp, vous pouvez coder quelque chose comme:
vous avez maintenant un
&&
, que vous pouvez appeler comme une fonction et il n'évaluera aucune des formes que vous lui passerez à moins qu'elles ne soient toutes vraies.Pour voir comment cela est utile, contraste:
et:
D'autres choses que vous pouvez faire avec les macros sont la création de nouveaux mots-clés et / ou mini-langues (consultez la
(loop ...)
macro pour un exemple), l'intégration d'autres langues dans lisp, par exemple, vous pouvez écrire une macro qui vous permet de dire quelque chose comme:Et ce n'est même pas entrer dans les macros Reader .
J'espère que cela t'aides.
la source
(and ...
évaluera les expressions jusqu'à ce que l'on évalue à faux, notez que les effets secondaires générés par la fausse évaluation auront lieu, seules les expressions suivantes seront ignorées.Je ne pense pas avoir jamais vu de macros Lisp mieux expliquées que par cet homme: http://www.defmacro.org/ramblings/lisp.html
la source
Pensez à ce que vous pouvez faire en C ou C ++ avec des macros et des modèles. Ce sont des outils très utiles pour gérer le code répétitif, mais ils sont limités de manière assez sévère.
Les macros Lisp et Lisp résolvent ces problèmes.
Parlez à tous ceux qui maîtrisent le C ++ et demandez-leur combien de temps ils ont passé à apprendre tous les fudgery de modèles dont ils ont besoin pour faire de la métaprogrammation de modèles. Ou toutes les astuces folles dans des livres (excellents) comme Modern C ++ Design , qui sont toujours difficiles à déboguer et (en pratique) non portables entre des compilateurs du monde réel, même si le langage est normalisé depuis une décennie. Tout cela fond si la langue que vous utilisez pour la métaprogrammation est le même langage que vous utilisez pour la programmation!
la source
Une macro lisp prend en entrée un fragment de programme. Ce fragment de programme est représenté par une structure de données qui peut être manipulée et transformée comme vous le souhaitez. À la fin, la macro génère un autre fragment de programme, et ce fragment est ce qui est exécuté au moment de l'exécution.
C # n'a pas de fonction de macro, mais un équivalent serait si le compilateur analysait le code dans un arbre CodeDOM et le transmettait à une méthode, qui l'a transformé en un autre CodeDOM, qui est ensuite compilé en IL.
Cela pourrait être utilisé pour implémenter la syntaxe "sucre" comme la
for each
clauseusing
-statement, les expressions linqselect
et ainsi de suite, en tant que macros qui se transforment en code sous-jacent.Si Java avait des macros, vous pourriez implémenter la syntaxe Linq en Java, sans avoir besoin de Sun pour changer le langage de base.
Voici un pseudo-code pour savoir à quoi
using
pourrait ressembler une macro de style lisp en C # pour l'implémentation :la source
using
se ressembler à ceci ;)Étant donné que les réponses existantes donnent de bons exemples concrets expliquant ce que les macros réalisent et comment, cela pourrait peut-être aider à rassembler certaines des réflexions sur les raisons pour lesquelles la macro-installation est un gain significatif par rapport à d'autres langues ; d'abord à partir de ces réponses, puis une excellente d'ailleurs:
- Vatine
- Matt Curtis
- Miguel Ping
- Peter Seibel, dans "Practical Common Lisp"
la source
Je ne suis pas sûr de pouvoir ajouter un aperçu aux (excellents) messages de tout le monde, mais ...
Les macros Lisp fonctionnent très bien en raison de la nature de la syntaxe Lisp.
Le lisp est un langage extrêmement régulier (penser à tout est une liste ); les macros vous permettent de traiter les données et le code de la même manière (aucune analyse de chaîne ou autre piratage n'est nécessaire pour modifier les expressions lisp). Vous combinez ces deux fonctionnalités et vous avez un moyen très propre de modifier le code.
Edit: Ce que j'essayais de dire, c'est que Lisp est homoiconique , ce qui signifie que la structure des données d'un programme lisp est écrite en lisp lui-même.
Ainsi, vous vous retrouvez avec un moyen de créer votre propre générateur de code au-dessus du langage en utilisant le langage lui-même avec toute sa puissance (par exemple, en Java, vous devez pirater votre chemin avec le tissage de bytecode, bien que certains frameworks comme AspectJ vous permettent de faites cela en utilisant une approche différente, c'est fondamentalement un hack).
En pratique, avec les macros, vous finissez par créer votre propre mini-langue au-dessus de lisp, sans avoir besoin d'apprendre des langues ou des outils supplémentaires, et en utilisant toute la puissance de la langue elle-même.
la source
S-expressions
, pas de listes.Les macros Lisp représentent un modèle qui se produit dans presque tous les projets de programmation importants. Finalement, dans un grand programme, vous avez une certaine section de code où vous réalisez qu'il serait plus simple et moins sujet aux erreurs d'écrire un programme qui génère le code source sous forme de texte que vous pouvez ensuite simplement coller.
En Python, les objets ont deux méthodes
__repr__
et__str__
.__str__
est tout simplement la représentation lisible par l'homme.__repr__
renvoie une représentation qui est du code Python valide, c'est-à-dire quelque chose qui peut être entré dans l'interpréteur comme Python valide. De cette façon, vous pouvez créer de petits extraits de Python qui génèrent du code valide qui peut être collé dans votre source réelle.En Lisp, tout ce processus a été formalisé par le système macro. Bien sûr, cela vous permet de créer des extensions de la syntaxe et de faire toutes sortes de choses fantaisistes, mais son utilité réelle est résumée par ce qui précède. Bien sûr, il est utile que le système de macro Lisp vous permette de manipuler ces "extraits" avec toute la puissance de la langue entière.
la source
En bref, les macros sont des transformations de code. Ils permettent d'introduire de nombreuses nouvelles constructions syntaxiques. Par exemple, considérez LINQ en C #. Dans lisp, il existe des extensions de langage similaires qui sont implémentées par des macros (par exemple, construction de boucle intégrée, itération). Les macros réduisent considérablement la duplication de code. Les macros permettent d'incorporer de «petits langages» (par exemple, là où en c # / java on utiliserait xml pour configurer, en lisp la même chose peut être réalisée avec des macros). Les macros peuvent masquer les difficultés d'utilisation de l'utilisation des bibliothèques.
Par exemple, en lisp, vous pouvez écrire
et cela masque tous les éléments de la base de données (transactions, fermeture de connexion appropriée, récupération de données, etc.) alors qu'en C #, cela nécessite la création de SqlConnections, SqlCommands, l'ajout de SqlParameters à SqlCommands, la boucle sur SqlDataReaders, la fermeture correcte de ceux-ci.
la source
Bien que ce qui précède explique ce que sont les macros et même des exemples sympas, je pense que la principale différence entre une macro et une fonction normale est que LISP évalue tous les paramètres avant d'appeler la fonction. Avec une macro c'est l'inverse, LISP transmet les paramètres non évalués à la macro. Par exemple, si vous passez (+ 1 2) à une fonction, la fonction recevra la valeur 3. Si vous passez ceci à une macro, elle recevra une liste (+ 1 2). Cela peut être utilisé pour faire toutes sortes de choses incroyablement utiles.
Mesurer le temps nécessaire à l'exécution d'une fonction transmise. Avec une fonction, le paramètre serait évalué avant que le contrôle ne soit transmis à la fonction. Avec la macro, vous pouvez épisser votre code entre le début et la fin de votre chronomètre. Ce qui suit a exactement le même code dans une macro et une fonction et la sortie est très différente. Remarque: Il s'agit d'un exemple artificiel et l'implémentation a été choisie de sorte qu'elle soit identique pour mieux mettre en évidence la différence.
la source
Je l'ai obtenu dans le livre de cuisine Common Lisp, mais je pense que cela explique pourquoi les macros Lisp sont bonnes d'une manière agréable.
"Une macro est un morceau de code Lisp ordinaire qui opère sur un autre morceau de code Lisp putatif, le traduisant en (une version plus proche de) Lisp exécutable. Cela peut sembler un peu compliqué, alors donnons un exemple simple. Supposons que vous vouliez un version de setq qui définit deux variables sur la même valeur. Donc, si vous écrivez
lorsque
z=8
x et y sont réglés sur 11. (Je ne pense à aucune utilité pour cela, mais ce n'est qu'un exemple.)Il devrait être évident que nous ne pouvons pas définir setq2 comme une fonction. Si
x=50
ety=-5
, cette fonction recevrait les valeurs 50, -5 et 11; il n'aurait aucune connaissance des variables qui devaient être définies. Ce que nous voulons vraiment dire, c'est quand vous (le système Lisp) voyez(setq2 v1 v2 e)
, traitez-le comme équivalent à(progn (setq v1 e) (setq v2 e))
. En fait, ce n'est pas tout à fait vrai, mais cela suffira pour l'instant. Une macro nous permet de faire précisément cela, en spécifiant un programme pour transformer le modèle d'entrée(setq2 v1 v2 e)
"en modèle de sortie(progn ...)
".Si vous pensiez que c'était bien, vous pouvez continuer à lire ici: http://cl-cookbook.sourceforge.net/macros.html
la source
setq2
comme fonction six
ety
sont passés par référence. Je ne sais pas si c'est possible en CL, cependant. Donc, pour quelqu'un qui ne connaît pas Lisps ou CL en particulier, ce n'est pas un exemple très illustratif IMOEn python, vous avez des décorateurs, vous avez essentiellement une fonction qui prend une autre fonction en entrée. Vous pouvez faire ce que vous voulez: appeler la fonction, faire autre chose, encapsuler l'appel de fonction dans une version d'acquisition de ressources, etc. mais vous ne pouvez pas jeter un œil à l'intérieur de cette fonction. Supposons que nous voulions la rendre plus puissante, disons que votre décorateur a reçu le code de la fonction sous forme de liste, vous pouvez non seulement exécuter la fonction telle quelle, mais vous pouvez maintenant en exécuter des parties, réorganiser les lignes de la fonction, etc.
la source