Common Lisp
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] Geschichte1984 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] SyntaxCommon 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 ;; 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)
DatentypenCommon Lisp unterstützt eine Vielzahl von Datentypen, mehr als viele andere Sprachen. Diese Typen sind hierarchisch angeordnet. Skalare TypenZahlen 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. DatenstrukturenCommon 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.
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. PaketsystemCommon 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. FunktionenIn 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
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 FunktionenDas Makro (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
Lese-, Kompilier- und LaufzeitAls 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 LesemakrosCode, 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 Auch ad hoc kann ein Programmierer Code zur Lesezeit ausführen, indem er sich des Standardlesemakros Kompilierzeit und LispmakrosCommon 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. VariablenbindungWie 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 VariablenCommon 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 (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: (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 (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 SondervariablenCommon 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 (let ((*standard-output* ausgabedatei))
(format t "~&Das aktuelle Datum ist: ")
(print-date))
Die Tatsache, dass wir 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, Vergleich mit anderen Lisp-DialektenCommon 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Ä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. EntwicklungsumgebungenSprachen 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. Literatur
Deutschsprachige Bücher
Weblinks
Einzelnachweise
|