Common Lisp – Wikipedia

Common Lisp
Logo
Logo

Inoffizielles Lisp-Logo

Basisdaten
Paradigmen: multiparadigmatisch: funktional, prozedural, modular, objektorientiert, reflexiv
Erscheinungsjahr: 1984, 1994 für ANSI Common Lisp
Designer: Scott E. Fahlman, Richard P. Gabriel, David Moon
Entwickler: ANSI X3J13 committee
Typisierung: dynamisch
Wichtige Implementierungen: Allegro Common Lisp, Armed Bear Common Lisp, CLISP, Clozure CL, CMUCL, Embeddable Common Lisp, GNU Common Lisp, LispWorks, Movitz, Poplog, Scieneer Common Lisp, Steel Bank Common Lisp
Dialekte: CLtL1, CLtL2, ANSI Common Lisp
Beeinflusst von: Lisp Machine Lisp, Maclisp, Symbolics ZetaLisp, Scheme, Interlisp
Beeinflusste: Dylan, Eulisp, ISLisp, Ruby, SubL Clojure
common-lisp.net

Common Lisp (oft abgekürzt mit CL) ist eine Multiparadigmen-Programmiersprache innerhalb der Sprachfamilie Lisp. Sie entstand aus einer 1981 gestarteten Bemühung unter Leitung von Scott Fahlman,[1] einen standardisierten Dialekt als Nachfolger von Maclisp (und dessen Varianten)[2] zu finden und wurde 1994 als ANSI Common Lisp standardisiert.[3]

1984 erschien die erste Ausgabe des Buches Common Lisp: The Language (kurz CLTL1), welches die erste Spezifikation der Sprache enthielt. Kurz darauf wurde das ANSI-Subkomitee X3J13 gegründet, um auf Basis der Spezifikation einen Standard zu erstellen. Die zweite Ausgabe des Buches (CLTL2) von 1990 enthielt Änderungen des Subkomitees und beschreibt einen Zwischenstand. 1994 erschien die endgültige Version des Standards (ANSI/X3.226-1994).[2][4]

Common Lisp benutzt S-Expressions, um sowohl Source-Code als auch Daten darzustellen. Funktions- und Makroaufrufe werden als Listen geschrieben, die als erstes Element den Namen der Funktion bzw. des Makros enthalten. Kommentare werden mit ; eingeleitet oder mit #| |# umschlossen.

;; Addiere 2 und 2 (+ 2 2)  ;; Definiere die Variable *p* als 3,1415 (defparameter *p* 3.1415)  ;; Setze die vorher definierte Variable *p* auf 42 (setq *p* 42)  ;; Eine Funktion, die ihr Argument quadriert (defun square (x)   (* x x))  ;; Quadriere 3 (square 3) 

Common Lisp unterstützt eine Vielzahl von Datentypen, mehr als viele andere Sprachen. Diese Typen sind hierarchisch angeordnet.

Zahlen in Common Lisp sind vom Typ number. Untertypen von number sind unter anderem integer (Ganzzahlen), ratio (rationale Zahlen bzw. Brüche), real (Gleitkommazahlen) und complex (komplexe Zahlen). Arithmetik mit Ganzzahlen und Brüchen ist beliebig genau. Eine Klasse von Programmfehlern in Sprachen wie C, die durch überlaufende Ganzzahlen verursacht werden, ist in Common Lisp somit praktisch ausgeschlossen.

Der Common-Lisp-character-Typ ist nicht auf ASCII beschränkt, was nicht überrascht, da Lisp älter als ASCII ist. Viele Implementierungen unterstützen Unicode.[5]

Der Typ symbol ist ein besonderes Merkmal fast aller Lispdialekte, in anderen Sprachfamilien hingegen größtenteils unbekannt. Ein Symbol in Common Lisp ist ähnlich einem Bezeichner in anderen Sprachen, da es als Variable benutzt wird und Werte zugewiesen bekommen kann. Da sie aber Objekte erster Klasse sind, können sie auch zu anderen Zwecken benutzt werden.

Datenstrukturen

[Bearbeiten | Quelltext bearbeiten]

