The Making of Scheme in Perl. This document is linked from Wolf Busch's Heimatseite, and it's completely German. If you know one who understands German very good, ask him to translate it. If you understand German yourselfmuch better. Enjoy!


Vorwort

\bersicht

Ungefdhr 1976 las ich in der Zeitschrift "Spektrum der Wissenschaft", Rubrik "Mathematische Knobeleien", einen Artikel von Douglas R. Hofstadter, der dem geneigten Leser auf spielerische Art die Programmiersprache LISP beibrachte. Ich war geneigt. Mit dem Bleistift und reichlich unbeholfen lvste ich die \bungsaufgaben. Und unbeleckt von jeglichem Vorwissen erkundigte ich mich sofort bei einem Thekennachbarn, Studiengang Elektrotechnik, nach einem programmierbaren Taschenrechner mit LISP …

In der Dra der Homecomputer erschien eine LISP-Implementierung f|r das Wohnzimmerwunder C64. 1987 kaufte ich meinen ersten XT f|r sensationelle 2 Mille, Monitor gr|n, und selbstverstdndlich kam Xlisp drauf, preisg|nstiger geht’s nimmer. 1990 leistete ich mir eine legale Lizenz f|r PC-Scheme von Texas Instruments und stellte fest: Scheme ist mein Lieblingslisp. Denn es ist fix und vor allem einfach!

  nach oben

So einen sollte jeder haben!

Heute gibt es Perl5 als beinahe plattformunabhdngige Skriptsprache. Lduft |berall, kennt kaum Speichergrenzen, kann Daten referenzieren, ist umsonst, und vor allem: es macht ungefragt einen Garbage Collect, wenn Not im Speicher ist. – Ein Perl-Programm ist – nach Aussage des Autors Larry Wall – in der Laufzeit langsamer, aber in der Entwicklung schneller als C. – Programming Republic of Perl Setzen wir einen drauf und sagen: Ein LISP-Programm ist in der Laufzeit langsamer, aber in der Entwicklung schneller als Perl, und begeben uns an die Feierabendaufgabe, LISP in Perl zu implementieren. (Nichts gegen Perl. Es ist durch und durch prima. Aber warum nicht beides?)

Dieser Text dient als Begleitlekt|re f|r den, der den Quelltext liest oder eigene Erweiterungen vornehmen will. Er ist auch f|r den interessierte Laien geeignet, der reinschnuppern will. Zum Verstdndnis brauchen Sie, lieber Leser, etwas Vorkenntnis |ber die Speicherverwaltung von LISP – und Sie sollten ein bi_chen Perl kvnnen.

Wir gehen nach der Strategie Bottom-Top vor. Wir beschrdnken uns auf die dokumentierten Fdhigkeiten von Perl – wir vermeiden unverstdndlichen Hacks. Einen Knoten realisieren wir z. B. mit einer Referenz auf ein Feld mit zwei Elementen. Im Dokument perlref.html erfahren Sie, wie Referenzen funktionieren.

Zum zitierten Quelltext in diesem Artikel: Sie finden hier eine verstdndlich aufbereitete Fassung der einzelnen Funktionen vor, die dem Verstdndnis zugute kommt. Im Quelltext steht meist eine Kurzfassung, die zugunsten des Durchsatzes auf private Variablen verzichtet, sich aber nicht so fl|ssig liest.

  nach oben

\bersicht

Die Shell, Anatomie eines Knotens, Daten und Typen, Listen, Einlesen der Benutzereingabe, Ausdr|cke evaluieren, Spezialformen, Primitive Funktionen, Makros, Zusammenfassung, Erweiterungen, Was der Meister sich verkniff, Ausblick; Erweiterungen 1.1, Bekannte Fehler.

Anhang: Technik, Besonderheiten, Makros, Restriktionen, Spezialformen, Perl, Funktionen, Glossar.

  nach oben

Die Shell

Beim Entwickeln verwenden wir Projekt-Shell.pl, so da_ wir die Zwischenresultate interaktiv kontrollieren kvnnen:

$prompt = "Perl> ";
print $prompt;
do { my($commandline);
     my @result = eval ($commandline);
     print "=> " . (join (", ", @result)) . ";" ;
     print $@; # Fehlerstatus
     print "\n$prompt";
} until eof;

Die Sorte von Shell kennen wir bereits von LISP. Sie liest eine Eingabezeile, schickt sie durch die Routine eval() und beantwortet unsere Anfrage ohne Umschweife in der ndchsten Zeile.

  nach oben

Anatomie eines Knotens

LISP arbeitet mit Knoten. Perl arbeitet auf Wunsch mit Referenzen (symbolischen Zeigern) auf allerlei Daten. Einen LISP-Knoten realisieren wir in Perl mit einer Referenz auf ein Feld (Array), das zwei Elemente hat. Element 1 ist das CAR, Element 2 das CDR. Beide Elemente m|ssen Skalare sein ("einfache" Werte, also eine Zahl, ein Wort, eine Referenz. Felder und Assoziativlisten scheiden aus.)

So bilden wir einen Primitivknoten:

sub ConsLowLevel {
     my $car = shift();
     my $cdr = shift();
     my @pair = ($car, $cdr);
     my $reference = \@pair;
     return($reference) };

Die privaten Skalare $car und $cdr nehmen die Funktionsargumente auf. Das private Feld @pair erhdlt die Skalare $car und $cdr als Elemente. Der private Skalar $reference nimmt die Referenz auf @pair auf. Die Funktion |bergibt als R|ckgabewert diese Referenz. – Den gleichen Effekt hat die k|rzere Version sub ConsLowLevel { [@_] }, die ohne private Variablen auskommt. Welche Version wir verwenden, ist Geschmackssache.

Und so sieht die Funktion aus, die den Primitiv-CAR ausliest:

sub CarLowLevel {
     my $reference = shift();
     my @pair = @$reference;
     my ($car, $cdr) = @pair;
     return $car };

Im Quelltext node.pl finden Sie die didaktisch nicht ganz so gehaltvolle Kurzfassung sub CarLowLevel {$_[0]->[0]}, deren Analyse wir uns hier schenken. Wer sie kryptisch findet, sei auf die Perl-Dokumentation verwiesen, insbesondere perllol.html.

  nach oben

Daten und Typen

In Pascal z. B. sind die Datentypen mit den Variablen verbunden, in LISP nicht. Deswegen ist LISP aber nicht typfrei, sondern verbindet die Datentypen direkt mit den Werten. Wir realisieren ein Datum als Primitivknoten, dessen CAR den Typ und dessen CDR den Wert des Datums enthdlt. Damit kann eine Rechenoperation zur Laufzeit |berpr|fen, ob sie auch die richtige Futtersorte hat.

Die Datentypen Symbol, Pair, … werden durchnumeriert mit 1, 2, …, und damit bauen wir die Funktionen, welche die Daten mitsamt Typ erzeugen:

sub MakeSymbol { &ConsLowLevel (1, @_) } ;
sub MakePair { &ConsLowLevel (2, @_) } ;
# ()

Die Typ-Kontrolle erfolgt dementsprechend mit Abfrage des Primitiv-CAR:

sub IsSymbol { &CarLowLevel (@_) == 1 } ;
sub IsPair { &CarLowLevel (@_) == 2 } ;
# ()

Die Wertabfrage des Datums geschieht entsprechend:

sub GetValue { &CdrLowLevel (@_) }

Was immer der Wert des Datums ist, er steht im CDR des Primitivknotens – sei es ein Skalar, dann darf er kvrperlich drin sein, oder sei es eine komplexe Datenstruktur, dann mu_ es eine Referenz darauf sein. Beim Datentyp Vektor z. B. ist der Wert des Datums eine Referenz auf ein Perl-Datenfeld.

Wir brauchen hier noch nicht zu bestimmen, wie die Datentypen im einzelnen zu handhaben sind – sie sind jederzeit feststellbar, und kvnnen wir die einzelnen Fdlle spdter behandeln.

