Quote, Backquote und eval
Code ist eine Liste. Mit Quote hältst du ihn als Daten, mit eval führst du ihn aus:
(let ((code '(+ 1 2 3)))
(format t "~a~%" code)
(format t "~a~%" (first code))
(format t "~a~%" (eval code))
(setf (first code) '*)
(format t "~a = ~a~%" code (eval code)))
(let ((x 10) (name 'y))
(format t "~a~%" `(wert ,x))
(format t "~a~%" `(summe ,@(list 1 2 3) ende))
(format t "~a~%" `(,name ist ,(* x 2))))Ausgabe
(+ 1 2 3) + 6 (* 1 2 3) = 6 (WERT 10) (SUMME 1 2 3 ENDE) (Y IST 20)
Das Backquote ` ` baut Listen als Vorlage: ,x setzt einen Wert ein, ,@liste` fügt Elemente einzeln ein.
Eigene Makros
Ein Makro ist eine Funktion, die Code erzeugt. Sie läuft vor der Auswertung und ersetzt den Aufruf durch das Ergebnis:
(defmacro mein-unless (bedingung &body rumpf)
`(if ,bedingung nil (progn ,@rumpf)))
(defmacro wiederhole (n &body rumpf)
`(dotimes (,(gensym) ,n) ,@rumpf))
(defmacro mit-zeit-ausgabe (name &body rumpf)
`(progn
(format t "Start ~a~%" ,name)
(prog1 (progn ,@rumpf)
(format t "Ende ~a~%" ,name))))
(defmacro tausche (a b)
(let ((tmp (gensym)))
`(let ((,tmp ,a))
(setf ,a ,b)
(setf ,b ,tmp))))
(format t "~a~%" (mein-unless nil 1 2 3))
(wiederhole 2 (format t "x "))
(terpri)
(format t "~a~%" (mit-zeit-ausgabe "Rechnung" (+ 40 2)))
(let ((p 1) (q 2))
(tausche p q)
(format t "~a ~a~%" p q))
(format t "~a~%" (macroexpand-1 '(mein-unless (> 1 2) :a :b)))Ausgabe
3
x x
Start Rechnung
Ende Rechnung
42
2 1
(IF (> 1 2)
NIL
(PROGN A B))Eine kleine Fachsprache bauen
Makros erlauben, die Sprache an das Problem anzupassen:
(defmacro definiere-tests (&rest faelle)
`(progn
,@(loop for (name ausdruck erwartet) in faelle
collect `(format t "~a: ~a~%" ',name
(if (equal ,ausdruck ,erwartet) "ok" "FEHLER")))))
(definiere-tests
(addition (+ 1 2) 3)
(liste (reverse '(1 2)) '(2 1))
(falsch (* 2 2) 5))
(defmacro fuer-jedes ((var liste) &body rumpf)
`(mapc (lambda (,var) ,@rumpf) ,liste))
(fuer-jedes (x '(1 2 3))
(format t "~a " (* x x)))
(terpri)Ausgabe
ADDITION: ok LISTE: ok FALSCH: FEHLER 1 4 9
Closures
(defun make-zaehler ()
(let ((n 0))
(lambda () (incf n))))
(let ((a (make-zaehler)) (b (make-zaehler)))
(funcall a) (funcall a)
(funcall b)
(format t "~a ~a~%" (funcall a) (funcall b)))
(defun memoize (f)
(let ((cache (make-hash-table :test 'equal)))
(lambda (&rest args)
(multiple-value-bind (wert gefunden) (gethash args cache)
(if gefunden wert
(setf (gethash args cache) (apply f args)))))))
(let ((langsam-quadrat (lambda (x) (format t "rechne ~a~%" x) (* x x))))
(let ((schnell (memoize langsam-quadrat)))
(format t "~a~%" (funcall schnell 4))
(format t "~a~%" (funcall schnell 4))))Ausgabe
3 2 rechne 4 16 16
Merke
- Code ist Daten: Quote (
'), Backquote (``),,xund,@liste` defmacroerzeugt Code zur Übersetzungszeit;macroexpand-1zeigt das Ergebnisgensymverhindert Namenskonflikte- Closures kapseln Zustand; so entstehen Zähler und Memoization
Aufgabe
Schreibe ein Makro mit-wiederholung, das einen Rumpf n-mal ausführt und die Nummer liefert.