Common Lisp kennt eine Reihe vordefinierter Datenstrukturen. Dazu zählen unter anderem Hashtables, Datenverbünde (sogenannte structures), Klassen, Streams, Pfadnamen, mehrdimensionale Arrays und Folgen. Letztere umfassen klassische Lisp-Listen, aber auch Bitvektoren, allgemeine Vektoren und Zeichenketten.

  • Hashtables speichern Zuordnungen von Objekten. So kann zum Beispiel einer Zeichenkette eine Zahl zugewiesen werden oder einem Symbol eine Funktion.
  • Bei Datenverbünden handelt es sich um beliebig verschachtelte Strukturen, die Daten jedes beliebigen Typs vorhalten können. Ein einzelner Verbund speichert in einer jeweils festen Menge von sogenannten slots Werte, die später nach Belieben verändert und extrahiert werden können. Strukturen bilden eine einfache Vererbungshierarchie.
  • Arrays sind n-dimensionale Listen von Werten, die entweder alle denselben Typ haben müssen (in sogenannten homogenen Arrays), oder – im Falle von heterogenen Arrays – beliebig zusammengestellt sein können. Sie unterstützen die üblichen Matrixrechenoperationen und können auch in dynamisch veränderbaren Größen auftreten. Berechnungen mit homogenen, in ihrer Größe festgelegten Arrays können von Compilern meist sehr effizient übersetzt werden.
  • Vektoren sind der Spezialfall von Arrays mit nur einer Dimension. Es gibt vordefinierte Vektortypen wie die Zeichenkette, ein Vektor von Zeichen, oder den Bitvektor, eine komfortable und effiziente Möglichkeit, Bits zu speichern und mit ihnen zu rechnen.

Klassische Lisp-Listen bestehen aus sogenannten CONS-Zellen. Damit sind geordnete Paare gemeint, deren erstes Element, der sogenannte CAR, einen Wert enthält, während deren zweites Element, der sogenannte CDR, auf die jeweils nächste Zelle oder im Falle des Listenendes auf das Symbol NIL zeigt. Im Grunde handelt es sich bei Lisp-Listen also um einfach verkettete Listen. Sie werden heute hauptsächlich verwendet, um Code darzustellen und mithilfe von Makros beliebig zu manipulieren – ein Mechanismus, der Common-Lisp-Programmierern einen Grad an Freiheit bietet, der bei anderen Programmiersprachen dem Sprachdesigner vorbehalten bleibt. Komplexere Daten speichert man für gewöhnlich in Klassen oder Verbünden, und Daten, mit denen man effizient rechnen möchte, in Arrays bzw. Vektoren.

Common Lisp enthält ein Paketsystem, das es erlaubt, Namensräume für Symbole zu definieren und Letztere zu im- und exportieren. Außerdem können der Bequemlichkeit und Lesbarkeit halber Abkürzungen bzw. Spitznamen für Pakete vergeben werden.

Symbole, die sich in anderen Namensräumen als dem aktuellen befinden, können angesprochen werden, indem man vor ihre Namen mit einem Doppelpunkt getrennt den jeweiligen Paketnamen schreibt. Beispielsweise zeigt der Name cl:format stets auf das FORMAT-Symbol im Paket CL, unabhängig davon, ob im aktuellen Paket möglicherweise eine Funktion definiert ist, die ebenfalls FORMAT heißt.

In Common Lisp sind Funktionen normale Objekte, die als Parameter übergeben oder von einer Funktion zurückgegeben werden können. Dadurch können sehr allgemeine Operationen ausgedrückt werden.

Das Auswertungsschema für Funktionen ist sehr einfach. Wenn ein Ausdruck der Form (F A1 A2 ) vorliegt, kann angenommen werden, dass das Symbol mit dem Namen F:

  1. ein spezieller Operator ist (special operator).
  2. ein schon definiertes Makro ist.
  3. der Name einer Funktion ist.

Wenn F der Name einer Funktion ist, dann werden die Argumente A1, A2, … von links nach rechts ausgewertet und der entsprechenden Funktion als Parameter übergeben.