$apfel = &MakeSymbol("APPLE");
$NIL = &MakeBoolean("");
&IsBoolean($NIL) ` 1

Stop. Der Revised4 Report sagt aus, da_ die Leere Liste nil nicht zu "falsch" evaluieren mu_, sondern je nach Implementierung auch als "wahr" gewertet werden darf, und weiter: NIL ist nicht vom Typ boolean. Das tut weh. Ich bleibe bei "falsch", bitte mich nicht pr|geln.

  nach oben

Listen

Der fundamentale Typ von zusammengesetzten Daten ist die Liste. Sie besteht aus einer Verkettung von Knoten. Das letzte Element ist die Leere Liste nil. Zusammengesetzt wird ein Knoten mit der Funktion &CONS(), die ein LISP-Datum des Typs PAIR erzeugt. Im Quelltext pair.pl finden Sie die Kurzfassung des Perl-Codes, hier die etwas verstdndlichere Langfassung:

sub CONS {
     my $car = shift();
     my $cdr = shift();
     my $node = &ConsLowLevel($car,$cdr);
     my $pair = &MakePair($node);
     return $pair };

Dazu die Funktion:

sub CAR {
     my $pair = shift();
     my $node = &GetValue($pair);
     return &CarLowLevel($node) };

und dementsprechend die Funktion &CDR(), und ein Grundstock f|r die Listenarithmetik ist gelegt – na ja, noch nicht ganz. Was soll passieren, wenn wir aus Versehen die Funktion &CAR() auf ein Symbol anwenden, z. B. $apfel (` APPLE)? Jawohl, genau richtig, eine Fehlermeldung!

sub CAR {
     my $pair = shift();
     &IsPair($pair) || die (&TypeMismatchMessage("CAR", "PAIR", $pair));
     my $node = &GetValue($pair);
     return &CarLowLevel($node) };

Die Perl-Funktion die() ist hier die Ultima Ratio: Datentyp falsch – will tot umfallen. Und weil das zu den Hauptfehlerquellen zdhlt, lohnt die Funktion &TypeMismatchMessage(), die den Absturz in simpler englischer Grammatik begr|ndet, z. B. CAR on non-PAIR 'APPLE' (is a SYMBOL). Sie ruft ihrerseits die Funktion &PrintForm() auf, die das LISP-Datum ins Menschliche |bersetzt.

\brigens sind Listen teilbar (genau das tun die Funktionen CAR und CDR). Alles andere gilt als unteilbar. Darum gilt in LISP alles, was nicht Liste ist, als Atom.

  nach oben

Einlesen der Benutzereingabe

Die klassische Benutzerschnittstelle von LISP ist die Programmschleife, welche die Eingabe vom Benutzer einliest, berechnet und ausgibt (read-eval-print-loop, abgek|rzt REP, hat nichts zu tun mit irgendwelchen politisch kackfarbenen Umtrieben.)

Als Grundlage brauchen wir einen dazu Parser, der aus einem |bergebenen Text ein LISP-Datum macht. Das geschieht mit Mustervergleich. Perl fdhrt dabei zu Hvchstform auf.

Trotzdem m|ssen wir ein kleines bi_chen "um die Ecke" denken. Falls der Parser sich rekursiv selbst aufrufen mu_, mu_ er stdndig auf dem Laufenden sein |ber das aktuelle Zwischenresultat (der abgearbeiteten Zeichenkette) und den Rest. Also |bergibt der Parser als Resultat ein Feld, dessen erstes Element das teilberechnete LISP-Datum ist; das zweite Element enthdlt den Rest der Kommandozeile, der noch abzuarbeiten bleibt. (Perl pur, diesmal keine Knoten!)

Der Parser erkennt aus der Benutzereingabe die Datentypen Buchstabe, Wort, Zahl, Wahrheitswert, Symbol, Vektor und Liste. Die |brigen Datentypen sind nicht unmittelbar einzugeben, sondern entstehen bei Berechnungen in LISP.

