![]() |
|
URL dieses Artikels:
Alberne Klammern?
Lisp - Die programmierbare Programmiersprache
von Armin Roehrl und Stefan Schmiedl
Pascal is for building pyramids - imposing, breathtaking structures built by armies pushing heavy blocks into place. Lisp is for building organisms ... - Alan Perlis Die Programmiersprache Lisp hat die letzten 40 Jahre dynamisch gemeistert und darf als ausgereift bezeichnet werden. Lisp-Code kann sowohl interpretiert als auch kompiliert werden, was neben einer bequemen, inkrementellen Entwicklung auch eine vernünftige Ausführungsgeschwindigkeit ermöglicht. Lisp ist sehr viel weiter verbreitet als man denkt: Vom Finanzbereich bis zum Telekommunikationsanbieter findet man Erfolgsstorys von Lisp [Erfolg]. Die bekannteste dürfte wohl der Artikel des Lisp-Gurus Paul Graham [Viaweb] sein, der seinen (in Lisp programmierten) eCommerce Store Viaweb für $50 Millionen an Yahoo verkaufen konnte.
Wenn man als Programmierer nicht einrosten will, sollte man jedes Jahr eine neue Sprache lernen. Wenn's 2000 Java war und 2001 Ruby, warum nicht für 2002 Lisp vorsehen? Man wird viele bekannte Sprachkonstruktionen wiederentdecken, die in Lisp schon jahr(zehnt)elang gang und gäbe sind. Lisp hilft Programmierzeit zu senken. Die NASA gab verschiedenen Teams die gleiche Programmieraufgabe in C, C++, Java und Lisp. Es wurden die Programmierzeit, der Speicherbedarf der Programme und die Geschwindigkeit gemessen. Das interessante Ergebnis der Studie [NASA] besagt: Lisps Geschwindigkeit war mit der Geschwindigkeit von C und C++ vergleichbar. Der Speicherbedarf war in etwa wie der von Java, aber die Lisp-Programmierteams hatten mit Abstand als erste die Aufgabe gelöst. In die gleiche Richtung geht der bekannte Artikel [Hindsight]: Mit Accelerating Hindsight: Lisp as a Vehicle for Rapid Prototyping betitelt zeigt er eindrucksvoll die Stärke von Lisp für Rapid Prototyping und wieso das etwas Gutes ist. So manches Projekt hinterlässt einen schlechten Nachgeschmack: Oft weiß man erst, wenn ein Projekt zu Ende ist, wie man es hätte machen sollen. Manager wollen so schnell wie möglich ein richtiges Produkt und keinen Prototypen, aber diese Sichtweise kann sich schnell als nur kurzfristig tauglich herausstellen. Lisp erlaubt es, in sehr kurzer Zeit etwas zu schaffen, das man beurteilen kann, zum Beispiel die Durchführbarkeit eines neuen Ansatzes. Damit bleibt mehr Zeit für die Implementation der Lösung, die sich in dieser Probephase als richtig erwiesen (!) hat. Die vier Hauptpunkte, wieso Lisp gut für Rapid Prototyping ist, sind:
Wir haben als Software für diesen Artikel die Open Source Implementation CLisp gewählt. Die zwei bekanntesten kommerziellen Lisp-Implementationen dürften von Xanalys (von Harlequin übernommen) [Xanalys] und Franz [Franz] kommen. Beide bieten auch eine kostenlose Testversion an. Clisp wird am einfachsten per rpm -ih clisp.rpm installiert. Die meisten Linux-Distributionen, wie z.B. Suse liefern automatisch clisp mit. Oder man kompiliert den Sourcecode: 8-10MB Sourcecode herunterladen [CLISP], entpacken, konfigurieren und kompilieren: tar xzvf clisp-2.27.tar.gzIn der Datei config.lisp sollte man den Namen des Rechners eintragen und gegebenenfalls den URL auf eine lokale Kopie der Common Lisp HyperSpec [hyperspec] setzen. ...Diese Adresse wird verwendet, um von CLisp aus Definitionen nachzuschlagen: [1]> (setq *browser* :lynx)Man kann Module wie GNU-Regexp für reguläre Ausdrücke oder CLX für ein Xlib-Äquivalent dazu linken. Dies kann bei Bedarf auch nachträglich geschehen, indem man die neue Bibliothek kompiliert und dann mit dem übrigen System zu einem neuen Linkset kombiniert. Beim Aufruf von CLisp kann dann das entsprechende Linkset (base und full sind normalerweise vorhanden) ausgewählt werden: > clisp -K <linkset>Der read-eval-print-loop Die Interaktion mit einer Lisp-Umgebung geschieht eigentlich immer in dieser klassischen Schleife: Ein LISP-Interpreter liest einen Lisp-Ausdruck, wertet ihn aus, und zeigt das Ergebnis an. Ein Lisp-Ausdruck wird entweder als Funktionsaufruf (mit Klammern) oder als symbolischer Name für einen Wert (ohne Klammern) interpretiert. Namen können prinzipiell jedes beliebige Zeichen enthalten, in der Regel wird zwischen Groß- und Kleinschreibung nicht unterschieden. Mehrteilige Namen werden durch einen Bindestrich gegliedert. [1]> (lisp-implementation-version)Durch die Präfix-Notation entfallen implizite Regeln wie Punkt vor Strich oder der Vorrang verschiedener Operatoren, wodurch Lisp-Programme nach einer kurzen Eingewöhnung sehr gut lesbar werden. Durch die Klammern ist auch sofort klar, wie viele Argumente zu einem bestimmten Funktionsaufruf gehören. Mit einem guten Editor (emacs und vi hatten eigentlich schon immer einen Lisp-Modus), der sich um die Einrückung kümmert und die Bezugsklammer anzeigen kann, ignoriert man nach kurzer Zeit Häufungen schließender Klammern. Schnupperkurs Für einen tieferen Einstieg in Lisp empfehlen wir eine der Lisp-Bibeln [Slade, Graham]. Angenehm fällt bei den beiden auf, dass es keine AI-Bücher sind. Wer in AI mit Lisp einsteigen will, sollte [Norvig, Winston] lesen. Eines der besten Nachschlagewerke mit viel Informationen ist nach wie vor [Steele], das auch komplett online [SteeleOnline] verfügbar ist. Obligatorisches Hello, World Listing 1 ![]() Listing 1 zeigt ein Hello World-Programm. Als Minimalversion würde auch nur (princ "Hello, world!") ausreichen. Die readline-Unterstützung von CLisp beschränkt sich übrigens nicht nur auf die Möglichkeit, die Eingaben per Cursortasten zu editieren, zum Beispiel kann man mit der Tab-Taste Funktions- und Variablennamen ergänzen lassen. [1] > *print-<Tab>Atome und Listen Ein Lisp-Atom ist ein symbolischer Name oder eine Zahl, Listen setzen sich aus Atomen oder Listen zusammen. Das Atom nil und die leere Liste () sind äquivalent, ansonsten gibt es keine Überschneidungen. Die erste Aufgabe des Evaluators "eval" ist es, den Wert eines Atoms zu finden. [1] *print-base*Die Sternchen am Anfang und am Ende kommen daher, dass globale Werte traditionell so markiert werden. Wenn eval auf eine Liste stößt, wird angenommen, dass der erste Eintrag der Name einer Funktion ist, die übrigen Elemente die Argumente für den Aufruf sind, die jeweils für sich ausgewertet werden, bevor die Funktion selbst ausgeführt wird. (Name-der-Funktion 1.-Argument 2.-Argument ...) [2] (+ 8 8)Neben Funktionen (wie +) gibt es noch special forms, die die Auswertung ihrer Argumente kontrollieren können: setq zum Beispiel nimmt den Variablennamen direkt und wertet nur den zweiten Ausdruck aus. Listen manipulieren Eine Liste setzt sich aus zweiteiligen Zellen zusammen: der erste Teil (car) enthält den eigentlichen Wert und der zweite Teil (cdr) enthält den Rest: [1]> (setq liste (list 1 2 3 4 5))An Stelle der historisch bedingten Namen car und cdr kann man auch first und rest verwenden. Definieren von Funktionen Funktionen in Lisp haben *sehr* flexible Parameterlisten. Neben notwendigen Parametern können noch optionale Parameter und Schlüsselwort-Parameter, letztere mit Standardwerten, angegeben werden. Schließlich gibt es noch die Möglichkeit, alle noch nicht anderweitig verwendeten Argumente über einen rest-Parameter an die Funktion zu übergeben. Definieren von Funktionen Funktionen in Lisp haben *sehr* flexible Parameterlisten. Neben notwendigen Parametern können noch optionale Parameter und Schlüsselwort-Parameter, letztere mit Standardwerten, angegeben werden. Schließlich gibt es noch die Möglichkeit, alle noch nicht anderweitig verwendeten Argumente über einen rest-Parameter an die Funktion zu übergeben. Listing 2 [1]> (defun square (x) (* x x))Im Beispiel in Listing 2 wird durch die Fehlermeldung im aktuellen read-eval-print-loop ein so genannter break-loop gestartet, der einige (implementationsabhängige) Besonderheiten aufweist. In einer integrierten Entwicklungsumgebung könnte man jetzt (komfortabel) einen Programmierfehler beseitigen und im Anschluss das Programm an der gleichen Stelle weiter ausführen lassen. Verzweigungen Neben dem bekannten (if test-expr true-expr false-expr) und dem Gegenstück (unless test-expr false-expr true-expr) ist die klassische Verzweigung in Lisp die cond-Anweisung, die einem hochgezüchteten case/switch-Statement gleicht. (cond (test-a epxr-a1 expr-a2 ... result-a)Die Tests werden der Reihe nach ausgewertet, der erste Zweig, dessen Test wahr (t) ist, wird ausgewertet. Das besondere ist, dass die Tests für die einzelnen Zweige voneinander unabhängig sein können, sie müssen sich nicht darauf beschränken, verschiedene Werte einer Variablen abzuprüfen. Rekursive Funktionen Typisch Lisp sind rekursive Strukturen, die oft elegante Formulierungen ermöglichen (siehe Listing 3). Listing 3 [1]> (defun fak (n)Schleifen und Iteratoren [1]> (dotimes (i 3 "fertig") (print i))Neben diesen konventionellen Schleifen gibt es noch die allgemeine do-Schleife, bei der zuerst lokale Variablen definiert werden, die nur in der Schleife verfügbar sind. Die Bedingung muss erfüllt sein, damit die Schleife abbricht: [3]> (do ((i 10 (1+ i))Schließlich stellt Lisp noch eine Reihe von Iteratoren zur Verfügung, die einen effizienten Umgang mit Listen erlauben. [1]> (mapcar #'print '(a 1 b 2))CLOS Das Common Lisp Object System ist eine sehr mächtige Lisp-Erweiterung, die auf echten generischen Funktionen basiert und überaus flexibel ist, wenn es um die Kombination von Methoden geht (siehe Listing 4). Listing 4 [1]> (defclass punkt ()An den Ergebnissen von Schritt 3 und 7 kann man sehen, dass es sich wirklich um das gleiche Objekt handelt, mit der print-object-Methode wird eine lesbare Ausgabe erzeugt. Die Ausdrücke mit setf und incf ändern den Wert der mit ihrem ersten Argument bezeichnet wird. Beim Parametertypen muss es sich nicht unbedingt um eine selbst definierte Klasse handeln. Man kann mit Typbezeichnern wie unter anderem mit NUMBER, FLOAT, INTEGER, RATIONAL, REAL oder COMPLEX die eingebauten Lisp-Typen referenzieren. Kurz gesagt, wird diejenige Methode mit den speziellsten passenden Typen ausgewählt. Alle haben von Lisp das beste kopiert, wieso dann noch Lisp lernen? Kent M. Pitman argumentiert, dass Sprachfeatures ein ökologisches System bilden, sodass es nicht ausreicht, Sprachen Feature für Feature zu vergleichen. Wesentlich ist die Integration der Elemente in der Sprache. Um Lisp wirklich einschätzen zu können, muss man es eine Zeit lang benützt haben. Hier sind ein paar Pluspunkte, die Pitman [Pitman1, Pitman2] auf Slashdot aufzählte:
Common Lisp ist
|
||
|