Definieren neuer Funktionen

[Bearbeiten | Quelltext bearbeiten]

Das Makro defun definiert Funktionen. Eine Funktionsdefinition beschreibt den Namen der Funktion, die Namen aller Argumente und den Funktionsrumpf:

 (defun square (x)    (* x x)) 

Funktionsdeklarationen können Deklarationen beinhalten, die dem Compiler Hinweise zur Optimierung u. a. bzgl. Typen geben. Zusätzlich können auch sogenannte documentation strings oder kurz docstrings angegeben werden, die das Laufzeitsystem benutzen kann, um dem Benutzer interaktive Hilfe zu bieten.

 (defun square (x)    "Berechne das Quadrat von X."    (declare (fixnum x)    ; Typangabe für ein Argument             (optimize (speed 3)                       (debug 0)                       (safety 1)))    (the fixnum (* x x)))  ; Typangabe für den Rückgabewert 

Anonyme Funktionen (Funktionen ohne expliziten Namen) werden mittels des lambda-Operators (nach dem Lambda-Kalkül) erzeugt. Viele Common-Lisp-Programme benutzen Funktionen höherer Ordnung, bei denen es nützlich ist, anonyme Funktionen als Argumente zu übergeben.

Lese-, Kompilier- und Laufzeit

[Bearbeiten | Quelltext bearbeiten]

Als Besonderheit gegenüber anderen Sprachen erlaubt Common Lisp es dem Programmierer, eigenen Code nicht nur zur Laufzeit auszuführen, sondern auch in drei anderen Phasen, der Lese-, der Kompilier- und der Ladezeit.

Lesezeit und Lesemakros

[Bearbeiten | Quelltext bearbeiten]

Code, der während der Lesezeit ausgeführt wird, kann den Parser steuern und so eigene Syntax definieren. Meist geschieht dies in Form von sogenannten Lesemakros (read macros oder auch reader macros), die einzelnen Sonderzeichen zugewiesen werden und beliebigen Text in Code umwandeln können. So gibt es beispielsweise Lesemakros, die für arithmetische Ausdrücke die gewohnte Infix-Syntax bereitstellen und Ausdrücke wie 3 * (4 + 3) in Common Lisp gültig machen.

Auch ad hoc kann ein Programmierer Code zur Lesezeit ausführen, indem er sich des Standardlesemakros #. bedient. Der Ausdruck (+ #.(print 10) 20) beispielsweise gibt nicht nur bei seiner Ausführung 30 zurück, sondern druckt auch während des Einlesens des Ausdrucks selbst die Zahl 10 auf dem Bildschirm aus. Da diese Funktionalität bereits das bloße Einlesen von Ausdrücken, die beispielsweise über ein Netzwerk empfangen wurden, zu einem Sicherheitsproblem macht, kann sie über die Variable *read-eval* an- und ausgeschaltet werden.

Kompilierzeit und Lispmakros

[Bearbeiten | Quelltext bearbeiten]

Common Lisp bietet, wie fast alle Lisp-Dialekte und im Gegensatz zu den meisten anderen Sprachen, die Möglichkeit, Code zur Kompilierzeit auszuführen und damit die den Programmcode darstellenden S-Ausdrücke beliebig zu manipulieren, zu generieren und umzuformen. Die Sprache sieht dafür in erster Linie die sogenannten Makros vor. Da Lispmakros auf der Ebene der Programmstruktur arbeiten und sich dadurch in die Sprache selbst perfekt einfügen, sind sie mit Makrosystemen anderer Sprachen nicht vergleichbar und würden daher eigentlich einen eigenen Namen verdienen.

Makros können auf alle Common-Lisp-Funktionalitäten, auch selbstdefinierte, zugreifen, wodurch ihnen sehr viele Möglichkeiten offenstehen, Code umzuformen. Common-Lisp-Programmierer verwenden Makros oft, um anwendungsspezifische Sprachen in Common Lisp einzubauen, die sich nahtlos in die Sprache einpassen und ihre Ausdrucksstärke erhöhen. Auch ist es möglich, Makros zur Bereitstellung neuer Programmierparadigmen zu nutzen.

