lisp

Inhalt

  1. Allgemein
  2. Prinzipien
  3. Daten im Speicher
    1. Datentypen
    2. Die Liste
  4. Der Interpreter
    1. Evaluation von Atomen
    2. Evaluation von Listen
    3. Quoting
  5. Grundlagen der Programmierung
    1. Werte
    2. Eigenschaften
    3. Funktionen und lambda-lists

Formatierung

1. Allgemein

lisp = list processor ist eine Sprache zur symbolischen Datenverarbeitung. Sie eignet sich also beispielsweise zur Programmierung mathematischer Anwendungen oder künstlicher Intelligenzen. Die Programmiersprache ist bereits recht alt - sie wurde in den Jahren 1956 bis 1962 von John McCarthy am MIT entwickelt.

Vorteile der Sprache sind hohe Flexibilität und Effizienz im Code, Nachteile vor allem die schlechte Compilierbarkeit und manchmal auch die fehlende Systemnähe.

2. Prinzipien

3. Daten im Speicher

3.1. Datentypen

Folgende grundlegenden Datentypen gibt es:

3.2. Die Liste

Am Interpreter-Prompt oder in einer lisp-Datei schreibt man eine Liste als eingeklammerte, durch Leerzeichen getrennte Folge von Symbolen, Zahlen etc. Also zum Beispiel:

Im Speicher werden alle Listen als cons-cells abgelegt. Eine cons-cell besteht aus zwei Pointern mit den Bezeichnungen car und cdr
Darstellung:

car cdr

Die Liste (c 3 p o) sieht dann im Speicher so aus:

c -> 3 -> p -> o nil

Das heißt: Mehrere cons-cells, deren car-Pointer jeweils auf ein Element der Liste zeigt, und deren cdr-Pointer jeweils auf den Rest der Liste, respektive die nächste cons-cell zeigt.

Daraus folgt nun:

In verschachtelten Listen zeigt mindestens ein car-Feld auch auf eine Liste:

(hello world (how are you))

=

hello -> world -> \/ nil
how -> are -> you nil

Anmerkung: Alle anderen Datentypen werden je nach Interpreter in verschiedenen Formaten im Speicher abgelegt und können daher hier schlecht beschrieben werden. Für den lisp-Programmierer sind derlei Interna des Interpreters aber auch kaum von Interesse.

4. Der Interpreter

Der lisp-Interpreter liest eine beliebige Eingabe (Datei oder Tastatur) solange ein, bis er eine gültige s-expression im Lesepuffer hat. Die wird dann evaluiert und das Ergebnis wird - wenn man im interaktiven bzw. Prompt Modus ist - ausgegeben.

Eine s-expression ist:

4.1. Evaluation von Atomen

Wenn die s-expression ein Atom ist, ist die Lage relativ einfach. Hat es einen der folgenden Datentypen, so evaluiert es einfach zu sich selbst:

Ist das Atom ein Symbol, versucht der Interpreter seinen Wert zu bestimmen, denn jedem Symbol kann man einen Wert, eine Funktionsdefinition und Eigenschaften zuweisen. Ein Symbol evaluiert also zu seinem Wert, so es denn einen hat; wenn nicht schreit der Interpreter "unbound symbol <symbolname>" oder etwas in der Art.

Anmerkung: Ob ein Symbol sowohl eine Funktionsdefinition als auch einen Wert haben kann, ob eines das andere ausschließt oder ob zwischen den beiden technisch gar kein Unterschied besteht ist vom Interpreter abhängig!

4.2. Evaluation von Listen

Listen werden bei der Evaluation als Funktionsaufrufe interpretiert. Sie haben die Form:

(<Funktion> <Parameter 1> <Parameter 2> ...)

Natürlich werden vor dem Funktionsaufruf alle Parameter rekursiv evaluiert, das heißt, ein Aufruf von (myfunction a b) übergibt nicht die Symbole a und b als Parameter sondern ihre Werte.

Normalerweise ist das erste Element eines Funktionsaufrufes ein Symbol - so liefert zum Beispiel (car some-list) das car-Feld des Wertes, der dem Symbol some-list zugewiesen wurde.
Das erste Element kann aber auch eine lambda-list sein. Manchmal will man nicht erst einem Symbol eine Funktionsdefinition zuweisen, sondern die Funktion direkt eingeben. Die genaue Syntax von lambda-lists wird später behandelt, hier aber mal ein kleines Beispiel:

((lambda (x) (car x)) some-list)

