Um es kurz zu machen: Makros werden zur Definition von Sprachsyntax-Erweiterungen für Common Lisp oder Domain Specific Languages (DSLs) verwendet. Diese Sprachen werden direkt in den bestehenden Lisp-Code eingebettet. Nun können die DSLs eine Lisp-ähnliche Syntax haben (wie Peter Norvig's Prolog-Interpreter für Common Lisp) oder völlig anders (z. B. Infix-Notation Mathematik für Clojure).
Hier ist ein konkreteres Beispiel:
Python verfügt über eine in die Sprache integrierte Listenverarbeitung. Dies gibt eine einfache Syntax für einen häufigen Fall. Die Zeile
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
ergibt eine Liste mit allen geraden Zahlen zwischen 0 und 9. Zurück in der Python 1.5 gab es keine solche Syntax; man verwendete eher so etwas wie diese:
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
Beide sind funktional gleichwertig. Nehmen wir an, Lisp habe ein sehr begrenztes Schleifenmakro, das nur Iterationen durchführt, und keine einfache Möglichkeit, das Äquivalent von Listenauflösungen zu realisieren.
In Lisp könnte man das Folgende schreiben. Ich sollte anmerken, dass dieses erfundene Beispiel so gewählt wurde, dass es mit dem Python-Code identisch ist und kein gutes Beispiel für Lisp-Code darstellt.
;; 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))))
Bevor ich fortfahre, sollte ich besser erklären, was ein Makro ist. Es ist eine Transformation, die am Code durchgeführt wird von Code. Das heißt, ein vom Interpreter (oder Compiler) gelesenes Codestück, das Code als Argument aufnimmt, bearbeitet und das Ergebnis zurückgibt, das dann an Ort und Stelle ausgeführt wird.
Das ist natürlich eine Menge Tipparbeit und Programmierer sind faul. Also könnten wir eine DSL für Listenauflösungen definieren. Tatsächlich verwenden wir bereits ein Makro (das Schleifenmakro).
Lisp definiert eine Reihe von speziellen Syntaxformen. Das Anführungszeichen ( '
) zeigt an, dass das nächste Token ein Literal ist. Die Quasiquote oder der Backtick ( `
) zeigt an, dass das nächste Token ein Literal mit Escapes ist. Escape-Zeichen werden durch den Komma-Operator angezeigt. Das Literal '(1 2 3)
ist das Äquivalent zu Pythons [1, 2, 3]
. Sie können sie einer anderen Variablen zuweisen oder sie an ihrer Stelle verwenden. Sie können sich vorstellen `(1 2 ,x)
als das Äquivalent zu Pythons [1, 2, x]
donde x
ist eine zuvor definierte Variable. Diese Listennotation ist Teil der Magie, die in Makros steckt. Der zweite Teil ist der Lisp-Reader, der auf intelligente Weise Makros durch Code ersetzt, aber das wird am besten weiter unten erläutert:
Wir können also ein Makro definieren, das lcomp
(kurz für Listenverständnis). Die Syntax entspricht genau dem Python, das wir im Beispiel verwendet haben [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)))
Jetzt können wir es in der Befehlszeile ausführen:
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
Ziemlich toll, oder? Aber das ist noch nicht alles. Sie haben einen Mechanismus, oder einen Pinsel, wenn Sie so wollen. Sie können jede Syntax verwenden, die Sie sich vorstellen können. Wie Python oder C#s with
Syntax. Oder die LINQ-Syntax von .NET. Letztendlich ist es das, was die Leute an Lisp reizt - ultimative Flexibilität.