Qu'est-ce qui rend les macros Lisp si spéciales?

297

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.

mentholé
la source
2
Soit dit en passant, il existe un processeur de macro de type LISP pour C # appelé LeMP: ecsharp.net/lemp ... JavaScript en a également un appelé Sweet.js: sweetjs.org
Qwertie
@Qwertie Est-ce que sweetjs fonctionne même de nos jours?
fredrik.hjarner
Je ne l'ai pas utilisé mais le dernier commit date d'il y a six mois ... assez bien pour moi!
Qwertie

Réponses:

308

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

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

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:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

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.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

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]xest 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))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Maintenant, nous pouvons exécuter sur la ligne de commande:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

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 withsyntaxe 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.

gte525u
la source
51
+1 pour implémenter la compréhension de liste en Lisp, pourquoi pas?
ckb
9
En fait @ckb LISP a déjà une macro de compréhension de la liste dans la bibliothèque standard: (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 )
Suzanne Dupéron
8
Je sais que c'est assez indépendant, mais je me pose des questions sur la syntaxe et comment fonctionne réellement l'analyse ... Disons que j'appelle lcomp de cette façon (en changeant l'élément de "for" en "azertyuiop"): (lcomp x azertyuiop x in ( plage 10) si (= (% x 2) 0)) la macro fonctionnera-t-elle toujours comme prévu? Ou le paramètre "for" est-il utilisé dans la boucle de sorte qu'il doit s'agir de la chaîne "for" lors de l'appel?
Dader
2
@dader Ainsi, la boucle est en fait une autre macro et for est un symbole spécial utilisé dans cette macro. J'aurais pu facilement définir la macro sans la macro de boucle. Cependant, si je l'avais fait, le message aurait été beaucoup plus long. La macro de boucle et toute sa syntaxe est définie en CLtL .
gte525u
3
Une chose qui me confond avec les macros d'autres langues, c'est que leurs macros sont limitées par la syntaxe du langage hôte. Les macros Lispy peuvent-elles interpréter la syntaxe non Lispy. Je veux dire, imaginez créer une syntaxe de type haskell (pas de paranthèse) et l'interpréter en utilisant des macros Lisp. Est-ce possible et quels sont les avantages / inconvénients de l'utilisation de macros par rapport à l'utilisation directe d'un lexer et d'un analyseur?
CMCDragonkai
105

Vous trouverez ici un débat complet sur la macro lisp .

Un sous-ensemble intéressant de cet article:

Dans la plupart des langages de programmation, la syntaxe est complexe. Les macros doivent démonter la syntaxe du programme, l'analyser et la réassembler. Ils n'ont pas accès à l'analyseur du programme, ils doivent donc dépendre de l'heuristique et des meilleures hypothèses. Parfois, leur analyse du taux de coupure est erronée, puis ils cassent.

Mais Lisp est différent. Macros Lisp n'ont accès à l'analyseur, et il est un analyseur très simple. Une macro Lisp ne reçoit pas une chaîne, mais un morceau de code source pré-préparé sous la forme d'une liste, car la source d'un programme Lisp n'est pas une chaîne; c'est une liste. Et les programmes Lisp sont vraiment bons pour démonter des listes et les remonter. Ils le font de manière fiable, tous les jours.

Voici un exemple étendu. Lisp a une macro, appelée "setf", qui effectue l'affectation. La forme la plus simple de setf est

  (setf x whatever)

qui définit la valeur du symbole "x" sur la valeur de l'expression "que ce soit".

Lisp a également des listes; vous pouvez utiliser les fonctions "car" et "cdr" pour obtenir le premier élément d'une liste ou le reste de la liste, respectivement.

Maintenant, que se passe-t-il si vous souhaitez remplacer le premier élément d'une liste par une nouvelle valeur? Il existe une fonction standard pour ce faire, et incroyablement, son nom est encore pire que "voiture". C'est "rplaca". Mais vous n'avez pas à vous souvenir de "rplaca", car vous pouvez écrire

  (setf (car somelist) whatever)

pour régler la voiture de somelist.

