Lisp - Die programmierbare Programmiersprache

Alberne Klammern?

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:

  • Lisp ist reichhaltig
  • Lisp verfügt über generische Funktionen und Message Passing
  • Lisp unterstützt modulares Design (unter anderem mit einem Condition-System, Macros,
    optionalen Deklarationen, Funktionen höherer Ordnung)
  • Lisp ermöglicht eine interaktive Entwicklung und Debugging in einer einheitlichen Umgebung

[Hindsight] untermauert diese Aussagen durch ausführliche Codebeispiele, sollte jedoch erst gelesen werden, nachdem man Grundkenntnisse in der Syntax von Lisp besitzt.
Kent M. Pitman, ein bekannter Lisp-Guru, wurde auf Slashdot gefragt, was für ihn das wichtigste an einer abstrakten Sprache sei? Er antwortete darauf: Meine Zeit, da sie nicht erneuerbar ist. Am Ende seines Lebens wolle er sagen können, dass er viele interessante Dinge getan hat und nicht nur ein paar interessante, die aber dafür wirklich schnell gelaufen sind [Pitman].
Lisp (= LISt Processor, nicht Lots of Insignificant and Silly Parentheses) wurde Ende der 50er Jahre von McCarthy entwickelt und 1959 gab es die LISP1 Implementation [LispGeschichte1, LispGeschichte2]. Lisp ist damit ein bisschen jünger als Fortran (1954-57) [FortranGeschichte] und doch erstaunlich moderner.
Lisp beruht auf der Theorie des Lambda-Kalküls und wurde von Anfang an mit dem Ziel entwickelt, die Möglichkeit zu vollständiger Abstraktion zu liefern. Dies ist sicher mit ein Grund, wieso vor allem AI-Forscher Lisp als ihre Sprache zu schätzen gelernt haben. Diese Reinheit ging in der Vergangenheit auf Kosten der Geschwindigkeit, doch mittlerweile ist Lisp (wegen optimierender Compiler) schnell geworden.
Anfang der 70er Jahre haben Forscher zwei neue Hardware-Techniken eingesetzt, um Lisp zu beschleunigen: Tagged Architecture und Micro-Programming. Tagged Architecture ermöglicht den einfachen Umgang mit Daten, deren Typ sich dynamisch ändern kann. Mit Micro-Programming kann man auf Hardware-Ebene komplexe Instruktionen erstellen, die für die high-level Lisp Semantik vorteilhaft sind. Diese Experimente führten letztendlich zu speziellen Lisp Computern, so genannten Lisp Maschinen [LispMachine], die sich aber vor allem auf Grund des hohen Preises nie wirklich durchsetzen konnten. Symbolics Inc. [Symbolics] stellte eine komplette Workstation her, inklusive Entwicklungsumgebung Genera, ebenso hatten Xerox und Texas Instruments eigene Produkte. Die zahlreichen Lisp-Dialekte wurden in den 80er Jahren zu Common Lisp standardisiert. Das Ergebnis ist Common LISP, dessen Spezifikation [hyperspec] auch online verfügbar ist.
Es gibt verschiedene Varianten und Abkömmlinge von Lisp: die bekanntesten dürften Scheme und T sein. In letzter Zeit macht Dylan immer wieder von sich reden, eine moderne Sprache, die viel von Lisp geerbt hat, nur nicht die einfache Syntax. Zwei der bekanntesten Lisp Anwendungen dürften wohl Emacs [Elisp] und AutoCAD sein, die beide einen Lisp-Dialekt zur Anpassung und Automatisierung einsetzen.

CLISP Installation

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:

...
(defun short-site-name () "workhorse")
(defun long-site-name () "workhorse.approximity.com")
...
(setq *clhs-root-default* "file:///usr/local/share/doc/HyperSpec/")

Diese Adresse wird verwendet, um von CLisp aus Definitionen nachzuschlagen:

> clisp -K 

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)
"2.27 (released 2001-07-17) (built 3215460179) (memory 3215461181)"
[2]> (+ 1 2 (* 2 2))
7
[3]> *print-case*
:UPCASE

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.

Obligatorisches Hello, World

Listing 1

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*
10

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 …)

[1]> (setq liste (list 1 2 3 4 5))
(1 2 3 4 5)
[2]> (car liste)
1
[3]> (cdr liste)
(2 3 4 5)
[4]> (second liste)
2
[5]> (elt liste 1)
2

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

