image: clisp-logo.png

Common Lisp (1984)

John McCarthy

Lisp, ent­wick­elt 1958 von John McCarthy am Mas­sa­chu­setts In­sti­tute of Tech­no­logy (MIT), ist eine der ältesten Pro­gram­mier­spra­chen und basiert auf der rekursiven Funk­tions­theo­rie. Lisp wur­de vor allem in der Künstlichen In­tel­li­genz (KI) ein­ge­setzt, mit be­kann­ten An­wen­dun­gen wie dem Ex­per­ten­sys­tem DENDRAL und dem KI-Frame­work Cyc. Heutzutage ist Lisp immer noch relevant, insbesondere in der Forschung und in spe­zia­li­sier­ten Be­rei­chen der Soft­ware­ent­wick­lung. Dialekte wie Common Lisp und Scheme werden weiterhin für ihre Flexi­bi­li­tät und mächtigen Makrosysteme geschätzt, die es ermöglichen, komplexe Pro­gramme effizient zu entwickeln und zu erweitern.

1. Hello, world!

Lisp ist eine Skript­spra­che, was bedeutet, dass der Code zur Laufzeit interpretiert wird. Ein moderner Lisp-Interpreter ist »SBCL« (»Steel Bank Common Lisp«), den wir in diesem Tutorial verwenden. Du hast damit zwei Möglichkeiten, Lisp-Code auszuführen:

  1. Du kannst Lisp-Code direkt in der SBCL-Shell ausführen.
  2. Du kannst Lisp-Code in einer Textdatei speichern und dann ausführen.

Möglichkeit 1: Lisp-Code in der SBCL-Shell ausführen

Öffne dazu ein Ter­mi­nal, indem du entweder StrgJ drückst oder das Panel-Symbol rechts oben drückst. Dein Fenster sollte jetzt ungefähr so aussehen:

Starte nun die SBCL-Shell, indem du sbcl eingibst und dann Enter drückst. Du solltest eine Ausgabe wie diese sehen:

Jetzt kannst du Lisp-Code direkt in der Shell eingeben und ausführen. Schreibe einfach (format t "Hello, world!") und drücke Enter. Du solltest die Ausgabe Hello, world! sehen.

Du kannst die SBCL-Shell wieder beenden, indem du (exit) eingibst und Enter drückst oder einfach StrgD drückst.

Möglichkeit 2: Lisp-Code in einer Textdatei speichern und ausführen

Lisp-Pro­gramme werden in Textdateien mit der Endung .lisp geschrieben. Ein Lisp-Interpreter liest anschließend den Quelltext und führt ihn aus.

Stelle zuerst sicher, dass du keinen Ordner geöffnet hast. Um sicherzugehen, drücke einfach den Shortcut für »Ordner schließen«: StrgK und dann F. Dein Work­space sollte jetzt ungefähr so aussehen:

Quelltext schreiben

Klicke auf »New File« und wähle als Dateityp »Text File« (oder bestätige einfach mit Enter).

Schreibe nun den folgenden Code in die Datei:

1
(format t "Hello, World!~%")

Da Visual Studio Code noch nicht weiß, dass es sich um Lisp-Quelltext handelt, ist dein Programm momentan noch einfarbig, aber das wird sich gleich ändern. An dem weißen Punkt erkennst du, dass deine Änderungen noch nicht gespeichert sind.

Drücke nun StrgS, um die Datei zu speichern. Gib hello.lisp ein – der vollständige Pfad zu deiner Datei lautet dann /workspace/hello.lisp.

Da Lisp standardmäßig nicht von Visual Studio Code un­ter­stützt wird, müssen wir noch eine passende Er­wei­te­rung installieren. Klicke dazu auf das Er­wei­te­rungs-Symbol in der Seitenleiste oder drücke StrgShiftX. Suche nach der Er­wei­te­rung »Lisp-Syntax« und installiere sie.

Alternativ kannst du auch StrgP drücken und ext install slbtty.lisp-syntax eingeben, um die Er­wei­te­rung zu installieren.

Anschließend solltest du dein Lisp-Programm farbig sehen:

Skript ausführen

Um unser Programm auszuführen, müssen wir den Lisp-Interpreter aufrufen (in unserem Fall sbcl) und ihm den Da­tei­na­men unseres Programms übergeben.

Öffne dazu ein Ter­mi­nal, indem du StrgJ drückst und gib folgenden Befehl ein:

sbcl --script hello.lisp
Du musst nicht den vollständigen Da­tei­na­men schreiben. Schreib einfach sbcl --script he und drücke Tab, um den Da­tei­na­men automatisch zu hello.lisp vervollständigen zu lassen. Du kannst danach ganz normal weiterschreiben.

Das Programm sollte die Nachricht Hello, World! im Ter­mi­nal ausgeben:

Fehler finden und beheben

Wenn du einen Fehler im Code machst, wird Lisp eine Fehlermeldung ausgeben. Versuche zum Beispiel, statt format das Wort formt zu schreiben:

(formt t "Hello, World!~%"))

Speichere die Datei und führe das Skript erneut aus:

sbcl --script hello.lisp
Nutze die Pfeiltaste hoch , um den letzten Befehl erneut einzugeben. So kannst du schnell dein Programm testen, nachdem du es verändert hast.

SBCL sollte eine Fehlermeldung ausgeben, die dir hilft, den Fehler zu finden (du musst etwas nach oben scrollen, um den Auslöser zu finden):

