330 votes

Ce qui rend les macros Lisp si spéciales

En lisant les essais de Paul Graham sur les langages de programmation, on pourrait penser que les macros Lisp sont la seule solution. En tant que développeur occupé travaillant sur d'autres plateformes, je n'ai pas eu le privilège d'utiliser des macros Lisp. Comme quelqu'un qui veut comprendre le buzz s'il vous plaît expliquer ce qui rend cette fonctionnalité si puissante.

S'il vous plaît aussi relier cela à quelque chose que je comprendrais du monde de python, java, c #, c développement.

346voto

gte525u Points 1431

La réponse courte: les macros sont utilisées pour la définition de la syntaxe du langage d'extension pour Common Lisp ou Spécifique au Domaine des Langues (Dsl). Ces langues sont incorporés à l'intérieur de votre existant du code Lisp. Maintenant, les DSLs peut avoir une syntaxe semblable à Lisp (comme Peter Norvig du Prologue Interprète pour Common Lisp) ou complètement différente (par exemple, la Notation Infixe Mathématiques pour Clojure).

Comme un exemple plus concret: Python a des interprétations de la liste intégré dans la langue. Cela donne une syntaxe simple pour un cas courant divisibleByTwo = [x for x in range(10) if x % 2 == 0] donne les nombres de 0 à 9 qui sont divisibles par deux. De retour dans le Python 1.5 jours, il n'y avait pas une telle syntaxe, et qui aurait exprimé comme suit:

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

Ce sont à la fois fonctionnellement équivalent. Nous allons appeler notre suspension de l'incrédulité et de faire semblant de Lisp est très limitée boucle macro qui ne fonctionne tout simplement itération et pas de moyen facile de faire l'équivalent d'interprétations de la liste.

En Lisp, vous pouvez écrire le suivant. Je note cet exemple artificiel est repris à l'identique du code Python pas 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 expliquer un peu de ce qu'une macro est. C'est une transformation de code par le code de code. C'est un morceau de code lu par l'interprète (ou le compilateur) qui prend dans le code comme un argument n'est qu'une manipulation et renvoie le résultat qui est ensuite exécuté en place.

Bien sûr, c'est beaucoup de saisie et de programmeurs sont des paresseux. Donc, nous pourrions définir DSL pour faire des interprétations de la liste. En fait, nous avons déjà à l'aide d'une macro déjà (la boucle de macro).

Lisp définit un couple de syntaxe spéciale formes. L'apostrophe (') indique le prochain jeton est un littéral. Le quasiquote (`) indique le prochain jeton est un littéral avec échappe. Les fuites 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'affecter à une autre variable ou de l'utiliser à la place. Vous pouvez penser `(1 2 ,x) comme l'équivalent de Python [1, 2, x] où x est une variable définie précédemment. Cette liste notation est une partie de la magie qui va dans les macros. La deuxième partie est le Lisp lecteur qui intelligemment subtitutes macros pour le code, mais c'est mieux illustré ci-dessous:

Nous pouvons donc définir une macro appelée lcomp (court pour la compréhension de liste). C'est la 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
            ;; conditioanl-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 la ligne de commande:

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

Très soigné, hein? Maintenant, il ne s'arrête pas là. Vous disposez d'un mécanisme, d'un pinceau. Vous pouvez avoir toute la syntaxe que vous pourriez probablement vouloir. Comme Python ou C#'s avec la syntaxe. Ou .NET de la syntaxe LINQ. En fin de compte, c'est ce qui attire les gens à Lisp - flexibilité ultime.

109voto

VonC Points 414372

Vous trouverez un vaste débat autour de lisp macro ici.

Un sous-ensemble intéressant de cet article:

Dans la plupart des langages de programmation, la syntaxe est complexe. Les Macros ont à prendre part au programme de la syntaxe, de l'analyser et de le remonter. Ils n'ont pas accès au programme de l'analyseur, donc ils doivent compter sur des heuristiques et des meilleures estimations. Parfois leur coupe-analyse du taux qui est mal, et puis ils se cassent.

Mais Lisp est différent. Lisp macros n' ont accès à l'analyseur, et c'est vraiment un simple analyseur. Une macro Lisp n'est pas remis d'une chaîne, mais un preparsed morceau de code source sous la forme d'une liste, parce que la source d'un programme Lisp n'est pas une chaîne de caractères; c'est une liste. Et de programmes Lisp sont vraiment bons à prendre en dehors des listes et de les remettre ensemble. Ils le font de manière fiable, tous les jours.

Ici est un exemple. Lisp a une macro, appelé "setf", qui effectue la mission. La forme la plus simple de setf est

  (setf x whatever)

qui fixe la valeur du symbole "x" à la valeur de l'expression "quel que soit".

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

Maintenant, si vous souhaitez remplacer le premier élément d'une liste avec une nouvelle valeur? Il n'y est une fonction standard pour le faire, et incroyablement, son nom est encore pire que "voiture". Il est "rplaca". Mais vous n'avez pas à mémoriser "rplaca", parce que 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 est de la forme (voiture quelque CHOSE). Il dit de lui-même "Oh, le programmeur est d'essayer de mettre la voiture de quelque chose. La fonction à utiliser est "rplaca'." Et tranquillement il réécrit le code à la place:

  (rplaca somelist whatever)

58voto

Vatine Points 8884

Common Lisp macros essentiellement étendre la "syntaxique primitives" de votre code.

Par exemple, en C, le switch/case construire fonctionne uniquement avec les types intégraux et si vous désirez l'utiliser pour des flotteurs ou des chaînes, vous êtes de gauche avec des instructions if imbriquées et comparaisons explicites. Il n'y a également aucun moyen que vous pouvez écrire un C macro pour faire le travail pour vous.

Mais, depuis une macro lisp est (essentiellement) un programme lisp qui prend des bouts de code en entrée et renvoie un code de remplacer "l'invocation" de la macro, vous pouvez étendre votre "primitives" des oeuvres aussi loin que vous le voulez, généralement de se retrouver avec un plus lisible programme.

Pour faire la même chose en C, vous devez écrire une coutume pré-processeur qui mange votre initiale (pas-assez-C) la source et crache quelque chose qu'un compilateur C peut comprendre. Ce n'est pas une mauvaise façon de s'y prendre, mais ce n'est pas forcément le plus facile.

47voto

dsm Points 7429

Lisp macros vous permettent de décider quand (si) toute partie ou l'expression sera évaluée. Pour mettre un exemple simple, pensez à:

expr1 && expr2 && expr3 ...

Cela nous dit: Évaluer l' expr1, et, faut-il vrai, d'évaluer expr2, etc.

Maintenant, essayez de faire de cette && dans une fonction... c'est vrai, vous ne pouvez pas. Appeler quelque chose comme:

and(expr1, expr2, expr3)

Évalue tous les trois exprs avant de céder une réponse, peu importe si expr1 était faux!

Avec lisp macros, vous pouvez coder quelque chose comme:

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

maintenant, vous avez un &&, que vous pouvez appeler comme une fonction et il n'évalue pas tous les formulaires que vous transmettez à moins qu'ils sont tous vrais.

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 (consulter l' (loop ...) macro pour un exemple), l'intégration d'autres langues, en lisp, par exemple, vous pourriez écrire une macro qui permet de dire quelque chose comme:

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

Et c'est même pas entrer dans le Lecteur de macros.

Espérons que cette aide.

33voto

Rayne Points 14518

Je ne pense pas que je n’ai jamais vu les macros de Lisp a expliqué mieux que par cet homme : http://www.defmacro.org/ramblings/lisp.html

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X