...
(defun short-site-name () "workhorse")
(defun long-site-name () "workhorse.approximity.com")
...
(setq *clhs-root-default* "file:///usr/local/share/doc/HyperSpec/")

Typisch Lisp sind rekursive Strukturen, die oft elegante Formulierungen ermöglichen (siehe Listing 3).

Listing 3

...
(defun short-site-name () "workhorse")
(defun long-site-name () "workhorse.approximity.com")
...
(setq *clhs-root-default* "file:///usr/local/share/doc/HyperSpec/")

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

...
(defun short-site-name () "workhorse")
(defun long-site-name () "workhorse.approximity.com")
...
(setq *clhs-root-default* "file:///usr/local/share/doc/HyperSpec/")

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.

CLISP Installation

Kent M. Pitman argumentiert, dass Sprachfeatures ein ökologisches System pictureen, 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:

  • Lisp ist dynamisch. Sogar in einem laufenden Image können in einem Debugger Funktionen, Klassen usw. geändert und neu definiert werden. Der laufende Code benützt ab sofort die neuen Methoden. Dies geht sogar, wenn die neue Klasse andere Slots hat, und man kann definieren, wie das Update von den alten auf die neuen für bereits existierende Instanzen gehen soll.
  • Lisp ist introspektiv.
    * Die Syntax von Lisp ist sehr formbar. Lisp erlaubt es Programmierern, die Syntax beliebig umzudefinieren. Nicht umsonst kommt der Name Emacs von Editing macros.
  • Lisp erzwingt keine Deklarationen von Variablentypen, wodurch Prototyping schneller wird. Andererseits können Deklarationen in funktionierenden Code eingebettet werden, um die Ausführungsgeschwindigkeit oder den Speicherbedarf zu optimieren.
  • Lisp besitzt ein mächtiges Klassensystem und ein flexibles Meta-Klassen System. Das Klassensystem ermöglicht mächtige Slot- und Methodendefinitionen. Das Meta-Klassen System erlaubt es dem Benutzer, das Objektsystem wie Daten zu behandeln, die programmiert werden können, um neue Klassen zu erstellen.
  • automatische Speicherverwaltung.
  • mächtige integrierte Tools.

Letzter Tip für Programmierer die unter Java-wütigem Management leiden: Mittels [Jscheme] oder [Jfranz] kann man Lisp durch die Hintertür in Java-Projekte mogeln. [Jfranz] erlaubt den Aufruf von Java-Klassen und [Jscheme] erlaubt die Einbindung von Scheme in Java-Programme.

  • interaktiv
  • ein Lisp für den professionellen Gebrauch
  • für konventionelle und AI-Projekte geeignet

Common Lisp Programme sind

  • einfach zu testen (interaktiv)
  • leicht zu maintainen
  • portabel (Es gibt einen Standard für die Sprache und die Bibliotheksfunktionen)

CLisp

  • braucht nur 2MB Speicher)
  • implementiert fast den gesamten ANSI Standard, sowie einige Erweiterungen)
  • arbeitet mit vi und emacs)
  • ist kostenlos)

Common Lisp bietet

  • klare Syntax und Semantik
  • reichhaltige Datentypen: Zahlen, Strings, Arrays, Listen, Characters, Symbols, Structures, Streams, usw.
  • runtime typing: Der Programmierer braucht keinen Typendeklarieren, aber er wird benachrichtigt, wenn es Probleme gibt
  • viele generische Funktionen: 88 arithmetische Funktionen für alle Art von Zahlen (ganze, rationale, fließkomma- und komplexe Zahlen), 44 Such-, Filter- und Sortier-Funktionen für Listen, Arrays und Strings
  • automatische Speicherverwaltung (garbage collection)
  • Programme sind in Module packbar
  • ein Objekt System, generische Funktionen mit mächtigen Methodenkombinationen
  • Makros: Jeder Programmierer kann seine eigenen Spracherweiterungen machen

CLISP bietet

  • einen Interpreter
  • einen Compiler, der 5-mal so schnell wie der Interpreter ist
  • einen Source-level Debugger, der es erlaubt durch interpretierten Code zu steppen
  • alle Datentypen können beliebig groß sein (die Größe wird nie deklariert und kann dynamisch verändert werden)
  • beliebig lange Integer
  • Beliebig-genaue Arithmetik bei floating point Operationen
  • Mehr als 800 Bibliotheksfunktionen und Makros, mehr als 600 davon sind in C geschrieben
Geschrieben von
Armin Roehrl und Stefan Schmiedl
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.