So ist zum Beispiel das im ANSI-Standard enthaltene klassenbasierte Objektsystem CLOS metazirkulär als ein Satz von generischen Funktionen und Klassen implementierbar, über die eine Schicht von Makros gelegt wird, um die bequeme Definition von Klassen, generischen Funktionen und anderen CLOS-Sprachelementen zu ermöglichen. Wie die Implementierung von CLOS tatsächlich aussieht, hängt jedoch vom verwendeten Lispsystem ab und wird vom Standard nicht vorgeschrieben.

Variablenbindung

[Bearbeiten | Quelltext bearbeiten]

Wie die meisten anderen Sprachen kennt auch Common Lisp das Konzept des Bindens von Werten an Namen. Es erweitert dieses Konzept jedoch durch zwei Aspekte: Einerseits können Variablennamen selbst als normale Objekte verwendet und zum Beispiel weitergegeben oder gespeichert werden, und andererseits stehen zwei ganz unterschiedliche Formen der Variablenbindung zur Verfügung. Der erstgenannte Aspekt wurde bereits im Abschnitt über Datentypen erläutert.

Lexikalische Bindung und lexikalische Variablen

[Bearbeiten | Quelltext bearbeiten]

Common Lisp verwendet standardmäßig lexikalische Bindung (lexical scoping), ein Konzept, das Scheme seinerzeit in die Lisp-Welt eingeführt hat. Dabei sind Namen immer in einem bestimmten, abgeschlossenen Teil des Quelltexts gültig, so dass es bei der Bindung nicht zu Überschneidungen mit zuvor oder künftig definierten Bindungen kommen kann.

Da Bindungen in Common Lisp jederzeit erzeugt werden können und Überschneidungen ausgeschlossen sind, ist es möglich, mit ihrer Hilfe allein durch Funktionsdeklarationen Datenstrukturen zu erzeugen. Dabei nutzt man einen Effekt aus, den man closure nennt: das automatische Einfangen von Bindungen. Closures sind Funktionen und ihre Bindungen.

Möchte man beispielsweise den my-cons-Operator definieren, der ein Paar aus zwei Werten erzeugt, so könnte man auf die Idee kommen, diesen eine anonyme Funktion (also einen lambda-Ausdruck) zurückliefern zu lassen, die die beiden Werte eingefangen hat, und auf Anfrage, d. h. wenn sie mit einem Argument num aufgerufen wird, einen der beiden zurückliefert (und zwar den ersten Wert für num = 0 und sonst den zweiten):

 (defun my-cons (x y)    (lambda (num)      (if (zerop num)          x          y))) 

Man kann die von diesem Operator erzeugten Werte zum Beispiel wie folgt verwenden (man beachte: funcall ist der Operator zum Aufrufen von Funktionen, die in Variablen gespeichert sind):

 (defvar *paar-A* (my-cons 100      200))  ;=> *paar-A*  (defvar *paar-B* (my-cons *paar-A* 42))   ;=> *paar-B*  (print (funcall *paar-A* 0))              ;=> 100  (print (funcall *paar-A* 1))              ;=> 200  (print (funcall *paar-B* 0))              ;=> (LAMBDA ...)  (print (funcall (funcall *paar-B* 0) 1))  ;=> 200 

Man beachte hierbei, dass die verschiedenen Bindungen von *paar-A* und *paar-B* für die Namen X und Y sich nicht überschneiden, sondern für jeden Aufruf von my-cons jeweils neu erschaffen werden. Es ist auch möglich, solche Bindungen nachträglich zu ändern, wie der folgende Codeausschnitt zeigt, der eine versteckte Bindung namens zähler definiert:

 (let ((zähler 0))    (defun zähler+     () (incf zähler))    (defun zähler-     () (decf zähler))    (defun hole-zähler () zähler))   (hole-zähler)  ;=> 0  (zähler+)      ;=> 1  (zähler+)      ;=> 2  (hole-zähler)  ;=> 2  (zähler-)      ;=> 1  (hole-zähler)  ;=> 1   (setf zähler 100)  ;=> Fehler, weil zähler nicht global definiert ist. 