sub ParseAtom {
     my $str = shift;
     my $port = shift;
     my @result;
     ($str, $port) = &input($str, $port);
     $str =~ s/^\s*//; # f|hrende Leerzeichen entfernen
     if ($str eq '' && $port eq 'eof') { @result = ($EOF, $str) } # eof
     elsif ( $str =~ /^(#e|#i)?$NumberPattern/io ) { # Number
         my @arr = &ParseNumArr($&);
         @result = (&MakeNumber(\@arr), $') }
     #  Buchstabe, Wort, 
     elsif ($str =~ s/^'//) { # quote
         my ($qval, $rest) = &ParseAtom($str);
         my $first = &MakeList(&MakeSymbol("QUOTE"), $qval);
         @result = ($first, $rest) }
     #  Quasiquote, Unquote, Unquote-Splicing 
     elsif ($str =~ s/^\(//) { # Listen
         @result = &ParseList($str) }
         else { die "Don't understand input '$str'"}; # sonst stimmt was nicht.
     @result }

Der Parser ist |brigens die Instanz, die das 'Hochkomma als Abk|rzung f|r (quote …) behandelt, ebenso das umgekehrte Hochkomma f|r (quasiquote …), das Komma f|r (unquote …) und das Komma mit Klammeraffen f|r (unquote-splicing …).

Die Funktion &ParseAtom() erhdlt als Argument die Benutzereingabe und den Port. Falls die Eingabe sich |ber mehrere Zeilen erstreckt (bei Listen und Vektoren), f|llt sie mit $input() die Zeile wieder auf.

Die Funktion &ParseList() macht aus Klammerausdr|cken Listen. Dabei ruft sie f|r die atomaren Elemente wiederum &ParseAtom() auf. Sie unterscheidet zwischen "ordentlichen" und "unordentlichen" Listen (solchen, deren innerstes CDR nicht nil ist), bietet aber ansonsten nicht viel neues und ist deshalb hier ausgelassen.

Interne Zahlendarstellung

Oben habe ich gesagt, da_ wir uns erst bei Bedarf um die Behandlung der einzelnen Datentypen zu k|mmern brauchen. Sie d|rfen mich beim Wort nehmen, jetzt sind die Zahlen dran. – Das Zahlen-Vergleichsmuster fdllt etwas ldnglich aus und wird deshalb aus mehreren Teilmustern zusammengesetzt, die letztlich die Standard-Zahleneingaben erkennen und f|r LISP aufbereiten.

Der Scheme-Standard sieht vor, da_ die Zahlen genau und ungenau sein d|rfen. Wir stellen eine Zahl als Referenz auf ein Feld dar, dessen erstes Element den Zdhler und dessen zweites den Nenner der Zahl enthdlt. – Weiter: der Standard sieht komplexe Zahlen vor. Das Feld enthdlt also weitere zwei entsprechende Eintrdge f|r den imagindren Anteil.

Wenn wir also eine Zahl in der Form 3/4+2/5i eingeben, ruft der Parser unterm Strich die Funktion &MakeNumber([3, 4, 2, 5]) auf. Die Zahl in diesem Beispiel ist genau. – Ungenaue Zahlen kennzeichnen wir durch den Nenner 0, z. B. &MakeNumber([0.75, 0, 0.4, 0]). Das ist verwechslungssicher, denn bekanntlich darf nicht durch 0 dividiert werden.

Um die Genauigkeit durch mvglichst viele Rechenoperationen hindurch zu retten, mu_ unser LISP den Bruch bei Bedarf k|rzen. Um z. B. die Zahl 48/56 darzustellen, ermittelt die Perl-Funktion &ggT(48, 56) deren grv_ten gemeinsamen Teiler, also 8, und wir k|rzen damit zu 6/7.

  nach oben

Gro_er Zahlenkompromi_

Die interne Zahlengenauigkeit in meinem verwendeten Perl ist md_ig. Als Kriterium f|r Genauigkeit verwende ich den Mustervergleich auf den Buchstaben e im Zahlwort (in Perl ist es Wurscht, ob eine Zahl als "richtige" Zahl oder als Wort eingesetzt ist.) Falls also der Buchstabe e auftaucht, gilt die Zahl als ungenau. Falls z. B. die Funktion & ggT(1e45, 5)darauf stv_t, warnt sie: can’t compute lcm(1e45, 5) - returning 1 – und tut es auch. Was soll’s.

  nach oben

Kleiner Zahlenkompromi_

Intern rechnet dies LISP mit der vollen verf|gbaren Zahlengenauigkeit, aber bei der Bildschirmdarstellung der Zahlen wird die 14. Nachkommastelle kaufmdnnisch gerundet, so da_ die Funktion (cos (- pi)) auf dem Bildschirm 0.0 produziert, obwohl es bei meinem Perl intern -1.22514845490862e-16 sind. Wenn die Rechenungenauigkeit genau wissen wollen, verwenden Sie die Funktion (number->string). Wenn Sie die volle Wahrheit auf dem Bildschirm haben wollen, entfernen Sie in der Funktion &NumberPrintForm() den Funktionsaufruf &ungefaehr() (siehe Quelltext pair.pl). – Ach so, Kosinus und PI sind in diesem LISP nicht drin, aber Sie kvnnen es definieren, das Werkzeug ist da.

  nach oben

Ausdr|cke evaluieren

Wir haben die fundamentalen LISP-Funktionen &CONS(), &CAR(), &CDR()ganz normal in Perl programmiert. Das ld_t vermuten, da_ Perl von sich aus imstande ist, LISP-Programme auszuf|hren. Die Vermutung stimmt. Die Unterschiede sind rein syntaktischer Natur, und sie lassen sich an einer Hand aufzdhlen.

Die Funktion &GeneratePerlSource($expr) macht aus einem LISP-Ausdruck einen Perl-Quelltext, den der Evaluierer &EvalExpr()der Perl-Funktion eval() zuf|hrt. Die Details:

  nach oben

Symbolnamen

LISP-Symbole schvpfen aus einem grv_eren Zeichenvorrat als Perl-Variablen. Perl kann zwar auch Variablennamen aus Sonderzeichen zusammenstellen, z. B. ${'---'}, aber nicht innerhalb von privaten Umgebungen via my – dort geht es strikt alphanumerisch zu. Also ersetzen wir jedes Sonderzeichen durch seinen ASCII-Wert, dreistellig, mit f|hrendem Unterstrich. Zur Vermeidung von Kollisionen erhdlt jeder Variablenname zusdtzlich einen f|hrenden Unterstrich. Mit der Funktion &Lisp2PerlName("+") erzeugt der Quelltextgenerator z. B. aus dem Symbol + den Variablennamen ${__043}.

  nach oben

Symbolwerte

Wenn eine Perl-Variable noch nicht definiert ist, dann erzeugt sie den Undefinierten Wert. Wenn ein LISP-Symbol noch nicht definiert ist, setzt es eine Fehlermeldung. Bei Verwendung des Symbols PI mu_ der Quelltext also lauten: ( $_PI || die ("unbound: PI")). Das Programmteil, die den Quelltext erzeugt, lautet also:

{ my $name = &GetSymbolName($expr);
  my $Pname = &Lisp2PerlName($name);
  "($Pname || die ('unbound: $name')" }

Falls ein Symbol sicher existiert, also als formales Argument innerhalb eines LABDA-Ausdrucks, entfdllt die Pr|fung. Siehe eval.pl und special.pl.

  nach oben

Unmittelbar darstellbare Konstanten

Eine Konstante stellen wir im Quelltext dar wie in "Daten und Typen" beschrieben: als Referenz auf ein anonymes Feld, das den Typ und den Wert der Konstanten enthdlt. Das Wort "a+b" steht im Quelltext als [3, "a\+b"], sicherheitshalber entschdrft mit quotemeta("a+b"), die Zahl 1 als [4, [1, 1, 0, 1]]. Der Programmteil f|r Worte und Buchstaben lautet "[$expr->[0], \"" . quotemeta($expr->[1]) . "\"]", der f|r Zahlen "[$expr->[0], [" . join(", ", @{$expr->[1]}) . "]]".

  nach oben

Berechnete Konstanten

Ein LISP-Ausdruck kann auch eine Konstante enthalten, die nur aus Berechnungen entstehen kann, z. B. eine zyklische Liste. Wie kriegen wir diese in einen Quelltext? Wir schreiben ihre Referenz in die Assoziativliste %Val und schreiben den Schl|ssel in den Quelltext. Als Schl|ssel nehmen wir die Bildschirmreprdsentation der Referenz, z. B. ARRAY(0x796b20). Die Funktion &GenerateValueSource() tut das und |bergibt z. B. den Quelltext $Val{'ARRAY(0x796b20)'} zur|ck. – Eine Stolperfalle besteht darin, da_ die Perl-Dokumentation nichts aussagt |ber die Konsistenz dieser Reprdsentation. Also testet die Funktion, ob der gleiche Schl|ssel mvglicherweise schon f|r einen anderen Wert existiert, und zdhlt notfalls hoch, z. B. $Val{'ARRAY(0x796b20)a'}

  nach oben

Listen

Eine Liste ist normalerweise ein Funktionsaufruf. Der LISP-Ausdruck (list apfel birne)mu_ im Prinzip zum Quelltext do { my $function = ${_LIST}; my $code = $function->[1]; &$code(${_APFEL}, ${_BIRNE}) } umgeformt werden. Die Perl-Variablen f|r die Funktion listund die Argumente apfel, birnewerden gewonnen wie in "Symbolwerte" beschrieben. Im Quelltext steht $f und $c anstelle $function und $code, au_erdem finden Sie einen Test auf den Datentyp "Funktion".

  nach oben

Spezialformen

Ohne die Spezialform ist kein LISP lebensfdhig. Zu den fundamentalen zdhlt die wenig beachtete QUOTE, die sich hinter dem 'Hochkomma versteckt.

F|r die Spezialformen legen wir eine Assoziativliste %SpecialSource an, deren Schl|ssel die Symbolnamen und deren Werte Quelltextgeneratoren sind.

Unser Quelltextgenerator &GeneratePerlSource mu_ so erweitert werden, da_ er bei einer Liste feststellt, ob sie eine Spezialform bildet, und zwar mit $SpecialSource{&GetSymbolName(&CAR($expr))}.

  nach oben

Quote

Die QUOTE definieren wir so: $SpecialSource{'QUOTE'} = sub { &GenerateQuotedSource(&CAR(@_)) }. Tja, und sofort brauchen wir einen weiteren Quelltextgenerator, der sich von dem obigen nur dadurch unterscheidet, da_ er Symbole und Listen "so nimmt, wie sie sind". Siehe special.pl und eval.pl.

Die Spezialform set! |berschreibt eine Variable dann, wenn diese existiert – falls nein, Fehlermeldung. – Theoretisch kvnnten wir auch define als Spezialform anlegen. Tun wir aber nicht, das gibt’n Makro. Zum Definieren verwenden wir den Namen **define**.

  nach oben

Lazy Evaluation

Zu den Spezialformen zdhlen u. a. IF, OR, AND. Diese evaluieren ihre Ausdr|cke nachtrdglich bei Bedarf (lazy evaluation). Beispiel:

Lisp> (or "so wahr ich hier stehe" was f|r'n Quatsch)
` "so wahr ich hier stehe"

OR hvrt auf zu rechnen, sobald es auf einen wahren Ausdruck stv_t – also einen, der nicht zu NIL evaluiert. Dahinter darf jeder Quatsch der Welt stehen, z. B. auch gerne mal ein Symbol, das gar nicht definiert ist. Pfusch? Nein, sondern serivse Kurzschlu_logik.

  nach oben

LAMBDA

LAMBDA ist in Scheme nichts weiter als eine Spezialform, die eine Funktion erzeugt. Aus dem Ausdruck (lambda (x) (list x x)) macht sie im Prinzip den Quelltext [7, sub { my ${_X} = (shift())); do { my $f = ${_LIST}; my $c = $f->[1]; &$c(${_X}, ${_X}) } }], plus Kontrolle auf korrekte Argumentenzahl etc. Siehe lambda.pl.

  nach oben

Primitive Funktionen

Betrachten wir eine Primitive Funktion – also eine, die wir nicht in LISP realiseren kvnnen oder wollen – z. B. list:

$_LIST = &MakeFunction(\&MakeList)

(Die Funktion &MakeList gibts schon, die gehvrt zu den Butter-und-Brot-Funktionen in unserem Lisp.) K|rzer geht’s kaum. Probe? Probe:

Lisp> (list 'apfel'birne)
` (apfel birne)

Eine weitere Vereinfachung bildet die Hilfsfunktion &deffunc("name", sub { … } ), die den Funktionsnamen nach Perl |bersetzt, und die von sich aus wei_, da_ der Wert vom Typ Funktion sein soll. – Einige Funktionen, z. B. Vergleichsfunktionen, definieren wir nicht einzeln, sondern am St|ck in einer for-Schleife. Das ist k|rzer und weniger tippfehleranfdllig.

  nach oben

Die vier Grundrechenarten

Das Multiplizieren zweier rationaler Zahlen ist trivial (Zdhler mal Zdhler durch Nenner mal Nenner und k|rzen). Zum Addieren werden die Br|che gleichnamig gemacht, die Zdhler addiert und die Summe anschlie_end ebenfalls gek|rzt. Die Funktion &add2($z1, $n1, $z2, $n2) addiert und &mul2($z1, $n1, $z2, $n2) multipliziert Zdhler/Nenner-Paare unter Ber|cksichtigung der Genauigkeit, das hei_t: wenn die eine Zahl ungenau ist, wird die andere ungenau gemacht, und nur die Zdhler werden berechnet. (Ausnahme: falls eine genaue 0 mit einer ungenauen Zahl multipliziert wird, kommt eine genaue 0 heraus.)

Das Addieren zweier komplexer Zahlen geschieht durch Addition der reellen und der imagindren Komponenten, die Subtraktion durch Addition mit dem Negativwert. – Das Multiplizieren geht nach der Regel "jeder mit jedem". Die Funktion &add4(…) addiert, &mul4(…) multipliziert Real/Imagindr-Gruppen.

Zum Dividieren multiplizieren wir mit dem Kehrwert. Mit Herrn Binomi fdllt das gar nicht schwer: Um zur Zahl (a+b*i) den Kehrwert 1/(a+b*i) zu finden, erweitern wir den Bruch um (a-b*i), ergibt nach der 3. binomischen Regel (a-b*i)/(a2+b2) oder a/(a2+b2)-b/(a2+b2)*i – k|rzen, und aus. Das erledigt f|r uns die Funktion &Kehrwert(…). Siehe number.pl.

  nach oben

Call-with-current-Continuation

Diese Funktion, abgek|rzt call/cc, dient dem "geordneten R|ckzug" aus einem komplizierten Geflecht aus Funktionsaufrufen. Sie verwendet eine lokale Variable namens $CallCCResult und eine private LISP-Funktion mit dem Perl-Namen $exitfunc, die einfach nur ihr Argument in $CallCCResult schreibt und anschlie_end "stirbt".

Die call/cc erwartet als Argument einen LAMBDA-Ausdruck, dem sie als Argument $exitfunc mit auf die Reise gibt. Innerhalb des LAMBDA-Ausdrucks hat der Verbraucher freie Bahn – wenn er der Meinung ist, es sei zu viel des Guten, ruft er die Ausstiegsfunktion auf, und diese liefert dann das Resultat der Rechenoperation. Beispiel? Beispiel:

Lisp> (**define** reelle_wurzel
         (lambda (x)
             (call/cc (lambda (abbruch) ; der "Abbruch" wird zur Ausstiegsfunktion.
                 (if (< x 0) (abbruch 'geht-nicht!)) ; die Ausstiegsfunktion "Abbruch" verhindert den Rest.
             (sqrt x)))))
` REELLE_WURZEL
Lisp> (reelle_Wurzel 4)
` 2.0
Lisp> (reelle_Wurzel -4)
` GEHT-NICHT!

Wie ist es gemacht? Die Perl-Funktion eval sorgt daf|r, da_ nicht der "Tod" von LISP eintritt, sondern nur der Abbruch der aktuellen Funktion. Anschlie_end schaut call/cc nach, ob der LAMBDA-Ausdruck etwas nennenswertes hervorgebracht hat – falls nein, hat die Ausstiegsfunktion zugeschlagen, und das Resultat steht im $CallCCResult. Nur, wenn da auch nichts drinsteht, gab es ein echtes Problem, das in der Variablen $@steht. – Der Code im Kern: eval { &$c($exitfunc) } || $CallCCResult || die ("$@") }

  nach oben

Makros

Die Beliebtheit von LISP steht nicht nur f|r seine Mdchtigkeit, sondern auch f|r seine Einfachheit. Ist unser LISP schon einfach? Na ja, es gdbe da noch einige Verbesserungsmvglichkeiten, z. B. lokale Bindungen mit dem let-Konstrukt.

Das Makro erlaubt uns, aus einer relativ menschenfreundlichen Syntax einen reguldren Ausdruck zu machen, z. B. aus (let ((a pi) (b 2)) (* a b)) den Ausdruck ((LAMBDA (a b) (* a b)) pi 2). Das Makro ist die Patentante des Prdprozessors bei C.

F|r das Makro legen wir eine Assoziativliste %Macro an, deren Schl|ssel die Makro-Namen und deren Werte LISP-Funktionen enthalten. (Makros programmieren wir prinzipiell in LISP.) Wir realisieren den Makro-Generator als Spezialform:

$SpecialSource{MACRO} = sub {
  my $expr = shift;
  my $sym = &CAR($expr);
  my $MacroFunc = &CADR($expr);
  "do { \$Macro{'" 
    . &GetSymbolName($sym) 
    . "'} = " 
    . &GeneratePerlSource($MacroFunc)  
    . ";" 
    . &GenerateQuotedSource($sym) # Makro-Symbol als R|ckgabewert
    . " } " }

Und jetzt m|ssen wir &GeneratePerlSource so erweitern, da_ er zufvrderst auf Makro testet und ggf. umformt:

while (&IsPair($expr) && $Macro{&GetSymbolName(&CAR($expr))}) { # Makro
  my $MacroSym = &CAR($expr); # 'let
  my $MacroName = &GetSymbolName($MacroSym); # "LET"
  my $MacroFunc = $Macro{$MacroName}; # #<code ...>
  my $PerlCode = &GetFunctionCode($MacroFunc); # code ...
  $expr = &$PerlCode($expr) } # Expansion!

Nun kvnnen wir das Makro let definieren:

(macro let
     (lambda (bindings . body)
         (cons 
             (cons 'lambda
                  (cons (map car bindings) body))
             (map cadr bindings))))
` LET
Lisp> (let ((a 1) (b 2)) (+ a b))
` 3

Na ja, war relativ einfach. Bei komplizierten Makros brauchen wir aber zur Vorsorgeuntersuchung etwas, das uns die Makro-Expansion einmal "trocken" vorf|hrt. Die Funktion macrocode |bergibt den Code eines Makros. Der Ausdruck (macrocode 'let) z. B. |bergibt den Code des Makros let. Damit kvnnen wir die Umformung direkt beobachten, anstatt |ber Fehler zu rdtseln:

Lisp> ((macrocode 'let) '(let ((a 1) (b 2)) (+ a b)))
` ((LAMBDA (A B) (+ A B)) 1 2)

Die quasi-quote erlaubt die Evaluierung einzelner Ausdr|cke innerhalb ihres Arguments mit den Schl|sselwvrtern unquote und unquote-splicing (abgek|rzt: ,<expr> und ,@<expr>). Das Makro define erspart uns die ldstige LAMBDA-Anweisung: (define (function arg1 …) …). Das Makro let* erzeugt einen verschachtelten let-Ausdruck, bei dem jedes Symbol dem nachfolgend definierten bekannt ist; bei letrec kennen die Symbole einander und sich selbst – n|tzlich bei lokalen Funktionen. Das Makro named-lambda macht Gebrauch davon und erzeugt eine Funktion, bei der Selbstaufrufe auch nach Umtaufe funktionieren. cond |berf|hrt eine |bersichtliche Fallunterscheidung in einen verschachtelten if-Ausdruck, case macht aus einer switch-dhnlichen Syntax einen cond-Ausdruck. Die Makros do und do* erzeugen eine abweisende bedingte Programmschleife.

  nach oben

Aber cave!

Der innere Ablauf bei der Makro-Umformung zeigt eine Einschrdnkung auf: Beim Kompilieren einer Funktion werden alle Makros vollstdndig angewendet. Also mu_ das Makro vorher bekannt sein. Sollten Sie eine Funktion definieren, die ein Makro verwendet, und das aufgerufene Makro erst spdter definieren, dann "denkt" der Compiler, es handele sich um eine normale Funktion – ein Aufruf des Makros als Funktion f|hrt zum Fehler.

Das zwingt zur Erkenntnis: Das Makro wirkt innerhalb von LISP eigentlich als Stvrenfried. F|r die reine Funktionalitdt wdre es absolut verzichtbar. Seine einzige Daseinsberechtigung ist seine Benutzerfreundlichkeit.

Falls benutzerdefinierte Makros wirklich erforderlich sind, empfehle ich diese Vorgehensweise: Definieren Sie alle nvtigen Makros "am St|ck". Stellen Sie die Definitionen an den Anfang Ihres Programms. Packen Sie sie in eine eigene Datei. Vermeiden Sie anderweitige Verwendung der Makro-Symbole, auch im mit quote "entschdrften" Zustand. Andernfalls sehe ich lange und erf|llte Abende der Fehlersuche voraus.

  nach oben

Erweiterungen

Wenn dies LISP gestartet wird, lddt es zuerst die Datei Autoload.lsp – sofern vorhanden. Hier kvnnen Sie Ihr LISP nach Gusto erweitern.

  nach oben

Zusammenfassung

Der einzige grv_ere Flaschenhals in diesem LISP ist die Benutzereingabe, bei der der eingegebene Klartext zundchst in ein LISP-Datum umgewandelt wird, dieses wiederum in Perl-Quelltext, und dieser zu Code. In der Laufzeit LISP fast so schnell wie Perl.

Den zweitgrv_te Flaschenhals bildet (eval<expr>), bei dem das LISP-Datum |ber Klartext zu Code gemacht wird.

Eine weitere Bremse f|r den Durchsatz ist der Datentyp Liste, f|r den es in Perl keine unmittelbare Entsprechung gibt.

Abgesehen davon beschrdnkt sich der Mehraufwand auf die Typ-Kontrolle bei geschdtzten 95% aller Funktionen. In summa stellen wir fest: dies LISP ist immerhin fast so schnell wie Perl. Und das ist ja wohl recht ordentlich, gell?

  nach oben

Was der Meister sich verkniff

Die Zahlenoperationen sind so genau, wie Perl eben zuld_t. Ziffernfolgen mit beliebiger Stellenzahl m|_ten ausdr|cklich programmiert werden, und das wdre bei den Bordmitteln von Perl vielleicht eine winzige Spur zu langsam  …

Unser LISP verzichtet auf einen Entlauser (siehe unten, Glossar: E). Im Fehlerfall "stirbt" das Programm und fdllt zur|ck auf die Eingabezeile. Seine Daten hat es nicht vergessen, allerdings den Absturzort. F|r den Notfall gibt es immer noch den Perl-Debugger, und f|r den Profi bleibt die Mvglichkeit, diesen LISP-spezifisch anzupassen.

Dies LISP optimiert Endrekursionen nicht zu Programmschleifen. Soll ein anderer machen. Das Werkzeug ist da (siehe Makros).

Es gibt  keinen Support, ich habe auch noch Familie. Betrachten Sie dies LISP als Hack vom Hacker f|r Hacker. Fehlerr|ckmeldungen sind nat|rlich hochwillkommen (bitte Email an Wolf-Dieter.Busch@T-Online.de)!

  nach oben

Ausblick

LISP ist eine einfache und zugleich mdchtige Programmiersprache, aber keine schnelle. Ein vern|nftiger Mensch w|rde z. B. niemals Bitmap-Manipulationen damit realisieren. Das Heimstadion von LISP ist die Logik. Die professionelle Applikation Schematext z. B. ist in LISP programmiert. Sie verwaltet gro_e Bestdnde von Internet-Dokumenten mitsamt einem komplizierten Geflecht von Hyperlinks (siehe Glossar: H). F|r alles, was |ber die Logik hinaus geht, ruft diese Applikation Fremdprogramme auf, z. B. Help-Compiler von Drittherstellern. – Andere Anwendungsbereiche sind z. B. maschinelle Roh-\bersetzungen in Fremdsprachen – jedenfalls, solange es noch keine vollstdndige Abbildung menschlicher Sprache in ein formales Modell gibt. (Wenn ich schon nicht alles Gesprochene verstehe aber lassen wir Politik und Betriebsrat au_en vor.)

Wenn ein Perl-Programm vor komplizierten logischen oder logistischen Problemen steht, kann es unser LISP einbinden. Die Funktion &CommandLisp() bildet eine pflegeleichte Klartext-Schnittstelle. Siehe unten: Technik.

Perl erlaubt es, den vollstdndigen Quelltext dieses LISP innerhalb eines Batchfiles auszuf|hren. (Vor ein paar Jahren hdtten wir |ber die Vorstellung gelacht.) Das ermvglicht praktische LISP-\bungen in Schule, Studium und Wohnzimmer. F|r den Beruf empfehle ich es nur bedingt, denn es gibt keinen Support – siehe oben.

Dies LISP ist in einer Hochsprache geschrieben, die syntaktische Dhnlichkeiten mit C hat. Vielleicht findet sich ja ein Freiwilliger, der mit einem geschickten Prdprozessor den Perl-Quelltext umformt in einen C-Quelltext? Wenn ich ein vervollstdndigtes Update nachschiebe? (Man wird ja noch fragen d|rfen )

  nach oben

Erweiterungen 1.1

In der ersten Fassung enthielt dies LISP genau den Funktionsumfang, den Scheme als wesentlich ansieht. Aber naheliegenderweise sollte es nicht weniger kvnnen als Perl.

Der Datentyp hash erlaubt schnelleren Zugriff auf Schl|ssel-Werte-Paare als die Listen vom Typ ((var1 val1) (var2 val2) ...). Die Funktion (make-hash "string1" val1 ...)erzeugt eine Assoziativliste. Mit (hash-set! hash "key" val) setzen Sie ein neues Schl|ssel-Wert-Paar ein, mit (hash-ref hash "key") rufen Sie es ab. Falls der Schl|ssel nicht vorkommt, gibt diese Funktion nil zur|ck. Mit der Funktion (hash-keys hash) erhalten Sie eine \bersicht der Schl|ssel, mit (hash-values hash) eine \bersicht der Werte.

Falls Sie mit Eigenschaften (properties) arbeiten wollen, bietet sich anstelle der klassischen assoc-Listen die wesentlich schnellere Assoziativlisten des Typs hash an. Der Schl|ssel mu_ aber ein Wort sein. Die neue Funktion object->string erzeugt aus einem beliebigen Lisp-Objekt dessen Bildschirm-Reprdsentation als Wort.

Der Mustervergleich ist primitiv programmiert: (match "abcdef" "C" "i") greift das Muster /C/ aus dem Wort "abcdef" ab – wegen des "i" nimmt es keine R|cksicht auf Gro_-Klein-Schreibung – und |bergibt als Resultat eine Liste der Bestandteile vor dem Treffer, den Treffer selbst, und dem Bereich dahinter: ("ab" "c" "def"). Wenn es keinen Treffer erzielt, |bergibt es nil.

In Abweichung von der Reinen Lehre ist jetzt die Lokale Sichtweise von Variablen eingebaut, und zwar mit dem Makro (local ((var1 val1) ...) ...), das syntaktisch analog let funktioniert, und das eine neue Spezialform lambda-local aufruft.

Die Eingabe von Zeichenketten darf jetzt mehrzeilig ausfallen. Au_erdem ist ein Fehler behoben: der Parser hat in der ersten Fassung die Sonderzeichen \" etc. nicht zuverldssig verarbeitet.

Die Funktion read-line liest eine Zeile von Tastatur oder Port und |bergibt den Inhalt als Wort. Merkw|rdigerweise nicht im Revised4 Report erwdhnt; mvglicherweise (?) deshalb, weil mit read-char programmierbar.

Aufgrund einer versehentlich gelvschten Programmzeile fehlte in der ersten Fassung die Funktion char?. Der Fehler ist behoben.

Die Symbole sind nun in einer Tabelle zusammengefa_t. Gleichnamige Symbole kommen nur einmal vor. Damit kann die Funktion eq? "ehrlich" werden und auch bei Symbolen auf wirkliche Identitdt testen anstatt wie bisher auf Typ- und Wert-Gleichheit. Erhebliche Speicherersparnis und damit reduzierte Speicherbereinigung gibts vermutlich nur, wenn man intensiv mit Eigenschaften (properties) arbeitet. (Ich hab da ein Projekt am Laufen mit deutscher Grammatik.)

Bei Eingabe eines einzelnen Punkts in der REP meldete dies LISP bisher single dot is ambigous. Das ist falsch. Richtig ist: ambiguous. Der orthographische Fehler ist behoben. (Hoffentlich hat er niemandem Kopfzerbrechen bereitet.)

  nach oben

Bekannte Fehler

Wenn beim Lesen einer Datei mit read oder load ein Fehler mit Abbruch auftritt, wird die Datei nicht geschlossen.

Wenn beim Schreiben in eine Datei ein Fehler mit Abbruch auftritt, wird die Datei nicht geschlossen.

Beim Verschachteln der Funktion call-with-current-continuation (call/cc) kann aus dem inneren call/cc-Konstrukt nur die innere Ausstiegsroutine sinnvoll eingesetzt werden, nicht die du_ere. Falls die du_ere aufgerufen wird, wird trotzdem (!) die innere ausgef|hrt.

Die Funktion map verarbeitet nur eine Liste als 2. Argument, nicht mehrere.

  nach oben

Anhang

Dies LISP enthdlt die Funktionen, die im Revised Report als essential gekennzeichnet sind. F|r die normale LISP-Syntax lesen Sie den Revised Report oder irgend ein nicht-produktspezifisches Lehrbuch zu Scheme. Hier finden Sie, was dar|ber hinausgeht, was also dort nicht vorkommen kann.

  nach oben

Technik

Speicherverwaltung – der Revised Report trifft genauere Aussagen zur Symbolbindung. Unter Perl gibt es keine Pointer, sondern Referenzen. Sollte den Anwender nicht weiter ber|hren.

Dies LISP ist geeignet f|r den Aufruf aus reguldren Perl-Programmen heraus, sozusagen im Serverbetrieb. Als Schnittstelle dient Funktion &CommandLisp(). Sie erhdlt als Argument einen Klartext, z. B. (cons 'a 'b), berechnet ihn und |bergibt das Resultat wiederum als Klartext, in unserem Beispiel (a . b). Die einzige erforderliche Dnderung im Quelltext besteht darin, den Aufruf der read-eval-print-Schleife am Ende des Programms auszukommentieren. – Wie gesagt: pflegeleicht.

Funktionen, Spezialformen, Syntaktische Besonderheiten (Makros)

assoc – erlaubt als optionales drittes Argument eine Vergleichsfunktion, z. B. (assoc 2 '((2 'Kaesekuchen) (6 nil)) =) ` (2 kaesekuchen). Ursache: Faulheit des Autors, damit er assv und assq leichter programmieren konnte. Empfehlung: Ignorieren.

**define** – Spezialform zum Definieren einer neuen Symbolbindung. Wird vom Makro define verwendet.

(die [expr]) – Funktion, f|hrt zu Abbruch des Programms (Ausstieg zur Eingabezeile). Zweck: Fehlerkontrolle zur Entwicklungs- und Laufzeit.

function? – funktionsgleich mit procedure?. Zweck: Unterst|tzt das Verstdndnis von LISP im Schulgebrauch; kommt dem dsthetischen Empfinden des Autors entgegen. Empfehlung: Schlie_en Sie sich mir an!

member – erlaubt als optionales drittes Argument eine Vergleichsfunktion, z. B. (member 6 '(2 6 3)) =) ` (6 3). Ursache: Faulheit des Autors, damit er memv und memq leichter programmieren konnte. Empfehlung: Ignorieren.

(perl string) ` (string1 … stringn) – Funktion, berechnet einen Perl-Ausdruck und |bergibt das Resultat als Liste von Wvrtern. Erwartet als Argument ein Wort. Zweck: Schnittstelle nach "drau_en", z. B. zur Nutzung der Spezialfdhigkeiten von Perl, oder f|r Implementierung einer Funktion system, die den Kommandoprozessor lddt. Empfehlung: Klasse, um das System abzuschie_en!

type-of – Funktion, |bergibt den Typ des Datums expr als Symbol. Zweck: Entlausen. Empfehlung: Entfernen Sie nach Fertigstellung des Programms alle type-of-Kontrollen. Beispiele (das Symbol Apfel ist gebunden an das Wort "gegen Karies"):

(type-of 'apfel)
` SYMBOL
(type-of apfel)
` STRING

**until** – Spezialform f|r Programmschleifen. Wird von den Makros do und do* verwendet.

  nach oben

Makros

Die Spezialform macro ist ziemlich locker angelehnt an die unverbindlichen Empfehlungen des Revised Report. Beispiel:

(macro let*
     (lambda (expr)
         (define bindings (cadr expr))
         (define body (cddr expr))
         (if (null? (cdr bindings))
             `(let ,bindings ,@body)
             `(let (,(car bindings))
                 (let* ,(cdr bindings) ,@body)))))
` LET*

Die Funktion macrocode |bergibt den Code eines Makros. Zweck: Entlausen. Beispiele:  

(macrocode 'let*)
` #<Code(0x8718c4)>
((macrocode 'let*) '(let* ((a 'wurst) (b "Kdse")) (list a b)))
` (LET ((A (QUOTE WURST)))
         (LET* ((B "Kdse")) (LIST A B)))

Die Funktion expand-macro lvst alle Makros innerhalb eines Ausdrucks auf. Beispiel: (expand-macro '(let* ((a 'wurst) (b "Kdse")) (list a b))) ` ((LAMBDA (A) ((LAMBDA (B) (LIST A B)) "Kdse")) (QUOTE WURST))

Um eine \bersicht der Makros abzurufen, empfehle ich diese Definition:

(define (macros) (map string->symbol (perl "keys(%Macro)")))
  nach oben

Restriktionen

Die Funktionen, die nicht als essential gekennzeichnet sind, sind nicht vollstdndig implementiert. Die Funktion peek-char ist nicht implementiert.

Spezialformen

LAMBDA, OR, MACRO, AND, **DEFINE**, BEGIN, **UNTIL**, SET!, QUOTE, IF, LAMBDA-LOCAL

Syntaktische Formen (Makros)

QUASIQUOTE, DO, LOCAL, NAMED-LAMBDA, DEFINE, CASE, COND, DO*, LET, LETREC, LET*

  nach oben

Das verwendete Perl

C:\WINDOWS\DESKTOP perl -v
This is perl, version 5.004_02
Copyright 1987-1997, Larry Wall
GNU General Public License, which may be found in the Perl 5.0 source kit.
Gurusamy Sarathy (Just Another Perl Porter)
  gsar@umich.edu
  08-AUG-1997
http://www.perl.com/CPAN/ports/win32/Gurusamy_Sarathy/
  nach oben

Realisierung der Funktionen

A B C D E F G H I L M N O P Q R S T V W Z

+ - * / die vier Grundrechenarten. Beispiel: (- 4 3 5) ` -4

< <= > >= arithmetische Vergleichsfunktionen. Beispiel: (< 4 11 16 1.435e20) ` #t

A

Funktionen

abs ermittelt den Absolutwert einer Zahl. Beispiel: (abs -13) ` 13

angle ermittelt den Winkel einer komplexen Zahl in geometrischer Darstellung. Beispiel: (angle -1) ` 3.1415926535898

append hdngt mehrere Listen zusammen. Beispiel: (append '(a b) '(c d) '(e f)) ` (a b c d e f)

apply wendet eine Funktion auf eine Liste an. Beispiel: (apply + '(3 4 5)) ` 12

assoc, assq, assv durchsucht eine Liste von Listen nach einem bestimmten Element. assocverwendet als Vergleichsfunktion equal?, assv verwendet eqv?, assq verwendet eq?. Beispiel: (assoc 'a '((1 2) (a b) (apfel birne))) ` (a b)

B

Funktionen

boolean? stellt fest, ob sein Argument ein Wahrheitswert ist. Beispiel: (boolean? nil) ` #t

C

Funktionen

car – ermittelt das erste Element einer Liste. Komplement zu cdr. Beispiel: (car '(a b c)) ` a

cdr – ermittelt den Rest einer Liste ohne das erste Element. Komplement zu car. Beispiel: (cdr '(a b c)) ` (b c)

caar, cadr, cdar, cddr, caadr, … cddddr – Kombinationen aus car und cdr – insgesamt 28 Kombinationen. cadr z. B. ist so definierbar: (define (cadr l) (car (cdr l))). Beispiel: (cadr '(a b c))` b

ceiling ermittelt die ndchsthvhere Ganzzahl. Beispiel: (ceiling 5.2) ` 6

char? stellt fest, ob ein Objekt ein Buchstabe ist. Beispiel: (char? #\a) ` #t

char->integer findet den ASCII- bzw. ANSI-Wert eines Buchstaben. Hinweis: Verwendet die Perl-Funktion ord(). Beispiel: (char->integer #\a) ` 65

char-alphabetic? stellt fest, ob ein Buchstabe alphabetisch ist. Beispiel: (char-alphabetic? #\9) ` ()

char<=?, char<?, char=?, char>=?, char>? vergleicht Buchstaben. Beispiel: (char=? #\a #\A)` ()

char-ci<=?, char-ci<?, char-ci=?, char-ci>=?, char-ci>? vergleicht Buchstaben ohne R|cksicht auf Gro_-Kleinschreibung. Beispiel: (char-ci=? #\a #\A) ` #t

char-downcase findet den passenden Kleinbuchstaben. Beispiel: (char-downcase #\A) ` #\a

char-lower-case? stellt fest, ob ein Buchstabe kleingeschrieben ist. Beispiel: (char-lower-case? #\A) ` ()

char-numeric? stellt fest, ob eine Buchstabe eine Ziffer darstellt. Beispiel: (char-numeric? #\8) ` #t

char-upcase findet den passenden Gro_buchstaben. Beispiel: (char-upcase #\a) ` #\A

char-upper-case? stellt fest, ob ein Buchstabe gro_geschrieben ist. Beispiel: (char-upper-case? #\A) ` #t

char-whitespace? stellt fest, ob ein Buchstabe ein Leerzeichen ist. Beispiel: (char-whitespace? #\newline) ` #t

close-input-port – schlie_t eine gevffnete Eingabe-Datei. Beispiel: (close-input-port port) ` #t. Seiteneffekt: der Eingabeport port wird geschlossen.

close-output-port – schlie_t eine gevffnete Ausgabedatei. Beispiel: (close-input-port port) ` #t Seiteneffekt: der Ausgabeport port wird geschlossen.

complex? stellt fest, ob ein Objekt eine komplexe Zahl ist. Beispiel: (complex? 5) ` )

cons erzeugt einen Knoten. Beispiel: (cons 'a 'b) ` (a . b)

current-input-port |bergibt das Filehandle, das f|r die Tastatureingabe zustdndig ist. Beispiel: (current-input-port) ` #<Port>

current-output-port |bergibt das Filehandle, das f|r die Bildschirmausgabe zustdndig ist. Beispiel: (current-output-port) ` #<Port>

D

Funktionen

display schreibt ein Objekt auf Bildschirm oder Port. Wvrter erscheinen ohne Gdnsef|_chen, Buchstaben ohne Schrdgstrich und Raute. Beispiel: (display '("abc" #\a)) ` #t. Seiteneffekt: schreibt (abc a) auf den Bildschirm.

E

Funktionen

eof-object? – stellt fest, ob ein Objekt ein Dateiende-Objekt ist. Beispiel: (eof-object? result) ` ()

eq?, eqv?, equal? vergleicht Objekte. eq? Vergleicht auf Identitdt, wobei gleichnamige Symbole als identisch gelten. eqv? vergleicht auf gleichen Typ und gleichen Wert (n|tzlich bei Konstanten), equal? vergleicht Listen rekursiv. Beispiel: (equal? '(a b) '(a b)) ` #t

even? stellt fest, ob eine Zahl gerade ist. Beispiel: (even? 15) ` ()

exact? stellt fest, ob eine Zahl genau ist. Beispiel: (exact? 15) ` #t

exact->inexact wandelt eine genaue Zahl um in eine ungenaue. Beispiel: (exact->inexact 4) ` 4.0

exit – verld_t LISP und Perl. Beispiel: (exit) ` Seiteneffekt: Ende des Programms.

exp – berechnet den Exponenten zur Eulerschen Zahl e. Beispiel. (exp 1) ` 2.718281828459

F

Funktionen

floor ermittelt die ndchstkleinere Ganzzahl. Beispiel: (floor 5.2) ` 5

for-each wendet eine Funktion auf eine Liste von Argumenten an. Funktionsgleich map. Beispiel: (for-each list '(a b)) ` ((a) (b))

G

Funktionen

gcd ermittelt den ggT (grv_ten gemeinsamen Teiler, greatest common divisor). Beispiel: (gcd 30 15 6) ` 3

H

Funktionen

hash-keys liefert eine \bersicht der Schl|ssel einer Assoziativliste. Beispiel: (hash-keys zustand) ` ("Gem|t")

hash-ref ermittelt den Wert eines Schl|ssels aus einer Assoziativliste. Beispiel: (hash-ref Zustand "Gem|t") ` (hungrig durstig muede)

hash-set! setzt ein Schl|ssel-Wert-Paar in einer Assoziativ-Liste. Beispiel: (hash-set! Zustand "Gem|t" '(satt zufrieden trotzdem muede)) ` (satt zufrieden trotzdem muede). Seiteneffekt: der Schl|ssel "Gem|t" in der Assoziativliste ZUSTAND wurde gedndert.

hash-values liefert eine \bersicht der Werte einer Assoziativliste. Beispiel: (hash-values zustand) ` ((SATT ZUFRIEDEN TROTZDEM MUEDE))

I

Funktionen

inexact? stellt fest, ob eine Zahl ungenau ist. Beispiel: (inexact? 15) ` ()

imag-part ermittelt den imagindren Anteil einer komplexen Zahl. Beispiel: (imag-part 5+2i) ` 2

inexact->exact wandelt eine ungenaue Zahl um in eine genaue. Beispiel: (inexact->exact 4.0) ` 4

input-port? – stellt fest, ob ein Objekt das Filehandle einer Eingabedatei ist. Beispiel: (input-port? port) ` #t

integer->char ermittelt einen Buchstaben nach ASCII bzw. ANSI. Verwendet die Perl-Funktion chr(). Beispiel: (integer->char 65) ` #\A

integer? stellt fest, ob eine Zahl ganzzahlig ist. Beispiel: (integer? 5.2) ` ()

L

Funktionen

lcm ermittelt das kgV (kleinste gemeinsame Vielfache, least common multiple). Beispiel: (lcm 30 15 6) ` 30

length berechnet die Elementzahl einer Liste. Beispiel: (length '(a b)) ` 2

list bildet eine Liste. Beispiel: (list 1 2 'apfel) ` (1 2 apfel)

list->string bildet aus einer Buchstaben-Liste ein Wort. Beispiel: (list->string '(#\W #\o #\l #\f)) ` "Wolf"

list->vector erzeugt einen Vektor aus den Elementen einer Liste. Beispiel: (list->vector '(a b c)) ` #(a b c)

list-ref ermittelt ein Listenelement aus seiner Position in der Liste. Beispiel: (list-ref '(a b c) 1) ` b

list? stellt fest, ob ein Objekt eine ordentliche Liste ist. Beispiel: (list? '(a . b)) ` ()

load lddt einen Quelltext. Beispiel: (load "autoload.lsp") ` #t. Seiteneffekt: der Quelltext autoload.lsp wird ausgef|hrt.

log ermittelt den nat|rlichen Logarithmus (also zur Basis der Eulerschen Zahl e). Beispiel: (log 1) ` 0

M

Funktionen

magnitude ermittelt den Absolutwert einer Komplexen Zahl. Beispiel: (magnitude 3+4i) ` 5.0

make-hash erzeugt eine Assoziativliste. Beispiel: (make-hash "Gem|t" '(hungrig durstig muede)) ` #<Hash {"Gem|t" => (HUNGRIG DURSTIG MUEDE)}>

make-polar erzeugt eine Komplexe Zahl mit dem ersten Argument als Absolutwert und dem zweiten als Winkel. Beispiel: (make-polar 5 2) ` -2.0807341827357+4.5464871341284i

make-rectangular erzeugt eine Komplexe Zahl mit dem ersten Argument als reellem und dem zweiten als imagindrem Anteil. Beispiel: (make-rectangular 5 2) ` 5+2i

make-string erzeugt ein neues Wort, wahlweise mit einem Vorgabewert. Beispiel: (make-string 5 #\x) ` "xxxxx"

map wendet eine Funktion auf die Elemente einer Liste an. Funktionsgleich for-each. Beispiel: (map list '(a b)) ` ((a) (b))

match pr|ft ein Wort auf das Vorkommen eines Musters. Beispiele: (match "abcdef" "C" "i") ` ("ab" "c" "def"); (match "abc" "X") ` ()

max ermittelt den grv_ten Zahlenwert. Beispiel: (max 3 5 4) ` 5

member, memq, memv stellt fest, ob ein Element in einer Liste vorkommt. member vergleicht mit equal?, memq vergleicht mit eq?, memv vergleicht mit eqv?. Beispiel: (member 'b '(a b c)) ` (b c)

min ermittelt den kleinsten Zahlenwert. Beispiel: (min 3 5 4) ` 3

modulo berechnet den Rest bei Division. Resultat hat Vorzeichen des Nenners. Beispiel: (modulo 14 3) ` 2

N

Funktionen

negative? stellt fest, ob eine Zahl negativ ist. Beispiel: (negative? -2) ` #t

newline schreibt einen Zeilenwechsel ins Display oder einen Port. Beispiel: (newline) ` #t. Seiteneffekt: Zeilenwechsel.

not negiert einen Wahrheitswert. Funktionsgleich null?. Beispiel: (not (= 1 1)) ` ()

null? stellt fest, ob ein Objekt die Leere Liste ist. Funktionsgleich not. Beispiel: (null? ()) ` #t

number->string macht aus einer Zahl ein Wort. Beispiel: (number->string 435.2) ` "435.2"

number? stellt fest, ob ein Objekt eine Zahl ist. Beispiel: (number? 'a) ` ()

O

Funktionen

object->string |bergibt die Bildschirm-Reprdsentation eines Objekts. Wenn ein drittes Argument gegeben ist, erscheinen Worte ohne Gdnsef|_chen und Buchstaben ohne Raute und Schrdgstrich. Beispiele: (object->string '(ab "cd" #\e)) ` "(AB \"cd\" #\\e)"; (object->string '(ab "cd" #\e) #t) ` "(AB cd e)"

odd? stellt fest, ob eine Zahl ungerade ist. Beispiel: (odd? 5) ` #t

open-input-file – vffnet eine Eingabedatei, |bergibt das Filehandle. Beispiel: (define x (open-input-file "test.txt")) ` x. Seiteneffekt: die Datei Eingabedatei test.txt wird gevffnet.

open-output-file – vffnet eine Ausgabedatei, |bergibt das Filehandle. Beispiel: (define y (open-output-file "test.out")) ` y. Seiteneffekt: die Datei Ausgabedatei test.out wird gevffnet.

output-port? stellt fest, ob ein Objekt das Filehandle einer Ausgabedatei ist. Beispiel: (output-port? p ` ) #\t

P

Funktionen

pair? stellt fest, ob ein Objekt ein Knoten ist. Beispiel: (pair? '(a . b)) ` #t

peek-charnicht unterst|tzt

positive? stellt fest, ob eine Zahl positiv ist. Beispiel: (positive? 15) ` #t

procedure? stellt fest, ob ein Objekt eine Funktion ist. Beispiel: (procedure? list) ` #t

Q

Funktionen

quotient dividiert zwei Zahlen ganzzahlig. Beispiel: (quotient 14 3) ` 4

R

Funktionen

rational? stellt fest, ob eine Zahl rational ist. Funktionsgleich number?. Beispiel: (rational? 15) ` #t

read liest einen Ausdruck von Tastatur oder Port. Beispiel: (read) (Benutzereingabe: apple) ` apple

read-char liest einen Buchstaben von Tastatur oder Port. Beispiel: (read-char) (Benutzereingabe: a) ` #\a

read-line liest eine Zeile von Tastatur oder Port und |bergibt ihn als Wort. Beispiel: (read-line)(Benutzereingabe: aber nein, ich bin ein Arbeitsbdr) ` "aber nein, ich bin ein Arbeitsbdr
"

real? stellt fest, ob eine Zahl reell ist. Funktionsgleich number?. Beispiel: (real? 15) ` #t

real-part ermittelt den reellen Anteil einer komplexen Zahl. Beispiel: (real-part 5+2i) ` 5

remainder berechnet den Rest bei Division. Resultat hat Vorzeichen des Dividenden. Beispiel: (remainder 14 3) ` 2

reverse erstellt eine Liste mit umgekehrter Reihenfolge. Beispiel: (reverse '(a b c)) ` (c b a)

round rundet eine Zahl kaufmdnnisch. Beispiel: (round 4.2) ` 4

S

Funktionen

set-car! ersetzt den CAR einer Liste durch ein anderes Element. Destruktiv. Beispiel: (set-car! x 'apfel) ` apfel. Seiteneffekt: das erste Element der Liste x ist mit dem Symbol apfel |berschrieben.

set-cdr! ersetzt den CDR einer Liste durch ein anderes Element. Destruktiv. Beispiel: (set-cdr! x '(birne)) ` (birne). Seiteneffekt: der Rest der Liste x (ohne erstes Element) ist mit der Liste birne |berschrieben. (Es d|rfte jetzt den Wert (apfel birne) haben.)

string bildet aus Buchstaben ein Wort. Beispiel: (string #\W #\o #\l #\f) ` "Wolf"

string->list bildet aus einem Wort eine Liste von Buchstaben. Beispiel: (string->list "Wolf") ` (#\W #\o #\l #\f)

string->number bildet aus einem Wort eine Zahl. Beispiel: (string->number "45.2") ` 45.2

string->symbol erzeugt ein aus einem Wort Symbol. Erlaubt auch Sonderzeichen. Beispiel: (string->symbol "Wolf") ` Wolf

string-append hdngt Wvrter zusammen. Beispiel: (string-append "a" "b" "c") ` "abc"

string<=?, string<?, string=?, string>=?, string>? vergleicht Wvrter. Beispiel: (string=? "Wolf" "wolf") ` ()

string-ci<=?, string-ci<?, string-ci=?, string-ci>=?, string-ci>? vergleicht Wvrter ohne R|cksicht auf Gro_- und Kleinschreibung. Beispiel: (string-ci=? "Wolf" "wolf") ` #t

string-length berechnet die Buchstabenzahl eines Worts. Beispiel: (string-length "abc") ` 3

string-ref ermittelt einen Buchstaben aus seiner Position im Wort. Beispiel: (string-ref "abc" 2) ` #\c

string-set! |berschreibt einen Buchstaben in einem Wort. Destruktiv. Beispiel: (string-set! "abc" 1 #\x) ` "axc". Seiteneffekt: das Wort abc wird kvrperlich |berschrieben mit axc.

string? Stellt fest, ob ein Objekt ein Wort ist. Beispiel: (string? "abc") ` #t

substring findet einen Teil eines Worts. Beispiel: (substring "abcdef" 2 3) ` "cde"

symbol->string ermittelt den Namen eines Symbols. Beispiel: (symbol->string 'apple) ` "APPLE"

symbol? stellt fest, ob ein Objekt ein Symbol ist. Beispiel: (symbol? 'apple?) ` #t

T

Funktionen

truncate schneidet den Nachkommateil einer Zahl ab. Beispiel: (truncate 4.2) ` 4

V

Funktionen

vector erzeugt einen neuen Vektor aus seinen Argumenten. Beispiel: (vector 'a 'b) ` #(a b)

vector->list macht aus einem Vektor eine Liste. Beispiel: (vector->list #(a b c)) ` (a b c)

vector-length ermittelt die Zahl der Elemente eines Feldes. Beispiel: (vector-length #(a b c)) ` 3

vector-ref ermittelt ein Element eines Feldes aus seiner Position. Beispiel: (vector-ref #(a b c) 1) ` b

vector-set! |berschreibt ein Element eines Feldes. Destruktiv. Beispiel: (vector-set! v 0 'apple) ` apple. Seiteneffekt: das erste Element des Feldes v ist nun das Symbol apple.

vector? stellt fest, ob ein Objekt ein Vektor ist. Beispiel: (vector? #(a b c)) ` #t

W

Funktionen

write schreibt einen Ausdruck auf Bildschirm oder Port. Wvrter erscheinen in Gdnsef|_chen, z. B. "Wort", Buchstaben mit Raute und Schrdgstrich, z. B. #\b. Kein Zeilenwechsel. Beispiel: (write "abc") ` #t. Seiteneffekt: auf dem Bildschirm steht "abc".

write-char schreibt einen Buchstaben ohne Raute und Schrdgstrich auf Bildschirm oder Port. Beispiel: (write-char #\a) ` #t. Seiteneffekt: auf dem Bildschirm steht a.

Z

Funktionen

zero? stellt fest, ob eine Zahl gleich Null ist. Beispiel: (zero? 0) ` #t

  nach oben

Glossar

D

Debugging – Neudeutsch: Suche nach Denkfehlern im Programm, mit Abstand die arbeitsintensivste Phase beim Programmieren. Fdllt bei LISP k|rzer aus als bei Perl, entfdllt aber nie ganz.

E

Entlausen – StarckDeutsch: Debugging (siehe oben).

H

Hyperlink – Neudeutsch: Vollelektronischer Ersatz f|r Wollfdden beim Buch in Papierform. Vorteile: geht schneller und bequemer; Nachteile: versagt bei Wollfdden in der Maus, also sofort in den Papierkorb damit!

  nach oben

Zur|ck zu Wolf Busch's Heimatseite