Es lohnt sich, die Fehlermeldungen genau zu lesen, um den Fehler zu finden und zu beheben. Leider gibt SBCL im Gegensatz zu vielen anderen Interpretern keine Zeilennummer an, aber die Meldung sollte dir trotzdem helfen, den Fehler zu finden. Denke daran, den Fehler wieder zu beheben, bevor du das nächste Beispiel ausprobierst.

2. Primfaktorzerlegung

Im zweiten Beispiel wollen wir eine Zahl in ihre Primfaktoren zerlegen. An diesem Beispiel kannst du sehen, wie man in Lisp Be­nut­zer­ein­ga­ben verar­bei­tet und Schleifen ver­wen­det. Erstelle eine neue Datei mit StrgAltN und schreibe den folgenden Code hinein:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(defun prime-factors (number &optional (return-values '()))
  ;; function declaration has an optional return-values parameter for
  ;; tail-recursive use
  (when (> number 0)
    ;; no prime factors for negative numbers
    (loop with max-divisor = (isqrt number)
          ;; loop through possible divisors
          ;; stop at divisor = square root of number
          for divisor = 2
            then (if (evenp divisor)
                     ;; if divisor is even, increment
                     ;; if not, add two
                     (1+ divisor) (+ divisor 2))
          do (cond ((> divisor max-divisor)
                    ;; have we checked all possible divisors but none fit?
                    ;; number is prime => return with any return-values
                    (return (cons (list number 1) return-values)))
                   ((zerop (rem number divisor))
                    ;; is number evenly divisible by divisor?
                    ;; we have found a factor of the number.
                    ;; recursively call prime-factors
                    (return
                      (prime-factors
                       ;; use number/divisor as new number
                       (truncate number divisor)
                       ;; add factor to return-values list (or increment
                       ;; counter if more sensible)
                       (if (eq divisor (caar return-values))
                           (cons 
                            (list (caar return-values) (1+ (cadar return-values)))
                            (cdr return-values))
                           (cons (list divisor 1) return-values)))))))))

(format T "Please enter the number to factorise: ~%")
(format T "Prime factors: ~d~%" (prime-factors (read)))

Speichere die Datei unter dem Namen factor.lisp und führe sie aus:

Das Programm hat die Zahl 123 in ihre Primfaktoren zerlegt und ausgegeben. Anders als andere Pro­gram­mier­spra­chen kann Lisp auch die Zahl 3000000000 in Sekundenbruchteilen zerlegen. Wenn du allerdings eine sehr große Zahl wie 123456789123456789 verwendest, dauert die Berechnung sehr lange (probier es gern aus, du kannst das Programm mit StrgC abbrechen).

3. Bubblesort

Im dritten Beispiel wollen wir eine Liste von 10 Zufallszahlen sortieren. Dafür verwenden wir den Bubblesort-Al­go­rith­mus, der zwar nicht besonders effizient ist, aber sehr einfach zu verstehen und zu implementieren. Der Bubblesort-Al­go­rith­mus funktioniert, indem er die Liste mehrmals durchläuft und benachbarte Elemente vertauscht, wenn sie in der falschen Reihenfolge sind.

An diesem Beispiel kannst du sehen, wie man in Lisp Arrays ver­wen­det, Funk­tio­nen ver­wen­det und Schleifen verschachtelt.

Erstelle eine neue Datei und schreibe den folgenden Code hinein:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(defun bubble-sort (sequence &optional (compare #'<))
  "sort a sequence (array or list) in place with an optional comparison
function (cl:< is the default)"
  (loop with sorted = nil until sorted do
    (setf sorted t)
    ;; assume the list is sorted before checking it
    (loop for a below (1- (length sequence)) do
      ;; loop through the sequence
      (unless (funcall compare
                       ;; use 'compare' function to check seq[a] vs. seq[a+1]
                       (elt sequence a)
                       (elt sequence (1+ a)))
        ;; if 'compare' function is unfulfilled, rotate [a] and [a+1]
        (rotatef (elt sequence a)
                 (elt sequence (1+ a)))
        ;; and of course this means the list is not yet sorted
        (setf sorted nil)))))

(let ((list
        ;; create a local scope in which 'list' is bound to the following
        ;; definition
        (mapcar #'random
                ;; map the function 'random' to our ten-element list
                ;; this creates random values between 0 and 100
                (make-list 10 :initial-element 100))))
  (format t "Original list: ~a~%" list)
  (bubble-sort list)
  ;; list is now sorted
  (format t "Sorted list: ~a~%" list))

Speichere das Skript unter dem Namen bubblesort.lisp und führe es aus:

Das Programm hat eine Liste von 10 Zufallszahlen sortiert. Versuche, den Quelltext so zu verändern, dass statt 10 Zahlen 100 oder mehr Zahlen sortiert werden.

4. Zusammenfassung

In diesem Kapitel hast du an drei Beispielen gesehen, wie man ein einfaches Lisp-Skript schreiben und ausführen kann. Das ist natürlich nur ein erster Eindruck. Um Lisp wirklich zu beherrschen, musst du noch viel mehr lernen – am besten, indem du eigene Skripte schreibst und ausprobierst. Die Buchhandlungen, Bib­lio­theken und Youtube sind voll von Material für dich. Viel Spaß beim Pro­gram­mier­en!