EMZETT.
Login

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 gensym fü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 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 (`), ,x und ,@liste
  • defmacro erzeugt Code zur Übersetzungszeit; macroexpand-1 zeigt das Ergebnis
  • gensym verhindert 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

Weiter im Kurs

Zurück: Listen, Sequenzen und Funktionen höherer Ordnung

Weiter: Strukturen, Klassen und CLOS

Alle Kapitel: Common Lisp im Überblick