Dynamische Bindung und Sondervariablen

[Bearbeiten | Quelltext bearbeiten]

Common Lisp unterstützt immer noch die in früheren Lisp-Dialekten verwendete dynamische Bindung, die globale Namen, aber zeitlich begrenzt gültige Wertbindungen verwendet. Aufgrund der möglichen Namenskonflikte wird diese Art der Wertbindung nur in Sonderfällen verwendet, weshalb in diesem Zusammenhang auch von special variables, also Sondervariablen, gesprochen wird.

Es ist üblich, die Namen von Sondervariablen zwischen zwei Sternchen zu setzen, um dem Leser ihre Natur auf einen Blick deutlich zu machen und Konflikte mit lexikalischen Variablen zu vermeiden.

Der Nutzen von Sondervariablen liegt gerade in ihren zeitlich begrenzten, aber globalen Auswirkungen begraben. Man kann sie sich auch als eine Form von impliziten Argumenten vorstellen, die den gesamten Aufrufsbaum hinab weitergereicht werden, ohne auf jeder Ebene explizit genannt werden zu müssen.

Ein Beispiel macht das Prinzip verständlicher. Nehmen wir an, es sei eine Funktion print-date vordefiniert (zum Beispiel in einer Bibliothek eines Drittanbieters), die das aktuelle Datum in besonders schöner Form auf die Standardausgabe schreibt. Nun möchten wir diese unersetzliche Funktion nutzen, aber das Ergebnis stattdessen in eine Datei schreiben. Hat nun der Entwickler der Funktion nicht an diese Möglichkeit gedacht und einen entsprechenden Parameter eingebaut, können wir uns die Tatsache zunutze machen, dass die Standardausgabe als Sondervariable definiert ist:

 (let ((*standard-output* ausgabedatei))    (format t "~&Das aktuelle Datum ist: ")    (print-date)) 

Die Tatsache, dass wir *standard-output* dynamisch an unsere ausgabedatei binden, sorgt dafür, dass jede Standardausgabe dort landet, wo wir sie haben wollen. let sorgt hierbei dafür, dass die Bindung nach Beendigung des Blocks wieder auf ihren Ausgangszustand zurückgesetzt wird.

Sondervariablen haben einen weiteren interessanten Aspekt, der im Falle von Nebenläufigkeit (Spracherweiterung zu ANSI Common Lisp) zutage tritt und ihre Implementierbarkeit oberhalb der eigentlichen Sprachebene unmöglich macht oder doch zumindest sehr erschwert: Ihre Bindungen sind nämlich thread-lokal. Das heißt, dass zwei parallele Programmabläufe, die auf denselben Namen zugreifen, unterschiedliche Bindungen für diesen Namen verwenden können, obwohl Namen von Sondervariablen global sind. Der obige Code würde demnach auch funktionieren, wenn zwischen der zweiten und der dritten Zeile ein anderer Thread auf die Idee käme, *standard-output* an seine eigene Datei zu binden. Da die beiden Bindungen voneinander unabhängig sind, stören sich die beiden Threads dadurch nicht. Man spricht hierbei von Bindungsüberlagerung, weil nicht etwa die bestehende Bindung verändert, sondern immer neue Bindungen auf die alten aufgelagert und bei Beendigung des let-Blocks wieder heruntergenommen werden.

Vergleich mit anderen Lisp-Dialekten

[Bearbeiten | Quelltext bearbeiten]

Common Lisp basiert vor allem auf dem am MIT entwickelten Maclisp-Dialekt, wurde aber auch stark von Symbolics ZetaLisp, InterLisp und Scheme beeinflusst. Guy Steele, der Vorsitzende des Common-Lisp-Komitees, hatte in den 1970er Jahren zusammen mit Gerald Jay Sussman Scheme entworfen.

Im Gegensatz zu den meisten anderen Lisp-Dialekten, welche traditionsgemäß ausschließlich dynamische Variablenbindung verwendeten, benutzt Common Lisp hauptsächlich die in Scheme eingeführte lexikalische Variablenbindung (lexical scope). Siehe Abschnitt Variablenbindung.