Ce qui se passe vraiment ici, c'est que "setf" est une macro. Au moment de la compilation, il examine ses arguments, et il voit que le premier a la forme (voiture QUELQUE CHOSE). Il se dit "Oh, le programmeur essaie de régler la voiture de quelque chose. La fonction à utiliser pour cela est" rplaca "." Et il réécrit tranquillement le code en place pour:

  (rplaca somelist whatever)
VonC
la source
5
setf est une belle illustration de la puissance des macros, merci de l'inclure.
Joel
J'aime le fait saillant .. parce que la source d'un programme Lisp n'est pas une chaîne; c'est une liste. ! Est-ce la principale raison pour laquelle la macro LISP est supérieure à la plupart des autres en raison de ses parenthèses?
Étudiant
@Étudiant, je suppose que oui: books.google.fr/… suggère que vous avez raison.
VonC
55

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.

Vatine
la source
41

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:

expr1 && expr2 && expr3 ...

Ce que cela dit, c'est: évaluer expr1, et, si c'est vrai, évaluer expr2, etc.

Essayez maintenant d'en faire &&une fonction ... c'est vrai, vous ne pouvez pas. Appeler quelque chose comme:

and(expr1, expr2, expr3)

Évaluera les trois exprsavant de donner une réponse, que ce expr1soit faux ou non!

Avec les macros lisp, vous pouvez coder quelque chose comme:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

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:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

et:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

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:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

Et ce n'est même pas entrer dans les macros Reader .

J'espère que cela t'aides.

dsm
la source
Je pense que cela devrait être: "(appliquez &&, @ exprs); cela pourrait être faux!"
Svante
1
@svante - sur deux points: premièrement, && est une macro, pas une fonction; appliquer uniquement des travaux sur les fonctions. deuxièmement, appliquer prendre une liste d'arguments à passer, vous voulez donc l'un des "(funcall fn, @ exprs)", "(appliquer fn (liste, @ exprs)" ou "(appliquer fn, @ exprs nil)", pas "(appliquer fn, @ exprs)".
Aaron
(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.
ocodo
31

Je ne pense pas avoir jamais vu de macros Lisp mieux expliquées que par cet homme: http://www.defmacro.org/ramblings/lisp.html

Rayne
la source
3
Surtout si vous avez une expérience Java / XML.
sunsations
1
Quelle joie de lire ce mensonge sur mon canapé un samedi après-midi! Très clairement écrit et organisé.
Colin
10

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.

  • Une syntaxe de macro / modèle limitée restreint leur utilisation. Par exemple, vous ne pouvez pas écrire un modèle qui se développe en autre chose qu'une classe ou une fonction. Les macros et les modèles ne peuvent pas facilement conserver les données internes.
  • La syntaxe complexe et très irrégulière de C et C ++ rend difficile l'écriture de macros très générales.

Les macros Lisp et Lisp résolvent ces problèmes.

  • Les macros Lisp sont écrites en Lisp. Vous avez toute la puissance de Lisp pour écrire la macro.
  • Lisp a une syntaxe très régulière.

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!

Matt Curtis
la source
11
Eh bien, pour être juste, le problème avec la métaprogrammation de modèle C ++ n'est pas que le langage de métaprogrammation est différent , mais qu'il est horrible - il n'a pas été conçu autant que découvert dans ce qui était censé être une fonctionnalité de modèle beaucoup plus simple.
Brooks Moses
@Brooks Bien sûr. Les fonctionnalités émergentes ne sont pas toujours mauvaises. Malheureusement, dans un langage lent dirigé par les comités, il est difficile de les corriger lorsqu'ils le sont. C'est dommage tant de nouvelles fonctionnalités modernes et utiles de C ++ sont écrites dans un langage que peu peuvent même lire, et il y a un énorme fossé entre le programmeur moyen et le "grand prêtre".
Matt Curtis
2
@downvoter: s'il y a un problème avec ma réponse, veuillez laisser un commentaire afin que nous puissions tous partager les connaissances.
Matt Curtis
10

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 eachclause using-statement, les expressions linq selectet 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 usingpourrait ressembler une macro de style lisp en C # pour l'implémentation :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }
JacquesB
la source
Maintenant qu'il ya effectivement est un processeur macro style Lisp pour C #, je voudrais signaler qu'une macro pour usingse ressembler à ceci ;)
Qwertie
9

