Kurz: Code ist eine Liste. Mit Quote hältst du ihn als Daten, mit eval führst du ihn aus:
Teil des Kurses Common Lisp
Kapitel 4 von 8 im Kurs Common Lisp. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.
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))Achtung
Nutze
gensymfür Hilfsvariablen im Makro, damit sie nicht mit Variablen des Aufrufers kollidieren (Variable Capture). Verwende ein Makro nur, wenn eine Funktion nicht ausreicht.
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 9Closures
(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
16Merke
- 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
Übungsaufgabe
Schreibe ein Makro mit-wiederholung, das einen Rumpf n-mal ausführt und die Nummer liefert.
Quiz zur Selbstkontrolle
Was ist ein Makro?
- Code, der Code erzeugt, bevor er ausgewertet wird (richtig)
- Eine Schleife
- Eine Datei
- Ein Kommentar
Wofür steht ,@ im Backquote?
- Fügt die Elemente einer Liste einzeln ein (richtig)
- Kommentiert aus
- Addiert
- Löscht
Wozu dient gensym?
- Erzeugt einen eindeutigen Symbolnamen (richtig)
- Erzeugt Zufallszahlen
- Generiert Klassen
- Sortiert
Weiter im Kurs
Zurück: Listen, Sequenzen und Funktionen höherer Ordnung
Weiter: Strukturen, Klassen und CLOS
Alle Kapitel: Common Lisp im Überblick