ist praktisch äquivalent zu

(car some-list)

4.3. Quoting

Alle Eingaben an den Interpreter werden also evaluiert. Häufig will man aber bestimmte Ausdrücke (vor allem Funktionsparameter) gar nicht evaluieren. Will man beispielsweise das car-Feld der Liste (a b c) ermitteln und gibt ein (car (a b c)) so erhält man eine Fehlermeldung - der Interpreter versucht den Parameter (a b c) zu evaluieren, es gibt aber keine Funktion, die dem Symbol a zugewiesen wäre. Die Lösung des Problems nennt sich quote. quote ist eine Funktion, die ihren Parameter unevaluiert zurückliefert. In unserem Beispiel müsste man also schreiben (car (quote (a b c))), was korrekt zu a evaluieren würde. Nun ist es aber etwas umständlich, in solchen Fällen jedesmal einen Aufruf von quote einzufügen; daher gibt es ein Makrozeichen, das zu einem Aufruf von quote expandiert wird:

(car '(a b c)) = (car (quote (a b c)))

5. Grundlagen der Programmierung

Wie bereits erwähnt wird in lisp alles zur Laufzeit abgewickelt. Es müssen also keine Variablen oder Funktionen deklariert werden. Funktionsdefinitionen können sich sogar während der Arbeit ändern!

Die wenigen existierenden Compiler für lisp können die enorme Flexibilität dieser Programmiersprache oft nicht in vollem Umfang bieten. Man muss jedoch sagen, dass nur ein kleiner Teil der Anwendungen die Möglichkeiten von lisp voll ausreizt - und wer nicht gerade eine hochkomplexe künstliche Intelligenz programmiert, der freut sich über einen Compiler, der seiner Anwendung ungeahnte Ausführungsgeschwindigkeit verleiht ;-)

5.1. Werte

Um einem Symbol einen Wert zuzuweisen, gibt es zwei grundlegende Funktionen: set und setq

Syntax: (set/setq <Symbol 1> <Wert 1> <Symbol 2> <Wert 2> ...)

Der einzige Unterschied der Funktionen besteht darin, dass setq die Symbol-Parameter (sozusagen die Variablennamen) automatisch quotet (daher das ''q'' am Ende des Namens), set jedoch nicht.
Um also den Symbolen a, s und n die Werte "Hello world!", xyz und 5 zuzuweisen hat man zwei Möglichkeiten:

  1. (setq a "Hello world!" s 'xyz n 5)
  2. (set 'a "Hello world!" 's 'xyz 'n 5)

Um seinen Wert zu erhalten muss man ein Symbol nur evaluieren. Mit den oben definierten "Variablen" liefert also (+ n 3) als Evaluationsergebnis 8 zurück.

5.2. Eigenschaften

Jedes Symbol hat auch eine Eigenschaftsliste. Sie ähnelt ein wenig den Einträgen einer INI-Datei. Die wichtigsten Funktionen für property-lists sind:

  1. (put <Symbol> <Schlüssel> <Wert>) - weist in der property-list von <Symbol> dem Schlüssel <Schlüssel> den Wert <Wert> zu.
  2. (get <Symbol> <Schlüssel>) - gibt den Wert des Schlüssels <Schlüssel> in der property-list des Symbols <Symbol> zurück. Existiert der Schlüssel nicht, wird nil zurückgeliefert.

5.3. Funktionen und lambda-lists

Eine lambda-list hat folgende Struktur:

(lambda (<Parameter 1> <Parameter 2> ...

        [&optional <Parameter o1> / (<Parameter o1> <Standard falls nicht angegeben>) ...]

        [&rest <Rest-Parameter>]

        [&aux <Hilfs-Parameter 1> / (<Hilfs-Parameter 1> <Wert>) ...]

        )

     <Anweisung 1>

     <Anweisung 2>

     ...

     <letzte Anweisung liefert Rückgabewert>

)

Interessant ist hier vor allem die Liste der Parameter. Es gibt folgende Typen:

Es existieren auch noch &key-Parameter, die aber nicht sehr häufig zum Einsatz kommen.

Wichtig ist außerdem, dass die letzte Anweisung einer Funktion automatisch ihren Rückgabewert bestimmt.

Um einem Symbol eine Funktionsdefinition zuzuweisen gibt es die Funktion defun. Ihre Syntax ist praktisch die gleiche wie die der lambda-lists:

(defun <Funktionsname - automatisch gequotet> (<Parameter>) <Anweisungen>)