Verfügbarkeit von Paketen und Bibliotheken

[Bearbeiten | Quelltext bearbeiten]

Ähnlich wie bei anderen Programmiersprachen (zum Beispiel CPAN im Falle von Perl), existiert mit ASDF-Install[6] eine Sammlung von Common-Lisp-Modulen, die sich automatisch installieren lassen.

Es gibt zahlreiche Pakete für die unterschiedlichsten Einsatzgebiete, beispielsweise für Webentwicklung, 3D-Grafik, Textverarbeitung und vieles mehr. Auch stehen Bindings für GUI-Toolkits wie GTK+, Cocoa, Microsoft Windows, Tk und andere ebenso zur Verfügung wie Implementierungen des Common Lisp Interface Managers (kurz CLIM) und ein Binding für das .Net-Framework.

Entwicklungsumgebungen

[Bearbeiten | Quelltext bearbeiten]

Sprachen mit S-Ausdruck-basierter Syntax wie Common Lisp eignen sich dank ihrer syntaktischen Einfachheit besonders für den Einsatz mächtiger Codeanalyse- und -managementprogramme. Außerdem können Entwicklungsumgebungen mit ihnen auf vielfältige Art und Weise integriert werden, bei Bedarf auch bis hin zur Vermengung von Programm- und IDE-Code.

Eine weit verbreitete freie Entwicklungsumgebung ist SLIME,[7] der Superior Lisp Interaction Mode für Emacs, welcher den üblichen interaktiven Softwareentwicklungsprozess in Common Lisp gut unterstützt. Das Fehlen von Refaktorierungswerkzeugen wird nur von wenigen Lispern als ein großes Problem angesehen, zumal die Sprache selbst mit ihren Makros gute Möglichkeiten dafür auf der Quelltextebene bietet.

Weiterhin existieren zahlreiche kommerzielle Common-Lisp-Entwicklungssysteme, wie zum Beispiel Allegro Common Lisp von Franz Inc. und LispWorks von LispWorks Ltd.

Deutschsprachige Bücher

[Bearbeiten | Quelltext bearbeiten]
  • R. A. Brooks: Programmieren in Common Lisp, Oldenbourg Wissenschaftsverlag, 1987, ISBN 3-486-20269-3
  • Herbert Stoyan: Programmiermethoden der Künstlichen Intelligenz, Band 1, Springer, 1988, ISBN 978-3-540-19418-7
  • Rüdiger Esser, Elisabeth Feldmar: LISP, Fallstudien mit Anwendungen in der Künstlichen Intelligenz, Vieweg, 1989, ISBN 3-528-04585-X
  • Herbert Stoyan: Programmiermethoden der Künstlichen Intelligenz, Band 2, Springer, 1991, ISBN 978-3-540-52469-4
  • Otto Mayer: Programmieren in Common Lisp, Spektrum Akademischer Verlag, 1995, ISBN 3-86025-710-2
  • Paul Graham: ANSI Common Lisp. Markt+Technik Verlag, 1997, ISBN 3-827-29543-2
  • Conrad Barski: Land of Lisp: Lisp-Programmierung einfach lernen und originelle Spiele programmieren, mitp, 2011, ISBN 978-3-8266-9163-8
  • Patrick M. Krusenotto: Funktionale Programmierung und Metaprogrammierung, Interaktiv in Common Lisp, Springer Fachmedien Wiesbaden 2016, ISBN 978-3-658-13743-4

Einzelnachweise

[Bearbeiten | Quelltext bearbeiten]
  1. Richard P. Gabriel erwähnt Scott Fahlman als Leiter des Common Lisp Projekts
  2. a b Guy L. Steele Jr.: Common Lisp: The Language. Prentice Hall, ISBN 0-13-152414-3.
  3. History im Common Lisp HyperSpec, abgerufen am 6. November 2016
  4. Paul Graham: On Lisp. Prentice Hall, 1993, ISBN 0-13-030552-9
  5. siehe cliki.net/Unicode Support (englisch)
  6. cliki.net/asdf
  7. common-lisp.net