É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:

... en C, il faudrait écrire un pré-processeur personnalisé [qui serait probablement considéré comme un programme C suffisamment compliqué ] ...

- Vatine

Parlez à tous ceux qui maîtrisent le C ++ et demandez-leur combien de temps ils ont passé à apprendre tout le fudgery de modèles dont ils ont besoin pour faire de la métaprogrammation de modèles [qui n'est toujours pas aussi puissant].

- Matt Curtis

... en Java, vous devez pirater votre chemin avec le tissage de bytecode, bien que certains frameworks comme AspectJ vous permettent de le faire en utilisant une approche différente, c'est fondamentalement un hack.

- Miguel Ping

DOLIST est similaire à forl de Perl ou à for de Python. Java a ajouté un type similaire de construction de boucle avec la boucle "améliorée" pour Java 1.5, dans le cadre de JSR-201. Remarquez la différence des macros. Un programmeur Lisp qui remarque un modèle commun dans son code peut écrire une macro pour se donner une abstraction au niveau source de ce modèle. Un programmeur Java qui remarque le même modèle doit convaincre Sun que cette abstraction particulière mérite d'être ajoutée au langage. Ensuite, Sun doit publier un JSR et convoquer un "groupe d'experts" à l'échelle de l'industrie pour tout hacher. Selon Sun, ce processus prend en moyenne 18 mois. Après cela, les auteurs du compilateur doivent tous mettre à niveau leurs compilateurs pour prendre en charge la nouvelle fonctionnalité. Et même une fois que le compilateur préféré du programmeur Java prend en charge la nouvelle version de Java, ils ne peuvent probablement pas utiliser la nouvelle fonctionnalité tant qu'ils ne sont pas autorisés à rompre la compatibilité des sources avec les anciennes versions de Java. Ainsi, une gêne que les programmeurs Common Lisp peuvent résoudre par eux-mêmes en cinq minutes afflige les programmeurs Java pendant des années.

- Peter Seibel, dans "Practical Common Lisp"

ZakW
la source
8

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.

Miguel Ping
la source
1
C'est un commentaire perspicace, cependant, cette idée que "tout est une liste" peut effrayer les nouveaux arrivants. pour comprendre une liste, vous devez comprendre les inconvénients, les voitures, les CDR, les cellules. Plus précisément, Lisp est constitué de S-expressions, pas de listes.
ribamar
6

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.

dnolen
la source
1
Votre premier paragraphe est très clair pour un étranger Lisp, ce qui est important.
Wildcard
5

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

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

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.

dmitry_vk
la source
3

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.

  • Ajouter une nouvelle structure de contrôle, par exemple une boucle ou la déconstruction d'une liste
  • 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.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0
Joerg Schmuecker
la source
Btw, Scala a ajouté des macros à la langue. Bien qu'ils n'aient pas la beauté des macros Lisp parce que le langage n'est pas homoiconique, ils valent vraiment la peine d'être examinés et les arbres de syntaxe abstraite qu'ils fournissent pourraient être plus faciles à utiliser à la fin. Il est trop tôt pour moi de dire quel système macro je préfère.
Joerg Schmuecker du
2
"LISP passe les paramètres non évalués à la macro" enfin une réponse qui le dit clairement. mais vous avez oublié la deuxième moitié de cela: et le résultat d'une macro est un code transformé qui sera évalué dans son ensemble par le système à la place de l'original comme s'il était là en premier lieu (à moins qu'il ne soit lui-même à nouveau un appel à une macro, qui sera également transformée, par cette macro cette fois).
Will Ness
0

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

(setq2 x y (+ z 3))

lorsque z=8x 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=50et y=-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

nilsi
la source
1
Il est possible de définir setq2comme fonction si xet ysont 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 IMO
néoascétique
Le passage de l'argument CL @neoascetic se fait uniquement par valeur (c'est pourquoi il a besoin de macros en premier lieu). certaines valeurs sont cependant des pointeurs (comme des listes).
Will Ness
-5

En 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.

stonemetal
la source