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]
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))
(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 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.