Software-Konstruktion mit LISP 9783110852769, 9783110117868


218 43 25MB

German Pages 420 [424] Year 1991

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Vorwort der Herausgeber
Vorwort
Vereinbarungen und Notation
Inhalt
I. Konstrukte (Bausteine zum Programmieren)
II. Konstruktionen (Analyse und Synthese)
III. Konstruktionsempfehlungen
Anhang
Recommend Papers

Software-Konstruktion mit LISP
 9783110852769, 9783110117868

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Programmierung komplexer Systeme 1 Herausgegeben von Fevzi Belli und Hinrich E. G. Bonin

Hinrich E. G. Bonin

Software-Konstruktion mit LISP

w DE

Walter de Gruyter

G Berlin • New York 1991

Dr. rer. pubi., Dipl.-Ing., Dipl.-Wirtsch.-Ing. Hinrich E. G. Bonin Professor für Verwaltungsinformatik an der Fachhochschule Nordostniedersachsen, Lüneburg

Das Buch enthält 65 Abbildungen

© Gedruckt auf säurefreiem Papier, das die US-ANSI-Norm über Haltbarkeit erfüllt.

Die Deutsche Bibliothek -

CIP-Einheitsaufnähme

Bonin, Hinrich: Software-Konstruktion mit LISP / Hinrich E. G. Bonin. Berlin ; New York : de Gruyter, 1991 (Programmierung komplexer Systeme ; 1) ISBN 3-11-011786-X NE: GT

© Copyright 1991 by Walter de Gruyter& Co., D-1000 Berlin 30. Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen,Übersetzungen,Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen. Printed in Germany. Druck: Gerike GmbH, Berlin. -Bindung: Lüderitz &BauerGmbH, Berlin. -Einbandentwurf: Johannes Rother, Berlin. - Textillustrationen: David Andersen, Berlin.

Vorwort der Herausgeber

Die Reihe „Programmierung komplexer Systeme" beschäftigt sich mit Realisierungs- und Zuverlässigkeitsaspekten komplexer Software-Systeme. Ihre Zielgruppe sind Ingenieure, Naturwissenschaftler, Studierende und Praktiker aller Fachrichtungen mit Informatik-Grundkenntnissen sowie DV-Verantwortliche für Entwicklung und Weiterbildung. Die Bände dieser Reihe möge ihnen das Programmieren von schwierigen, umfangreichen Aufgaben durchschaubarer machen. Gegen Mythen und Vorurteile wie beispielsweise „nicht möglich", „total einfach" oder „alles Glücksache" setzt diese Reihe den aktuellen Wissensstand der Forschung und die konkreten Erfahrungen der Praxis. Der Band 1 zeigt Programmieren als einen systematischen, disziplinierten Konstruktionsvorgang. Dazu wird LISP (Akronym für "LISI Processing") aus klassischen und neuen Blickwinkeln erläutert. LISP ist eine bewährte Sprache, geschaffen für die Bewältigung komplexer Aufgaben und prädestiniert für den Umgang mit Symbolen, abgeleitet aus dem Lambda-Kalkül und geprägt durch ihre Dualität von „Daten" und „Programm". LISP ermöglicht mit seinem modernen Dialekt Scheme ohne Ballast ein imperativ-, funktions- und objekt-geprägtes Programmieren. Der Band führt anhand von Beispielen aus der Verwaltungsund Büroautomation Schritt für Schritt über Funktionen höherer Ordnung zur Konstruktion mit Klassen, Instanzen und multipler Vererbung. Die Reihe präsentiert neben Bänden mit Lehrbuchcharakter (z.B. Band 3: „Logik für Software-Ingenieure" oder Band 5: „Einführung in die systemnahe Programmierung") neuere Forschungsergebnisse. So vertieft beispielsweise Band 2 Zuverlässigkeitsaspekte. Quantitative Modelle und Nachweisverfahren, wie sie im Hardware-Bereich längst bewährt sind, werden in einem innovativen Ansatz auf die Software übertragen. Band 4 dokumentiert neben der Einführung in die Thematik aktuelle Forschungsergebnisse in Form eines umfassenden Nachschlagewerkes über Maße und Meßverfahren zur Softwarekomplexität. Wir wünschen, daß die Reihe ihren Leserinnen und Lesern sowohl fundiertes Fachwissen wie Freude bei der Lektüre vermittelt. Den zukünftigen Autoren wünschen wir Mut und Ausdauer, ihre Innovationen leicht verständlich zu formulieren. Dabei wollen wir sie gern mit Rat und Tat unterstützen. Paderborn/Lüneburg, Februar 1991.

Fevzi Belli Hinrich E.G. Bonin

Vorwort

Nützliche Produkte entstehen kaum per Zufall. Sie erfordern einen sorgsam geplanten Herstellungsprozeß, wenn es gilt, sie wirtschaftlich, in gewünschter Qualität und termingerecht zu produzieren. Das planmäßige Konstruieren ist eine Aufgabe für Ingenieure. Sie kennen bewährte Grundkonstruktionen, mit denen sie die gestellte Aufgabe meistern. Stets greifen sie dazu auf schon existierende Halb- und Fertigprodukte (normierte Bausteine) zurück. Dieses Buch handelt vom planmäßigen Konstruieren von Software mit LISP (Akronym für ,JLISt Processing"). Es erläutert zweckmäßige Bausteine (Kapitel I), skizziert bewährte Konstruktionen (Kapitel II) und gibt praxisnahe Konstruktionsempfehlungen (Kapitel III). Programmieren ist einerseits eine kreative Arbeit, andererseits ist diese schöpferische Arbeit an und mit schnell änderbaren (Quellcode-)Texten systematisch und diszipliniert zu vollziehen; ganz im Sinne der Ingenieurdisziplin „Softwaretechnik" (engl.: Software Engineering). Warum gerade mit LISP ingenieurmäßiges Konstruieren erlernen? LISP ist doch ein „Oldie" mit mehr als 30 Jahren auf dem Buckel. Sicherlich ist dieser „Oldie" der „Klassiker" für den Umgang mit Symbolen und daher heute eine dominierende Sprache im Anwendungsgebiet „Künstliche Intelligenz". Übersehen wird leicht, daß LISP im Laufe der Zeit zu einer modernen Programmiersprache, einer leistungsfähigen, dialogorientierten Software-Entwicklungsumgebung und einer Menge sinnvoller Konstrukte (Vektoren, Klassen, Instanzen etc.) herangereift ist. Konzepte, die in ähnlich alten Sprachen, wie z.B. FORTRAN oder COBOL, nicht möglich sind, können in LISP problemlos formuliert werden. Mit LISP können wir funktionsgeprägte Konstruktionen zeigen, z.B. Funktionen höherer Ordnung, die sich selbst als Argument haben. Wir können Sprünge in die „Zukunft" und „Vergangenheit" eines Programms notieren (Continuation), Zahlen mit beliebig vielen Ziffern errechnen, unterschiedliche Werte mit gleichnamigen Variablen benennen (Pakete), Eigenschaften dynamisch vererben (objektgeprägte Konstruktionen), Bildschirmfenster definieren („Pop-up"-Fenster), Fraktalfiguren zeichnen (Graphik) und vieles mehr. Wenn LISP, welcher LISP-Standard? Common LISP (vgl. Steele u.a., 1984) oder Scheme (vgl. Rees/Clinger, 1986)? Um die Beispiele und Programm(fragment)e auf einem Personalcomputer der I B M XT-Klasse nachvollziehen zu können, kam das große Common LISP (ca. 1000 Seiten Dialektbeschreibung) nicht in Betracht. Dieses erfordert die Rechenkapazität einer Workstation (vgl. Anhang B). Das „kleine" Scheme (ca. 50 Seiten Dialektbeschreibung) bietet sich aufgrund der Stringenz des funktionalen Konzeptes (keine zusätzliche „function cell") und der Leistungsfähigkeit der eingebauten Konstrukte an. Dabei ist

VIII

Vorwort

Scheme auch bei dem hier eingesetzten PC System von Texas Instruments Inc. (Version 3.0, Betriebssystem MS-DOS) keinesfalls nur eine Untermenge von Common LISP. Das Buch wendet sich an alle, die Schritt für Schritt anhand von praxisnahen Beispielen LISP verstehen lernen wollen. Im Mittelpunkt steht das funktionsund objektgeprägte Programmieren. Dazu muß der Beginner viele Konstrukte erlernen. Als pragmatischer Leitfaden dient ihre Einteilung in Konstruktoren, Selektoren, Prädikaten und Mutatoren. Erklärt werden sie mit Beispielen, die vorwiegend aus dem Bereich der Verwaltungsautomation („kommerziellen DV") stammen. Beantwortet werden Fragen, die in allen Programmiersprachen relevant sind: Wie dokumentiert man durchschaubar? Wie benennt man eine Variable, eine Funktion, ein Makro etc.? Wie kommentiert man im Quellcodetext? Wie gliedert man ein komplexes Problem in weniger komplexe (Modularisierung)? Wie spezifiziert man in LISP-Notation (Pseudocode)? Sicherlich läßt sich LISP nüchtern und sachlich erläutern, ohne vom Glanz der „LISP-Philosophie" zu schwärmen. Dieses Buch versucht jedoch, die Begeisterung des Autors für das moderne LISP zu verdeutlichen. Erst wenn die „Lots of /nsidious Silly Parentheses" (spöttische Interpretation von LISP: viele heimtückische und blödsinnige Klammern) in ästhetische Konstrukte umschlagen, kommt zur Zweckmäßigkeit auch noch die Freude. Dieses Buch ist konzipiert als ein Arbeitsbuch zum Selbststudium und für Lehrveranstaltungen. Der LISP-Neuling möge es Abschnitt für Abschnitt sequentiell durcharbeiten. Der LISP-Vorbelastete kann zunächst die Zusammenfassungen der einzelnen Abschnitte studieren. Anhand ihrer zusätzlichen charakteristischen Beispiele ist individuell entscheidbar, ob der Inhalt des jeweiligen Abschnittes schon bekannt ist. Damit das Buch auch später als Nachschlagewerk hilfreich ist, enthält es sowohl Vorwärts- wie Rückwärts-Verweise. Ebensowenig wie z.B. Autofahren allein aus Büchern erlernbar ist, wird niemand zum „LISP wizard" (deutsch: Hexenmeister) durch Nachlesen von erläuternden Beispielen. Das intensive Durchdenken der Beispiele im Dialog mit einem LISP-System vermittelt jedoch, um im Bild zu bleiben, unstrittig die Kenntnis der Strassenverkehrsordnung und ermöglicht ein erfolgreiches Teilnehmen am Verkehrsgeschehen. Für diesen Lernprozeß wünsche ich der „Arbeiterin" und dem „Arbeiter" viel Freude. Danksagung: Für das große Interesse und die Durchsicht einer vorhergehenden Fassung danke ich meinen Kollegen Herrn Prof. Dr. Fevzi Belli, Herrn Prof. Dr. Günter Matthiessen und Herrn Dipl.-Ing. Thomas Goetz. Ohne die vielen Diskussionen mit Studentinnen und Studenten im Rahmen von LISP-Projekten z.B. Lehrveranstaltung „Teamorientierte Softwareentwicklung" (Universität Bremen, Fachbereich Mathematik und Informatik, WS 1987/88), Studienprojekt „FLAVOR" (Hochschule Bremerhaven, Studiengang Systemanalyse, SS 1988 WS 1988/89) und "LISP Programmierkurs" (Universität Hannover, Rechtsinformatik, WS 1988/89) wäre dieses Buch nicht in dieser Form entstanden. Ganz

Vorwort

IX

besonderer Dank gebührt an dieser Stelle den Hörerinnen und Hörern meiner Vorlesung „Nichtnumerische Programmierung" (Hochschule Bremerhaven und Fachhochschule Nordostniedersachsen), die durch ihre konstruktive Kritik an den ersten Manuskriptfassungen dieses Buch mitzugestalten halfen. Lüneburg, Dezember 1990

Hinrich E. G. Bonin '((V)ERFASSER)

Vereinbarungen und Notation

Aus Lesbarkeitsgründen sind nur die männlichen Formulierungen genannt; die Leserinnen seien implizit berücksichtigt. So steht z.B. das Wort „Programmierer" hier für Programmiererin und Programmierer. Klassische LISP-Systeme unterscheiden bei Symbolen nicht zwischen großen und kleinen Buchstaben. Die LISP-Konstrukte sind daher in Großbuchstaben dargestellt. Sie heben sich somit deutlich vom sonstigen Text ab. Dabei dient eine Textaufteilung auf mehrere Zeilen einer Verbesserung der Lesbarkeit. Sie hat keine Bedeutung für die Auswertung des über mehrere Zeilen verteilt notierten Konstruktes. Soweit möglich stehen deutsche Fachbegriffe. Ausnahmen bilden Begriffe, bei denen die Übersetzung nicht „eins-zu-eins" möglich ist und/oder die englische Fassung sich durchgesetzt hat, z.B. Flavor-System (deutsch: Aroma, Wohlgeschmack) als Bezeichnung für eine objektorientierte Software-Entwicklungsumgebung. Um das Verstehen des laufenden Textes zu sichern, sind Textpassagen im Sinne von gedanklichen Einschüben durch „eckige" Klammern abgegrenzt. Dabei treten folgende Einschübe auf: /Hinweis: „Text"/ „Text" verweist auf eine Besonderheit oder Empfehlung, entsprechend zu handeln. /Exkurs: „Text'7 Zum besseren Verstehen der Hintergründe erläutert „Text" einen zusätzlichen Aspekt ausführlicher. /LISP-System-Name: „Text'7 „Text" beschreibt eine Ausprägung des entsprechenden LISP-Dialektes. Notation für die Definition von LISP-Konstrukten: { } Der Inhalt zwischen den geschweiften Klammern kann einfach oder mehrfach vorkommen oder kann fehlen.

Nicht-terminales Symbol (vgl. Abschnitt 2.2); Eingrenzung eines Begriffes bei Formulierungen in Backus-Naur-Form (kurz: BNF)

()

LISP-Klammern Alternative (vgl. BNF Abschnitt 2.2)

Vereinbarungen und Notation

XII

Symbolischer Ausdruck

Notation für die Schnittstelle

zum

LISP-System:

eval> ==> ; Kommentar

eval>

Die Zeichenfolge „eval>" gibt an, daß die danach folgenden Zeichen dem Zyklus „Lesen, Auswerten, Ergebnisausgabe" (READEVAL-PRINT-Zyklus) übergeben werden. „eval>" steht anstelle der Aufforderung (Prompt) eines laufenden LISP-Systems, das eine Eingabe erwartet. LISP-Systeme verwenden unterschiedliche Aufforderungen und zusätzlich sind diese individuell modifizierbar. Wir benutzen eval> (vgl. z.B. Brooks, 1987; Narayanan/Sharkey, 1985) für Scheme. Zur Betonung eines bestimmten LISP-Dialektes ist ein Kennbuchstabe als Präfix vorangestellt: eval> ::= Scheme (vgl. Rees/Clinger, 1986) C-eval> Common LISP (vgl. Steele u.a., 1984) T-eval> ::= TLC-LISP (vgl. TLC-LISP, 1985)

==>

Dokumentiert das Ergebnis, d.h. die nach dem Pfeil dargestellten Zeichen werden vom PRINT-Teil ausgegeben. Das Semikolon kennzeichnet einen Kommentar. Alle Zeichen nach einem Semikolon in einer Zeile sind im READ-EVALPRINT-Zyklus Kommentare, d.h. werden vom LISP-System nicht ausgewertet (vgl. Abschnitt 11.2).

Inhalt

Vorwort Vereinbarungen und Notation I.

Konstrukte (Bausteine zum Programmieren)

1. Einführung: Handhabung von Konstrukten 1.1 Syntax: Symbolischer Ausdruck 1.2 Semantik: Regeln zum Auswerten (Evaluieren) 1.3 Repräsentation von Daten und Programm 1.4 Konstrukt: Schnittstelle und Implementation 1.4.1 LAMBD A-Konstrukt 1.4.2 Umgebung 1.5 Zusammenfassung: EVAL, APPLY und LAMBDA 2. Kombinieren von Konstrukten 2.1 Interaktion: Operand - Operation 2.2 Kontrollstrukturen 2.2.1 Sequenz 2.2.2 Selektion 2.2.3 Iteration 2.3 Zeitpunkt einer Wertberechnung 2.4 Typische Fehler 2.5 Zusammenfassung: LET, DO und MAP

VII XI 1 2 3 8 13 16 19 26 32 33 36 45 49 58 66 76 79 83

3. 3.1 3.2 3.3 3.4 3.5

Rekursion als Problemlösungsmethode Funktionsanwendung auf die Restliste Verknüpfung von Teilproblem-Lösungen Rekursive Konstrukte für rekursive Strukturen Geschachtelte Rekursion Zusammenfassung: Rekursion

85 89 92 94 97 100

II.

Konstruktionen (Analyse und Synthese)

105

4. 4.1 4.2 4.3 4.4

Verstehen einer Konstruktion Analyseaspekte Verbund von Konstruktor, Selektor, Prädikat und Mutator LISP-Klassiker: „Symbolisches Differenzieren" Zusammenfassung: Abstraktionsebene

106 110 113 115 121

5. 5.1

Abbildungsoption: Liste Zirkuläre Liste

123 129

XIV

Inhalt

5.2 5.3 5.4

Assoziationsliste Eigenschaftsliste Zusammenfassung: Liste, A-Liste und P-Liste

141 147 164

6. 6.1 6.2 6.3 6.4

Abbildungsoption: Vektor Vektor- Konstrukte Abbildung der CONS-Zelle als Vektor Höhenbalancierter Baum Zusammenfassung: Vektor

167 168 170 172 184

7. Abbildungsoption: Zeichenkette und Symbol 7.1 Mustervergleich 7.2 STRING-Konstrukte 7.3 Symbol und PRINT-Name 7.4 Generieren eines Symbols 7.5 Zusammenfassung: EXPLODE, IMPLODE und GENSYM

184 185 188 194 202 208

8. Abbildungsoption: Funktion mit zugeordneter Umgebung 8.1 Funktionen höherer Ordnung 8.1.1 Funktionale Abbildung der CONS-Zelle 8.1.2 Fixpunktoperator Y 8.1.3 Funktion ALTERNATE 8.2 Kontrollstruktur: Continuation 8.3 Syntaxverbesserung durch Makros 8.4 Lokaler Namensraum (Pakete) 8.5 Übersetzen von Funktionen 8.6 Zusammenfassung: Closure, Continuation, Makros und Pakete

211 213 214 218 224 226 233 239 250 252

9. 9.1 9.2 9.3 9.4

Generierte Zugriffskonstrukte Abschirmung von Werten DEFINE-STRUCTURE-Konstrukt Einfache statische Vererbung Zusammenfassung: Zugriffskonstrukte als Schnittstelle

257 258 261 265 269

10. 10.1 10.2 10.3 10.4 10.5

Klasse-Instanz-Modell Scheme Object-Oriented Programming System (SCOOPS) Multiple dynamische Vererbung Aktiver Slot Modifikation des Vererbungsgraphen Zusammenfassung: Spezialisierung und Faktorisierung

271 272 284 293 294 298

III.

Konstruktionsempfehlungen

303

11. Transparenz der Dokumentation 11.1 Benennung 11.2 Kommentierung

305 309 319

Inhalt

XV

11.3 Vorwärts-und Rückwärtsverweise 11.4 Zusammenfassung: Namen, Kommentare und Verweise

322 326

12. 12.1 12.2 12.3

Spezifikation mit LISP Anforderungen an Anforderungen Import-Export-Beschreibung Zusammenfassung: Spezifikation

330 334 338 342

13. 13.1 13.2 13.3 13.4 13.5

Wechsel der Abbildungsbasis Turtle Graphik Lösung: Nachrichtenversand Lösung: Verzweigungsfunktion Fraktalfigur Zusammenfassung: Graphik, SEND- und DISPATCH-Konstruktion.

344 344 346 353 360 362

14.

Ausblick

366

Anhang

369

A: Literaturverzeichnis

369

B: LISP-Systeme 1. Einführung 2. Rechner für LISP-Systeme 3. Software

384 384 387 390

C: PC Scheme Übersicht

394

D: Schlagwörter

398

I. Konstrukte (Bausteine zum Programmieren)

Wie jede Sprache, so dient auch eine formal(isiert)e Sprache der Kommunikation. Aufgabe einer Programmiersprache ist die Kommunikation zwischen Menschen über Texte („Botschaften"), die ein Rechner als Arbeitsanweisungen „interpretieren" und ausführen kann. Formulierungen in einer Programmiersprache haben einerseits Menschen, andererseits Rechner als Adressaten. Jeder Text in einer Programmiersprache hat zumindest im Erstellungsprozeß (Entwurfs-/Formulierungs-/Test-Zyklus) seinen Konstrukteur als Kommunikationspartner. Darüber hinaus wendet sich der Text an Dritte; an Mitkonstrukteure und an das Wartungs-/ Pflege- und Fortentwicklungs-Personal (vgl. Bild 1-1). Programmieren ist damit das Erstellen von Texten aus (sprachlichen) Konstrukten, welche die „Arbeit" eines Rechners bestimmen.

Bild

1-1.

Kommunikationsmittel

Programmiersprache

Programmiersprachen können stärker die Kommunikation mit dem Rechner oder die zwischen Programmierern unterstützen. Ein in Maschinencode notiertes Programm kann das gesamte Leistungsvermögen („Verstehbarkeitspotential") des Rechners effizient ausnutzen. Ein Programmierer kann ein solches Programm in der Regel nur mühsam verstehen. Ein Text, in einer Assembler-Sprache formuliert, ist im allgemeinen aufwendiger zu durchschauen als ein äquivalenter Text, notiert in einer höheren Programmiersprache. Dafür wird der Assembler-Text als eine l:l-Abbildung des Maschinencodes vom Rechner mit weniger Aufwand in konkrete Arbeitsschritte umgesetzt. Eine Programmier-

2

I. Konstrukte (Bausteine zum Programmieren)

spräche ist daher stets ein Kompromiß zwischen den unterschiedlichen Anforderungen der Kommunikations-"Partner" Mensch und Maschine. In einer natürlichen Sprache kann ein Sachverhalt klar und leicht verständlich oder umständlich („verkompliziert") und mißverständlich beschrieben werden. Kurze Sätze mit prägnanten Begriffen, verknüpft durch eine systematische Gliederung, helfen einen Sachverhalt besser, klarer und schneller zu vermitteln als lange Sätze mit stark verschachtelten Bezügen. In jeder Programmiersprache, also auch in „L/St Processing" (kurz LISP), steht man vor einem ähnlichen Formulierungsproblem. Es können leicht durchschaubare (transparente) oder undurchschaubare bzw. nur sehr schwer durchschaubare Texte notiert werden. Es geht daher um die Wahl geeigneter Begriffe (Sprachkonstrukte) und um ihre Kombination zu einem transparenten Text, der den Sachverhalt (die Problemlösung) so einfach wie möglich vermittelt. Im Kapitel I erläutern wir Konstrukte der Programmiersprache LISP und ihre Handhabung mit dem Ziel, ein „zweckmäßiges" (transparentes und effizientes) Programm zu konstruieren (Abschnitt 1). Programmieren wird als Lösen einer Konstruktionsaufgabe aufgefaßt (Abschnitt 2), wobei der Programmtext (Quellcodetext) die Konstruktion einerseits für den Programmierer (Transparenzaspekt) und andererseits für den Rechner (Effizienzsaspekt) dokumentiert. Bestimmte Konstruktionen sind für eine Programmiersprache charakteristisch. Für LISP sind es die Konstruktionen der rekursiven Problemlösungsmethode. Nicht jede weitverbreitete Programmiersprache verfügt über Konstrukte für rekursive Lösungen; z.B. besitzt COBOL (Abk. für engl.: common business oriented Zanguage) selbst im derzeit gültigem Standard (American National Standard COBOL X3.23-1985) über kein Konstrukt zur Formulierung rekursiver Definitionen; es müssen iterative (Ersatz-)Lösungen gewählt werden. Obwohl LISP iterative Konstrukte umfaßt (Abschnitt 2.2.3), unterstützt LISP rekursive Lösungen in besonderem Maße. Die Rekursion wird daher ausführlich erörtert (Abschnitt 3).

1. Einführung: Handhabung von Konstrukten Heute ist LISP eine universelle Programmiersprache, eine dialogorientierte Software-Entwicklungsumgebung, eine Menge vordefinierter Bausteine („Konstrukte") und ein Formalismus zur Spezifikation (Präzisierung) eines Problems. Die Ursprungsfassung konzipierte John McCarthy zwischen 1956 und 1958 am Massachusetts Institute of Technology Cambridge, Massachusetts (MIT) (vgl. McCarthy, 1960; McCarthy, 1966). Dabei konnte er auf frühere Arbeiten zurückgreifen, insbesondere auf die erste Listen-Verarbeitungs-Sprache mit dem Namen IPL (Akronym für: information-Processing Language), die Mitte der

1. Einführung: Handhabung von Konstrukten

3

fünfziger Jahre von Herbert A. Simon, A. Newell und J.C. Shaw entwickelt wurde. Mehr als 30 Jahre Geschichte haben zu einer großen Anzahl von LISP-Dialekten geführt (vgl. Anhang B) und zu mehreren Versuchen, einen Standard zu definieren und diesen durchzusetzen, zum Beispiel Common LISP (vgl. Steele u.a., 1984) oder Scheme (vgl. Rees/Clinger, 1986). Trotz der Vielfalt existierender Dialekte gibt es einen gemeinsamen LISP-Kern, das sogenannte „Pure LISP". Typisch für diesen Kern ist die Möglichkeit anwendungsspezifische Funktionen mit Hilfe des LAMBDA-Konstruktes (vgl. Abschnitt 1.4.1) definieren zu können. LISP ist konzipiert für den Umgang mit Symbolen. Wie der Name „L/St Processing" besagt, ist LISP geprägt: o durch die Liste als das Mittel zur Darstellung von Strukturen und o durch den Prozeß der Auswertung von Listen (symbolischen Ausdrücken). Beide Kennzeichen werden erst im Sinne einer „Kostprobe" vermittelt und in den folgenden Abschnitten unter verschiedenen Aspekten weiter vertieft. Zunächst ist es erforderlich die Syntax elementarer Konstrukte (engl.: primitives) zu erläutern. Danach gilt es die Semantik dieser Konstrukte zu beschreiben. Dabei geht es um die Bedeutung der elementaren Konstrukte, d.h. um die Werte, die solche Konstrukte aus (korrekten) Eingaben berechnen.

1.1 Syntax: Symbolischer Ausdruck Die Bausteine zur Konstruktion einer Liste sind die leere Liste und (Listen-)Elemente. Ein einfaches Element kann z.B. eine Zahl oder ein Symbol sein. Solche Elemente werden auch Atome genannt. Diese Bezeichnung weist darauf hin, daß sie einerseits eine (kleinste) betrachtbare Einheit und anderseits der Stoff zum Bau komplexer Konstruktionen sind. Später zeigen wir, daß auch in LISP die Atome aus elementaren Teilchen bestehen (vgl. Abschnitt 7.3). Die leere Liste wird dargestellt durch eine öffnende Klammer „(", unmittelbar gefolgt von einer schließenden Klammer „)". Diese leere Liste wird auch durch

4

I. Konstrukte (Bausteine zum Programmieren)

das Symbol NIL repräsentiert. Werden Elemente in die leere Liste eingefügt, wobei die Elemente selbst wiederum Listen sein können, dann entstehen geschachtelte Listen. Das Fundament („Mutter Erde") für solche komplexen Strukturen ist NIL, die leere Liste. Das Bild 1.1-1 zeigt Schritte zur Konstruktion einer geschachtelten Listestruktur. Die Liste selbst und jedes Element stellen einen sogenannten symbolischen Ausdruck dar (engl.: symbolic expression; abgekürzt „s-expression" oder kürzer „sexpr"; Plural "sexprs").

Erzeugte

Schritt 1. 2. 3. 4. 5. 6. Bild

Leere Liste: Element C eingefügt: Element B vorn eingefügt: Element A vorn e i n g e f ü g t : L i s t e (E F G) an d i e Stel e von A g e s e t z t : Element A vorn eingefügt: 1.1- 1. Beispiel:

Von

NIL

zu

einer

Struktur:

()

(C) (B C) (A B C) ((E F G) B C) (A (E F G) B C) geschachtelten

Liste

Ein symbolischer Ausdruck kann sein: o ein Symbol (auch Literalatom genannt), Z.B. A oder NULL? oder MEYER-AG, notiert als ein Buchstabe oder Sonderzeichen, gefolgt von keinem oder weiteren Buchstaben oder Sonderzeichen. o eine Zahl (auch Zahlatom genannt), Z.B. 77 oder +67.23, notiert mit optionalem Vorzeichen und gefolgt von einer oder mehreren Ziffer(n) und optionalem Dezimalpunkt. o ein Zeichen (engl.: character), Z.B #\a, dargestellt als Nummernzeichen # (engl.: sharp sign) gefolgt von einem Schrägstrich (engl.: backslash) und gefolgt von dem jeweiligen Zeichen. o eine Zeichenkette (engl.: string), Z.B. "Programmiersprachen bleiben bedeutsam", notiert als doppeltes Hochkomma, gefolgt von keinem, einem oder mehreren Zeichen und abgeschlossen wieder mit einem doppelten Hochkomma. o eine Liste von symbolischen Ausdrücken Z.B. (A (B)) oder (ABBUCHUNG! MEYER-AG 67.23), notiert als öffnende Klammer gefolgt von keinem, einem oder mehreren symbolischen Ausdrükken und einer schließenden Klammer.

1. Einführung: Handhabung von Konstrukten o und abhängig vom jeweiligen LISP-System weitere Typen und Strukturen Z.B. Vektoren, spezielle Symbole für Boolsche Wahrheitswerte, Umgebungen etc. Ein LISP-Programm ist ein symbolischer Ausdruck, der sich aus symbolischen Ausdrücken zusammensetzen kann, die sich wiederum aus symbolischen Ausdrücken zusammensetzen können usw. Ein LISP-Programm besteht letztlich aus zusammengesetzten symbolischen Ausdrücken einfachen Typs. Diese Typen haben Eigenschaften, die verstehbar sein sollten, unabhängig von den Implementations-Details des jeweiligen LISP-Systems (vgl. Bild 1.1-2). Jeder symbolische Ausdruck ist eine Datenstruktur und gleichzeitig ein syntaktisch korrektes LISP-Programm (im Sinne einer kontextfreien Analyse). Ein LISP-Programm bewirkt nichts anderes, als symbolische Ausdrücke ein- und auszugeben, neu zu konstruieren, zu modifizieren und/oder zu vernichten. Die LISP-Syntax ist durch die Regeln zur Bildung symbolischer Ausdrücke definiert. Die Liste, ein symbolische Ausdruck, kann sich wiederum aus Listen zusammensetzen. Die LISP-Syntax ist aufgrund dieser rekursiven Definition sehr einfach.

Konstrukte Konstrukt

Konstrukt

symbolisehe

Ausdrücke

- T Y P : Elementare

Symbol

sind

Zahl

L I S P - K o n s t r u k t e aus Zei chen

Benutzersicht

Zei chenkette

Liste

- Repräsentation:

Elementare L I S P - K o n s t r u k t e aus I m p l e m e n t a t i o n s s i c h t Realisierung abhanging vom jeweiligen LISP-System

Legende: === ::=

B e t r a c h t u n g s e b e n e ; V e r s t e h e n der e l e m e n t a r e n K o n s t r u k t e ohne I m p l e m e n t a t i o n s - D e t a i 1 k e n n t n i s s e

Bild 1.1-2. Symbolisehe

Ausdrücke

(kurz:

sexprs)

6

I. Konstrukte (Bausteine zum Programmieren)

Beispiele für Listen: (FRANZ MEYER-GMBH) (HANS 0TT0-0HG (ABTEILUNG 3 ) ) (T NIL NIL N I L ) ( ( ) NIL) ( ( E I N E ) (GUELTIGE L I S T E ) IST DIES MIT ZAHLATOM 12) ( ( ( ( ( A ) ) ) ( ( ( B ) ) ) ) ( 1 2 3 4 5 6 7 8 9))

Beispiel für eine unvollständige Liste-. ( D I E S ( I S T ) ) ((NICHT E I N E ) ) L I S T E )

Dieser Listennotation fehlt d i e öffnende Klammer am Anfang!

Um zu erkennen, ob eine gültige Liste vorliegt, bedarf es der Zuordnung der öffnenden Klammern „(" zu den schließenden Klammern „)". Nur wenn die Zahl der öffnenden Klammern gleich der Zahl der schließenden ist, kann eine oder können mehrere Listen vorliegen. Diese Bedingung ist notwendig, jedoch nicht hinreichend, wie der folgende Fall zeigt. ) ) ( K E I N E LISTE (

¡Keine L i s t e , da f a l s c h e

Klammernfolge!

Ein Verfahren zum Erkennen zusammengehörender Klammern ist daher erforderlich. Es stellt fest, ob es sich um eine oder mehrere Listen handelt oder keine ordnungsgemäße Liste vorliegt. Bei vielen öffnenden und schließenden Klammern ist nicht gleich überschaubar, welche Liste durch welche beiden Klammern begrenzt wird. Nicht zuletzt darauf beruht die spöttische Uminterpretation des Akronyms LISP in „Lots of /nsidious Silly Parentheses". Das folgende Beispiel verdeutlicht das Problem bei vielen Klammern. ( ( H I E R (SIND

(VIELE)(SCHOENE)))((KLAMMERN)(((ZU)))((ZAEHLEN))))

Moderne LISP-Systeme entschärfen diese Klammern-Zuordnungsproblematik durch integrierte Struktureditoren, die zu einer schließenden Klammer die zugehörende öffnende Klammer hervorheben, z.B. durch Aufblinkenlassen dieser Klammer (z.B. EDWIN in PC Scheme, vgl. Anhang C). Da beim Analysieren von LISP-Texten nicht immer eine entsprechende Rechnerunterstützung verfügbar ist, müssen die Klammern in der Praxis auch manuell „gezählt" werden. Es gibt verschiedene Empfehlungen für das Vorgehen beim Klammernzählen (vgl. z.B. Siklössy, 1976, S. 214 ff). Die Tabelle 1.1-1 skizziert ein Verfahren mit Angabe der Schachtelungstiefe von Listen in Listen. Dieses Verfahren, auf das obige Beispiel angewendet, ergibt folgende Schachtelungstiefen:

1. Einführung: Handhabung von Konstrukten

7

( (HIER (SIND (VIELE) (SCHOENE) ) ) 1 2 3 4 4 4 4 3 2 ( (KLAMMERN) 2 3 3

( 3

( 4

5

(ZU) 5 4

3

) ) ( (ZAEHLEN) ) ) ) 3 4 4 3 2 1

LISTENERKENN.UNG 1.

Erste

Zeichen

prüfen

Ist es e i n e ö f f n e n d e K l a m m e r , d a n n s e t z e i h r e S C H A C H T E L U N G S T I E F E a u f 1, andernfal1s b e e n d e L I S T E N E R K E N N U N G , d e n n es ist k e i n e 2.

Klammern

Liste.

markieren

Suche zeichenweise nach Zeichen bis zum letzten SCHACHTELUNGSTIEFE nach

Klammern, beginnend vom ersten Zeichen, und notiere die folgender Vorschrift:

Jede ö f f n e n d e K l a m m e r e r h ä l t eine um 1 h ö h e r e S C H A C H T E L U N G S T I E F E , wenn die z u l e t z t m a r k i e r t e Klamm e r (= V o r g ä n g e r k l a m m e r ) e i n e ö f f n e n d e K l a m m e r i s t , a n d e r n f a l l s e r h ä l t sie d i e S C H A C H T E L U N G S T I E F E d e r Vorgängerklammer. J e d e s c h l i e ß e n d e K l a m m e r e r h ä l t e i n e um 1 k l e i n e r e S C H A C H T E L U N G S T I E F E , wenn die zuletzt m a r k i e r t e Klamm e r (= V o r g ä n g e r k l a m m e r ) e i n e s c h l i e ß e n d e K l a m m e r i s t , a n d e r n f a l l s e r h ä l t sie die S C H A C H T E L U N G S T I E F E der Vorgängerklammer. 3.

Ergebnis

feststellen

Hat eine schließende Klammer die S C H A C H T E L U N G S T I E F E < 1 , dann liegt keine o r d n u n g s g e m ä ß e Liste vor. Es f e h l e n ö f f n e n d e K l a m m e r n . Hat eine schließende Klammer die S C H A C H T E L U N G S T I E F E = 1 , dann m a r k i e r t d i e s e das Ende einer Liste. Haben nur die erste und letzte Klammer die S C H A C H T E L U N G S T I E F E = 1 und ist d i e s e sonst > 1 , dann bilden alle Klammern eine Liste. Tab.1.1-1.

Verfahren zum bei Listen

Erkennen

der

Schachtelungstiefe

Ergebnis des Verfahrens: Es liegt eine ordnungsgemäße Liste vor. Die zunächst ungewohnte Vielzahl von Klammern zeigt nach kurzer Eingewöhnungszeit ihre Vorteile. Wir sparen damit das Schreiben vieler „BEGINEND"- oder „DO-OD"-Paare ein. Auch das Problem der Reichweite bei der

8

I. Konstrukte (Bausteine zum Programmieren)

Schachtelung von Alternativen (vgl. Abschnitt 2.2.2), das sogenannte „baumelnde" ELSE-Problem, wird vermieden.

(JT^ÄV)

= 3 =

( — )

( — ) ( — )

(

1.2 Semantik: Regeln zum Auswerten (Evaluieren) Der Prozeß, betont im Namen „L/St Processing", bewirkt die Auswertung von symbolischen Ausdrücken. Ein LISP-System liest einen symbolischen Ausdruck ein, z.B. von einer öffnenden Klammer bis zur korrespondierenden schließenden Klammer, und wertet diesen Ausdruck aus. Als Vollzug der Auswertung gibt es einen symbolischen Ausdruck zurück, der als Wert (engl.: value) des eingelesenen Ausdruckes betrachtet wird. Der Prozeß besteht aus drei Phasen: o Einlesen (READ-Phase), o Auswerten (EVAL-Phase) und o Ausgeben (PRINT-Phase). Ein LISP-System kann nach diesen drei Phasen den nächsten symbolischen Ausdruck auswerten. Der Prozeß ist somit ein READ-EVAL-PRINT-Zyklus (vgl. Bild 1.2-1). In diesem Buch sind die drei Prozeßphasen durch folgende Notation abgegrenzt: eval>

— >

¡Kommentar

Beispiel: eval>

(ABBUCHUNG! MEYER-AG 6 7 . 2 3 ) = = > 4 1 . 4 5

;Neuer

Saldo

Der nach dem Prompt eval> notierte symbolische Ausdruck durchläuft die READ- und die EVAL-Phase. Der symbolische Ausdruck, notiert nach dem Pfeil = = > , ist der ausgegebene Wert, d.h. das Ergebnis der PRINT-Phase. Der Text in einer Zeile, notiert hinter einem Semikolon, ist zusätzlicher Kommentar. Die Regeln zur Auswertung („Evaluierung") definieren die operationale Semantik von LISP, d.h. die Bedeutung der symbolischen Ausdrücke für die Abarbeitung durch das LISP-System. Diese „mechanische" Semantik ist in dem EVAL-

1. Einführung: Handhabung von Konstrukten

9

Konstrukt des LISP-Systems abgebildet. Die Regeln sind daher selbst in LISP formuliert.

- 0 eval> 12.34567 ==> 12.34567 eval> #\a ==> #\a eval> "Mein Programm: " ==> "Mein Programm: " eval> TRUE ==> #T e v a l > F A L S E ==>

()

¡Zeichen

(engl.:

character)

;Zeichenkette (engl.: string) ¡Repräsentation des W a h r h e i t s ; wertes "wahr" ¡Repräsentation des W a h r h e i t s ; wertes "nicht wahr"

10

I. Konstrukte (Bausteine zum Programmieren)

EVAL-Regel 2: Evaluieren einer Liste des Typs ( . . . )

Ist der symbolische Ausdruck eine Liste des Typs ( . . . ) dann ergibt sich der Wert durch Evaluieren der Argumente ... und anschließender Anwendung der Funktion auf diese Werte. Hier sei eine Funktion durch ein Symbol benannt. Zum Beispiel benennt das Symbol + üblicherweise die Additionsfunktion. (Anonyme Funktionen vgl. Abschnitt 1.4.1). ^Hinweis: Begriff "Funktion". Wie in LISP-ischer Terminologie üblich (vgl. z.B. Allen, 1978 oder Stoyan/Görz, 1984) sprechen wir hier von einer Funktion oder auch LISP-Funktion, obwohl das Konstrukt im mathematischen Sinne keine Funktion darstellt, sondern eine Prozedur oder einen Algorithmus. Der Begriff „Prozedur" betont den Prozeß für die Berechnung von Werten für eine Funktion. Mathematisch ist eine Funktion eine existierende eindeutige Zuordnung von Elementen der Definitionsmenge (engl.: domain) zu Elementen der Wertemenge (engl.: ränge), wobei es keinen Berechnungsprozeß gibt. Wenn verkürzt formuliert ist: „Eine Funktion F gibt den Wert Y zurück.", dann ist damit gemeint, daß die algorithmische Repräsentation der Funktion F den Wert Y produziert; kurz: Es handelt sich um die Ausführung der Prozedur F.] Mit der Nennung des Operations-Symbols als erstem Element einer Liste folgt LISP der Präfix-Notation. Die Präfix-Notation, auch Polnische Notation nach der polnischen Logikerschule um J. Lukasiewicz (1878 bis 1956) genannt, bezieht sich in LISP auf alle Operationen. Arithmetische Funktionen wie Addition, Subtraktion, Multiplikation oder Division haben damit eine gegenüber der Schulmathematik ungewohnte Schreibweise. Die Präfix-Notation gewährleistet eine einheitliche Syntax, unabhängig von der Zahl der Argumente. Die Operation steht stets an erster Stelle in einer Liste. Beispiele zur EVAL-Regel 2: eval> ( + 1 2 3) ==> 6 eval> (SIN 0) ==> 0. eval> (COS 1) ==> 0.54030230586814 eval> (* 11111111111111111111111111111111111111111111111111111 22222222222222222222222222222222222222222222222222222

33333333333333333333333333333333333333333333333333333) ==> 82304526748971193415637860082304526748971193415637857 61316872427983539094650205761316872427983539094650208 230452674897119341563786008230452674897119341563786 Die Argumente ... können Listen sein und damit ebenfalls dem Konstrukt-Typ ( ... ) entsprechen, so daß auch für sie die EVAL-Regel 2 anzuwenden ist.

1. Einführung: Handhabung von Konstrukten

11

eval> (+ 1 (+ 1 3) 1) ==> 6 eval> (MAX (+ 0 . 4 0 . 6 ) (+ 2.34 1)) ==> 3.34 eval> (+ ( - (+ (- 1 1) 1) 1) 1) ==> 1 EVAL-Regel 3: Evaluieren einer Liste des Typs (

... )

Ist der symbolische Ausdruck eine Liste des Typs ( < s p e z i e l 1es-symbol> . . . ) dann hängt der Wert von der Definition des speziellen Symbols ab. Ob von den einzelnen Argumenten ... einige evaluiert werden oder nicht, bestimmt die Definition, die das spezielle Symbol benennt. Ein LISP-System erkennt seine speziellen Symbole, so daß es nicht die EVAL-Regel 2 anwendet, sondern die für das spezielle Symbol definierte Evaluierung ausführt. Beispiele zur EVAL-Regel 3: Spezielles Symbol IF. Bei der Alternative (vgl. Bild 1.2-2) in der Art „if ... then ... eise ... end-if", die anhand des speziellen Symbols IF erkannt wird, evaluiert das LISP-System zunächst das erste Argument, hier cbedingungssexpr>. Sein Wert bestimmt die weitere Abarbeitung, d.h. welches Argument als nächstes zu evaluieren ist. Ist der Wert von ungleich NIL, d.h. die Bedingung gilt als erfüllt („True-Fall"), dann wird evaluiert und der gesamte IF-Ausdruck hat den Wert von . Ist der Wert von gleich NIL, d.h. die Bedingung gilt als nicht erfüllt („False-Fall"), wird die Alternative, formuliert als drittes Argument, evaluiert. Im „False-Fall" hat der gesamte IF-Ausdruck den Wert von . ( I F

)

Bild 1 . 2 - 2 . Alternative - Graphische D a r s t e l l u n g nach DIN 66261 (Struktogramme, v g l . Nassi/Shneiderman, eval> ( I F NIL "Ja" "Nein") ==> "Nein" eval> (IF #T "Ja" "Nein") ==> "Ja" eval> (IF 1 2 3) ==> 2

1973)

12

I. Konstrukte (Bausteine zum Programmieren)

eval>

( I F ANTWORT? "Dank f ü r d i e Z u s t i m m u n g . " "Denken S i e m i t ! " ) = = > "Dank f ü r d i e Z u s t i m m u n g . "

;Bei d i e s e m R ü c k a g e b e w e r t hat das Symbol ANTWORT? e i n e n Wert ; u n g l e i c h N I L . Er bestimmt s i c h nach der Regel f ü r das E v a l u ; i e r e n von Symbolen ( v g l . EVAL-REGEL 4 ) .

Spezielles Symbol DEFINE. eval>

( D E F I N E < s e x p r > ) ==>

Das spezielle Symbol DEFINE bindet ein Symbol an den Wert von im aktuellen Kontext, d.h. die Bindung findet in der Umgebung statt, in dem die DEFINE-Auswertung erfolgt. (Näheres zur Auswertungsumgebung später, vgl. z.B. Abschnitt 1.4.2). Dabei wird das erste Argument, hier , nicht evaluiert; das zweite, hier , jedoch. Der Wert des gesamten DEFINE-Konstruktes ist uninteressant und deshalb prinzipiell auch nicht spezifiziert. Da der READ-EVAL-PRINT-Zyklus für jeden eingelesenen symbolischen Ausdruck einen Wert zurückgibt (PRINT-Phase), hat auch das DEFINE-Konstrukt stets einen Wert. In PC Scheme wird dazu das gebundene Symbol als Wert zurückgegeben. Die Aufgabe des DEFINE-Konstruktes ist nicht die Rückgabe eines Wertes, sondern das Binden eines Symbols an einen Wert. Anders formuliert: Zwischen dem Symbol und seinem Wert entsteht eine Verbindung (Assoziation). Das Symbol „benennt" den Wert. Das DEFINE-Konstrukt ist somit der „Benennungsoperator" für symbolische Ausdrücke. /Common LISP: SETQ-Konstrukt. Die Bindung übernimmt in Common LISP und in vielen anderen Dialekten das SETQ-Konstrukt. Sein (Rückgabe-)Wert ist nicht das Symbol, sondern der Wert, an den das Symbol gebunden wird.7 eval> eval>

( D E F I N E MÜLLER-OHG ( * 0 . 1 4 1 0 0 ) ) ( D E F I N E MEYER-AG " D - 7 5 0 0 K a r l s r u h e " )

= = > MÜLLER-OHG = = > MEYER-AG

/Common LISP: C-eval>

(SETQ MÜLLER-OHG ( * 0 . 1 4 1 0 0 ) ) ==> 14

]

EVAL-Regel 4: Evaluieren eines Symbols Ist der symbolische Ausdruck ein Symbol, dann ist sein Wert derjenige symbolische Ausdruck, an den es in der (aktuellen) Umgebung gebunden ist. Gibt es keine entsprechende Bindung, dann führt das Evaluieren des Symbols zu keinem Wert, sondern abhängig vom jeweiligen LISP-System zu einer Fehlermeldung und zum Übergang in einen Fehlerkorrektur-Modus (engl.: inspect/debug-level). Beispiele zur EVAL-REGEL 4:

e v a l > ( D E F I N E SCHMIDT-GMBH 1 4 . 8 9 ) = = > SCHMIDT-GMBH e v a l > SCHMIDT-GMBH = = > 1 4 . 8 9 e v a l > ( D E F I N E MEYER-AG SCHMIDT-GMBH) = = > MEYER-AG

1. Einführung: Handhabung von Konstrukten eval> M E Y E R - A G ==> 14.89 eval> OTTO ==> ERROR V a r i a b l e not defined in current OTTO [inspect] ;Testmodus

13

environment

1.3 Repräsentation von Daten und Programm Im Mittelpunkt der Softwarekonstruktion steht der Begriff „Algorithmus". Darunter versteht man ein eindeutig bestimmtes Verfahren (Prozeß) oder die Beschreibung eines solchen Verfahrens. Diese intuitive Definition verdeutlicht, daß es bei einem Algorithmus nicht nur um ein Verfahren geht, sondern auch um seine Beschreibung. Sie kann in einer natürlichen oder formalen Sprache (z.B. in LISP) oder mit Hilfe eines graphischen Darstellungsmittel (z.B. Struktogramm) erfolgen. Bedeutsam ist die Eindeutigkeit der Beschreibung mit Hilfe eines endlichen Textes. Bestimmt wird ein Algorithmus durch die Vorgabe: o einer Menge von Eingabe-, Zwischen- und Endgrößen, o einer Menge von Elementaroperationen und o der Vorschrift, in welcher Reihenfolge welche Operationen wann auszuführen sind. /Exkurs: Algorithmus. Bei den theoretisch fundierten Definitionen des Alogrithmusbegriffs z.B. von A. M. Turing oder A. A. Markow geht es um Aufzählbarkeit, Berechenbarkeit und Entscheidbarkeit. In diesem Zusammenhang hat A. Church nachgewiesen, daß bei jeder möglichen Präzisierung des Algorithmusbegriffs die dadurch entstehende Klasse von berechenbaren Funktionen mit der Klasse der allgemein rekursiven Funktionen zusammenfällt (Näheres vgl. z.B. Klaus/Liebscher, 1979).] Im heutigen Programmieralltag z.B. bei der Erstellung eines Buchungsprogrammes in COBOL spricht man eher vom „Programm" als vom "Algorithmus". Dabei unterscheidet man strikt zwischen den „Daten" (Eingabe-, Zwischen- und Endgrößen) und dem „Programm" (Vorschrift basierend auf Elementaroperationen), obwohl im „von Neumann"-Rechner die Trennungslinie zwischen Programm und Daten unscharf ist. Man verbindet mit dem Begriff „Programm" etwas Aktives wie Datenerzeugen, -modifizieren oder -löschen. Die „Daten" selbst sind dabei passiv. Sie werden vom „Programm" manipuliert. Diese Unterscheidung hat für symbolische Ausdrücke wenig Aussagekraft; denn jeder symbolische Ausdruck ist einerseits ein „Programm", dessen Ausgabe (Wert) er im Zusammenwirken mit den EVAL-Regeln definiert, und andererseits das Mittel, um in LISP „Daten" darzustellen. Von Bedeutung ist in LISP die Frage, ob und wie ein symbolischer Ausdruck evaluiert wird. Sie ist entscheidend, wenn man eine Unterteilung in „Daten" und „Programm" vornehmen will. Bei einer Zeichenkette, z.B. "Programmieren ist das Abbilden von Wissen", besteht kein Grund diese zu evaluieren, weil sie selbst das Ergebnis ist. Zahlen, Zeichen und Zeichenketten haben eher passiven

14

I. Konstrukte (Bausteine zum Programmieren)

S Charakter; sie zählen zur Kategorie „Daten". Die Liste ist ambivalent. Sie gehört sowohl zur Kategorie „Daten" als auch zur Kategorie „Programm". Das folgende Beispiel verdeutlicht diese Ambivalenz. Beispiel:

Buchungsprogramm

Vom Konto MEYER-AG ist ein Betrag von DM 67,23 abzubuchen. Wir formulieren folgende Liste: eval>

(ABBUCHEN!

MEYER-AG

67.23)

==>

1200.45

Wir erhalten den neuen Saldo DM 1200.45, vorausgesetzt es existiert eine Funktion ABBUCHEN! und ein Konto MEYER-AG mit dem alten Saldo DM 1267.68 . Das Ergebnis von 1200.45 entstand durch „Laufenlassen" des Programmes: (ABBUCHEN! MEYER-AG 67.23). Diese Liste führt zu einer Ausgabe („Output"), indem sie dem READ-EVAL-PRINT-Zyklus übergeben wird; wir müßten sie der Kategorie „Programm" zuordnen. Jetzt wollen wir annehmen, daß die Information, vom Konto MEYER-AG sei ein Betrag von DM 67.23 abzubuchen, im Sinne von Daten einem LISP-System einzugeben ist. Wir können z.B. unterstellen, es sei nur zu beschreiben, daß abzubuchen ist, aber die Buchung selbst ist nicht zu vollziehen, weil das Konto MEYER-AG noch nicht freigegeben ist. Zunächst könnte diese Information statt als Liste als Zeichenkette dem READ-EVAL-PRINT-Zyklus übergeben werden: eval>

"ABBUCHEN!

MEYER-AG

67.23"

==>

"ABBUCHEN!

MEYER-AG

67.23"

Behalten wir jedoch die Liste bei, dann dürfen die EVAL-Regeln auf diese Liste nicht angewendet werden. Die Auswertung wäre zu blockieren, so daß ABBUCHEN! nicht als Name einer Funktion interpretiert wird. EVAL-Regel 3 bietet die Möglichkeit, anhand eines speziellen Symbols über die Auswertung der Argumente zu entscheiden. Das spezielle Symbol QUOTE repräsentiert diesen „Auswertungsblocker". Um EVAL-Regel 3 zur Anwendung zu bringen, muß

1. Einführung: Handhabung von Konstrukten

15

die Liste als Argument für das spezielle Symbol QUOTE geschrieben werden. Wir formulieren daher: eval>

(QUOTE (ABBUCHEN!

MEYER-AG

67.23)) = = > (ABBUCHEN!

MEYER-AG

67.23)

Da das Einfügen eines symbolischen Ausdrucks in die Liste mit dem Auswertungsblocker QUOTE als erstem Element häufig vorkommt, gibt es dafür eine Kurzschreibweise mit dem Hochkomma ' (engl.: quote mark). Es wird in der READ-Phase zum eigentlichen QUOTE-Konstrukt aufgelöst. eval>

'(ABBUCHEN!

MEYER-AG 6 7 . 2 3 )

==>

(ABBUCHEN!

MEYER-AG

67.23)

Wird das Evaluieren einer Liste blockiert, dann verhält sie sich analog zu einer Zeichenkette. Sie bleibt „bestehen". Wir müßten sie aus dieser Sicht der Kategorie „Daten" zuordnen. Einprägsam formuliert: Die „Quotierung" blockiert die Auswertung. Ein quotierter symbolischer Ausdruck entspricht einer Konstanten. Beispiele zum QUOTE-Konstrukt: e v a I > ' ( A B C ) ==> eval > C A 'B ' C ) = = > ERROR .

(AB

C)

Nach E V A L - R e g e l 3 i s t d e r Wert von (QUOTE A) = = > A D i e s e s A i s t weder d e r Programmcode e i n e r Funktion entsprechend EVAL-Regel noch d e r e i n e s s p e z i e l l e n S y m b o l s e n t s p r e c h e n d E V A L - R e g e l 3.

eval>

(SIN ' ( + 0.2 0 . 3 ) ) = = > ERROR . D i e Q u o t i e r u n g des A r g u m e n t s v e r h i n d e r t d i e A u s w e r t u n g von ( + 0 . 2 0 . 3 ) = = > 0 . 5 . Der S i n u s e i n e r L i s t e i s t n i c h t d e f i n i e r t , s o daß e i n e F e h l e r m e l d u n g e r f o l g t .

eval>

(SIN

(+ 0.2

0.3))

==>

0.479425538604203

/Hinweis: Flüchtigkeitsfehler. Häufig haben (Flüchtigkeits-)Fehler ihre Ursache in einer vergessenen oder falschen Hochkomma-Notierung. Prüfen Sie deshalb zunächst, ob der symbolische Ausdruck selbst oder sein Wert gemeint ist (vgl. Abschnitt 2.4).] Das Komplement zum QUOTE-Konstrukt ist das EVAL-Konstrukt. Es bewirkt die Anwendung der EVAL-Regeln. Diese transformieren eine „Konstante" als Datum wieder in die Rolle eines „Programms". In LISP sind „Programme" und „Daten" gleichermaßen als symbolischer Ausdruck abgebildet. Als Merksatz formuliert: Die Repräsentation von LISP-Programmen als LISP-Daten ist eine

16

I. Konstrukte (Bausteine zum Programmieren)

Haupteigenschaft dieser Programmiersprache. Sie ermöglicht das Manipulieren von Programmen durch Programme ohne die übliche Trennung in Systemprogrammierung und Anwednungsprogrammierung. Jeder LISP-Benutzer kann sein LISP-System selbst verbessern, wobei viele dieser Verbesserungen letztlich zur Verbesserung der kaufbaren LISP-Systeme geführt haben (vgl. McCarthy, 1980). Die folgenden EVAL-Beispiele dienen als erster Einstieg. Im Zusammenhang mit der Diskussion des Umgebungskonzeptes (vgl. Abschnitt 1.4.2) wird das EVAL-Konstrukt weiter erklärt. Beispiele zum EVAL-Konstrukt: eval> eval> eval> eval> eval> eval> eval> eval> eval> eval> eval>

'(+ 1 2) = = > (+ 1 2) ( E V A L '(+ 1 2 ) ) = = > 3 ( E V A L ' ( E V A L '(+ 1 2 ) ) ) = = > 3 ( D E F I N E M E I N E - A D D I T I O N '(+ 1 2)) = = > M E I N E - A D D I T I O N MEINE-ADDITION ==> (+ 1 2) (EVAL MEINE-ADDITION) ==> 3 ( D E F I N E N A C H R I C H T 'CIF A N T W O R T ? " M e y e r A G z a h l t ! " "Meyer AG zahlt nicht!") ==> NACHRICHT ( D E F I N E A N T W O R T ? ()) = = > A N T W O R T ? (EVAL NACHRICHT) ==> "Meyer AG zahlt nicht!" ( D E F I N E A N T W O R T ? 'Ja) = = > A N W O R T ? (EVAL NACHRICHT) ==> "Meyer AG zahlt!"

1.4 Konstrukt: Schnittstelle und Implementation Bisher wurde gezeigt: Ist der auszuwertende symbolische Ausdruck eine Liste, dann definiert das erste Element, ob die restlichen Listenelemente (Argumente) evaluiert werden oder nicht. Die restlichen Listenelemente können als Eingabe aufgefaßt werden. Sie sind Eingabe („input") für die Funktion, die durch das erste Listenelement benannt wird. Entsprechendes gilt, wenn das erste Element einen speziellen Fall benennt (EVAL-Regel 3). Die Eingabe-Schnittstelle zum Programmcode eines Konstruktes, das angewendet wird, bestimmt die Zulässigkeit eines Argumentes. So muß die Anzahl der angegebenen Argumente der jeweiligen Schnittstellen-Definition genügen. Zusätzlich muß der Typ eines Arguments passen. Arithmetische Funktionen fordern z.B. numerische Argumente, also symbolische Ausdrücke vom Typ Zahl bzw. Ausdrücke, die zum Typ Zahl evaluieren. Allgemein formuliert: Die Argumente müssen konsistent zur Schnittstelle sein. Beispiele zur Anzahl und zum Typ von Argumenten: eval>

eval> eval> eval>

(IF A N T W O R T ? " g e s t e r n " " h e u t e " " m o r g e n " ) = = > E R R O R ... ; Zu v i e l e A r g u m e n t e ( D E F I N E ) = = > E R R O R ... ;Zu w e n i g e A r g u m e n t e (DEFINE OTTO "Witzig" "Hudelig") = = > E R R O R ... ; Zu v i e l e A r g u m e n t e ( D E F I N E " M E Y E R - A G " " S a l d o i s t DM 1 2 3 . 4 5 " )

1. Einführung: Handhabung von Konstrukten ==>

ERROR

...

eval> (DEFINE MEYER-AG "Saldo eval> MEYER-AG ==> " S a l d o ist DM 1 2 3 . 4 5 "

eval>

(+ 1 2 ' A L T E R - W E R T ) = = > E R R O R ...

;Wertassoziation ; e i n Symbol

17 verlangt

ist DM 1 2 3 . 4 5 " ) = = >

MEYER-AG

¡Zeigt d e n W e r t der ; vorhergehenden DEFINE-Anwendung

; N i c h t n u m e r i s c h e r O p e r a n d für ; arithmetische Operation

eine

Betrachten wir einen symbolischen Ausdruck als gegebenes Konstrukt (als einen bereitgestellten Baustein) zur Abarbeitung einer spezifizierten Aufgabe, dann werden Angaben benötigt über: 1. die (Eingabe-)Schnittstelle, 2. den (Ausgabe-) Wert und über 3. die Nebeneffekte. /Hinweis: Mehrere (Ausgabe-)Werte (engl.: multiple valúes). Manche LISPSysteme, z.B. Common LISP, gestatten als Ergebnis des Evaluierens die Rückgabe von mehreren Werten. Da mehrere Werte (hilfsweise) in einer Liste zusammenfaßbar sind, betrachten wird diese Option nicht weiter./ zu 1) Schnittstelle: Die Kenntnis der Schnittstelle ist für die Wahl korrekter Argumente notwendig. zu 2) Wert: Zumindest muß der Typ des symbolischen Ausdrucks bekannt sein, den wir als Wert des Evaluierens erwarten, wenn wir diesen Wert als Argument für ein weiteres Konstrukt einsetzen wollen. Bekannt sein muß z.B., ob das Konstrukt eine Zahl, ein Symbol oder eine Liste als Wert hat. Beispiel für einen falschen Argument-Typ: eval>

(+ 3 ( D E F I N E M E Y E R - A G

2 4 5 . 3 4 ) 4) = = >

ERROR

...

Die A d d i t i o n s f u n k t i o n e r f o r d e r t n u m e r i s c h e A r g u m e n t e . Der W e r t des K o n s t r u k t e s ( D E F I N E M E Y E R - A G 5) ist M E Y E R - A G , a l s o ein S y m b o l . Es k o m m t w e g e n der I n k o m p a t i b i l i t ä t m i t der S c h n i t t s t e l l e der A d d i t i o n s f u n k t i o n zum F e h l e r .

zu 3) Nebeneffekt: Wenn symbolische Ausdrücke evaluiert werden, können sie nicht nur ihren Wert zurückgeben, sondern zusätzlich den Wert symbolischer Ausdrücke verändern. Sie verursachen einen Nebeneffekt, auch Seiteneffekt genannt. Bisher haben wir als einen solchen symbolischen Ausdruck das DEFINE-Konstrukt vorgestellt, d.h. die Liste mit dem speziellen Symbol DEFINE als erstem Element. Die Nebeneffekte eines Konstruktes müssen bekannt sein, um ihre Wertveränderungen einkalkulieren zu können.

18

I. Konstrukte (Bausteine zum Programmieren) Beispiel für einen Nebeneffekt: eval > S C H U L Z E - G M B H = = > eval > ( D E F I N E M E Y E R - A G ==> MEYER-AG

30.45 (DEFINE SCHULZE-GMBH 123.45)) ;Wert des D E F I N E - K o n s t r u k t e s ist in S c h e m e das g e b u n d e n e S y m b o l . N e b e n e f f e k t der M E Y E R - A G B i n d u n g ist d i e B i n d u n g von S C H U L Z E - G M B H an 1 2 3 . 4 5 . ==> SCHULZE-GMBH eval > M E Y E R - A G eval > (+ 0 . 5 5 S C H U L Z E - G M B H ) = = > 124.

/Exkurs: Signatur. Zur Spezifikation mit Hilfe von elementaren Funktionen (Operationen, nebeneffektfreien Konstrukten) fassen wir die Informationen: - Name der Funktion, - Typ der Argumente („domain" der Funktion) und - Rückgabewert („ränge" der Funktion) unter dem Begriff Signatur zusammen (vgl. Abschnitt 2.2).]

E I N G A B E :

Argument(e)

Schnittstelle [formale Parameter]

K 0 N S T R U K T:

A U S G A B E :

311d 1 . 4 - 1 .

I m p l e m e n t a t i on [Körper]

Wert(e)

Nebeneffekt(e)

LISP-Konstrukt

Solange es um Konstrukte geht, die das jeweilige LISP-System als eingebaute Konstrukte dem Benutzer direkt bereitstellt, ist ihre Implementation eine zweitrangige Frage. Bedeutsam sind für uns ihre Leistungskriterien: Anwortzeit- und Speicherplatz-Bedarf. Eine Darstellung des eigentlichen Programmcodes, der zur Anwendung kommt, hilft selten. Wir interessieren uns nicht für eine "externe" maschinenabhängige Repräsentation des Programmcodes (wohlmöglich, in Form von hexadezimalen Zahlen). Wesentlich ist die Abstraktion in Form des Symbols, das auf den Programmcode verweist. Beispielsweise interessiert uns bei der Additionsfunktion die Abstraktion in Form des + Symbols, verbunden mit Angaben zur Schnittstelle (hier: beliebig viele numerische Argumente), zum

1. Einführung: Handhabung von Konstrukten

19

(Rückgabe-)Wert (hier: eine Zahl) und zur Frage von Nebeneffekten (hier: keine). Soll ein selbst definiertes Konstrukt wie ein eingebautes Konstrukt benutzbar sein, dann ist es vorab zu implementieren. Implementation bedeutet hier: Es ist zu entscheiden, aus welchen dem LISP-System bekannten Konstrukten es zusammensetzbar ist. Unser Konstrukt besteht aus symbolischen Ausdrücken, auf die das LISP-System die EVAL-Regeln erfolgreich anwenden kann. Für die Einführung fassen wir zusammen (vgl. auch Bild 1.4-1): Definition Konstrukt: Ein Konstrukt („Baustein") ist ein symbolischer Ausdruck, konzipiert für die Anwendung der EVAL-Regeln. Sein Einsatz setzt Informationen voraus über: o die Schnittstelle (Anzahl der Argumente, Typ eines jeden Argumentes sowie die Festlegung, welche Argumente evaluiert werden.), o den (Rückgabe-)Wert (Vorhersage des Typs, z.B. ob Zahl, Symbol, Liste etc.) und über o Nebeneffekte (Veränderung des eigenen Zustandes und/oder des Zustandes von anderen symbolischen Ausdrücken). /Hinweis: Konstrukt. Der lateinische Begriff Konstrukt (Construct, Constructum) bezeichnet eine Arbeitshypothese für die Beschreibung von Phänomenen, die der direkten Beobachtung nicht zugänglich sind, sondern nur aus anderen beobachteten Daten erschlossen werden können. In der Linguistik ist z.B. Kompetenz ein solches Konstrukt. Wir bezeichnen mit Konstrukt einen verwendbaren „Baustein", der bestimmte Eigenschaften hat./ Beispiel zur Bezeichnung Konstrukt: Wir sprechen von einem DEFINE-Konstrukt, wenn es sich um die folgende Zeile handelt: (DEFINE

F00

'(INTERFACE

VALUE

SIDE-EFFECTS

BODY))

1.4.1 LAMBDA-Konstrukt Im Rahmen der EVAL-Regeln wurde gezeigt (vgl. Abschnitt 1.2), daß sich der Wert eines Konstruktes durch die Applikation (Anwendung) des Programmcodes auf die Argumente ergibt. Dabei bestimmt das erste Listenelement den Programmcode. Betrachten wir eine Funktion y = f(x), dann ist die grundlegende Operation die Applikation von f auf ein Argument x, um den Wert y zu erhalten. Bisher wurden eingebaute Konstrukte (Funktionen), wie z.B. + oder - oder MAX, auf Argumente angewendet. Nun wollen wir eigene Funktionen konstruieren. Grundlage dazu bildet das LAMBDA-Konstrukt. Es basiert auf dem Lamb-

20

I. Konstrukte (Bausteine zum Programmieren)

da-Kalkül, einem mathematischen Formalismus zur Beschreibung von Funktionen durch Rechenvorschriften. Alonzo Church hat für die Präzisierung der Berechenbarkeit diesen Lambda-Kalkül formuliert (vgl. Church, 1941; zur Evaluierung von Lambda-Ausdrükken vgl. Landin, 1964; zum Kalkülverstehen vgl. z.B. MacLennan, 1990; zur Implementation vgl. Allen, 1978). Programmiersprachen nach dem Modell des Lambda-Kalküls simulieren die sogenannten a - und ß-Reduktionsregeln des Kalküls mit Hilfe einer Symbol-Wert-Tabelle (Umgebung, vgl. Abschnitt 1.4.2). Die Simulation nutzt eine „Kopierregel", wobei an die Stelle der formalen Parameter (LAMBDA-Variablen) im Orginal in der Kopie die aktuellen Parameter (Argumente) gesetzt werden. Der griechische Buchstabe „X" selbst hat keine besondere Bedeutung. A. Church hat ihn in Anlehnung an die Notation von A.N. Whitehead und B. Russell benutzt (vgl. MacLennan, 1990, p. 357). Zur Eingabe auf alphanumerischen Terminals wird üblicherweise das Symbol LAMBDA verwendet. Eine selbst definierte Funktion entsteht durch Evaluierung des LAMBDAKonstruktes. eval>

(LAMBDA < s c h n i t t s t e l 1 e >

= = > # : : = S i e wird durch e i n e n oder mehrere f o r m a l e P a r a m e t e r im S i n n e von " P I a t z h a l t e r - V a r i a b l e n " f ü r d i e Argumente r e p r ä s e n t i e r t . M e h r e r e " P l a t z h a l t e r " werden a l s E l e m e n t e e i n e r L i s t e n o t i e r t . Die formalen Parameter b e z e i c h n e t man a l s L A M B D A - V a r i a b l e oder a l s l o k a l e V a r i a b l e n . Die f o r m a l e n P a r a m e t e r werden d u r c h S y m b o l e a n g e g e b e n .

::=

#

)

S i e w i r d d u r c h K o n s t r u k t e , b e i denen f o r m a l e P a r a m e t e r an S t e l l e n von A r g u m e n t e n s t e h e n , k o n s t r u i e r t . S i e w i r d auch a l s Term ( e n g l . : f o r m ) b e z e i c h n e t , um zu v e r d e u t l i c h e n , daß es e i n s y m b o l i s c h e r A u s d r u c k i s t , d e r e v a l u i e r t w i r d . Im Zusammenhang m i t d e r S c h n i t t s t e l l e bez e i c h n e t man d i e R e c h e n v o r s c h r i f t a l s (Funktions-)Körper ( e n g l . : body).

: : = S i e i s t d e r R ü c k g a b e w e r t des R E A D - E V A L P R I N T - Z y k l u s und r e p r ä s e n t i e r t h i l f s w e i s e d a s e n t s t a n d e n e F u n k t i o n s o b j e k t , d . h . den Programmcode, der d u r c h d i e S c h n i t t s t e l l e und d i e R e c h e n v o r s c h r i f t d e f i n i e r t i s t .

/Exkurs: Begriff "Argument". Argument ist die Bezeichnung für "Element der Definitionsmenge" einer Funktion. Ist f(x) definiert, so nennt man (salopp) auch

21

1. Einführung: Handhabung von Konstrukten

die Variable x Argument. Exakt sind jedoch nur die für x einzusetzenden Werte als die Argumente von f zu bezeichnen (vgl. z.B. Scheid, 1985).7 /Exkurs: Begriff "Variable". In Programmiersprachen ist die Variable primär als Gegensatz zur Konstanten zu sehen. Sie ist mit einer „Quantität" verbunden, die variiert werden kann. In der Mathematik benutzt man den Begriff „Variable" bei Funktionen im Sinne eines bestimmten, aber anonymen Elements einer Definitionsmenge (z.B. „X sei eine nichtnegative relie Zahl ...").] Beispiel fiir die Definition einer Funktion: Es ist eine Funktion ABBUCHEN zu definieren, die von einem Konto einen Betrag abbucht. Um den Vorgang leicht verstehen zu können, sind einige Zwischenschritte angebracht. Als ersten Schritt formulieren wir folgendes LAMBDA-Konstrukt: eval > (LAMBDA (ALTER_SALDO A B B U C H U N G S B E T R A G ) (- A L T E R _ S A L D O A B B U C H U N G S B E T R A G ) ) ==> # < P R O C E D U R E >

¡Schnittstelle ¡Rechenvorschrift

Die Schnittstelle hat die beiden formalen Parameter ALTER_ SALDO und ABBUCHUNGSBETRAG. Die Rechenvorschrift besteht nur aus der Subtraktionsfunktion, so daß wir eine Zahl als (Rückgabe-)Wert erwarten, wenn die entstandene Funktion angewendet wird. Wegen der Präfix-Notation bestimmt das erste Element einer Liste den anzuwendenden Programmcode. Beispielsweise benennt das Symbol Bindestrich (-) das Funktionsobjekt (den Programmcode) für die Subtraktion. Ermittelt man den Wert vom Symbol Bindestrich (gemäß EVAL-Regel 4, vgl. Abschnitt 1.2), dann erhält man das Funktionsobjekt und kann es auf die Werte seiner Argumente anwenden. Entsprechend ist das LAMBDA-Konstrukt als erstes Element einer Liste zu notieren. Die restlichen Listenelemente sind die Argumente, auf die das durch Evaluierung erzeugte Funktionsobjekt angewendet wird. Anwendung (Applikation) des eval>

LAMBDA-Konstruktes:

((LAMBDA < s c h n i t t s t e l 1 e > < r e c h e n v o r s c h r i f t > ) ... ) —•>

Da die Schnittstellendefinition zu erfüllen ist, sind in unserem Beispiel zwei weitere Listenelemente als Argumente anzugeben. eval>

;; 1. Listenelement ergibt Funktion ((LAMBDA (ALTER_SALDO A B B U C H U N G S B E T R A G ) (- A L T E R _ S A L D O A B B U C H U N G S B E T R A G ) ) 110.00 ¡2. Listenelement ist ihr 1. A r g u m e n t 50.00 ¡3. Listenelement ist ihr 2. A r g u m e n t ) ==> 60. ¡Wert

22

I. Konstrukte (Bausteine zum Programmieren)

Die Zuordnung des ersten Arguments, hier 110.00, zur LAMBDA-Variablen ALTER_ SALDO und des zweiten Arguments, hier 50.00, zur LAMBDA-Variablen ABBUCHUNGSBETRAG wird durch die Reihenfolge definiert. Die Argumente werden gemäß ihrer Reihenfolge an die LAMBDA-Variablen gebunden. Die Bindung der LAMBDA-Variablen folgt der Idee von Stellungsparametern. Ein gleichzeitiges Vertauschen der Reihenfolge der (nebeneffektfreien) Argumente und der LAMBDA-Variablen führt daher zum gleichen Wert. eval>

((LAMBDA (ABBUCHUNGSBETRAG ALTER_SALD0) (- ALTER_SALDO ABBUCHUNGSBETRAG)) 50.00

110.00)

==>

60.

Für den Wert des Konstruktes ist die Benennung der LAMBDA-Variablen unerheblich; soweit nicht Namenskonflikte mit freien Variablen auftreten (Näheres zur Konfliktlösung vgl. Abschnitt 7.4). Für das Verstehen eines Konstruktes ist die Benennung jedoch von entscheidender Bedeutung (Näheres dazu vgl. Abschnitt 11.1). Das folgende Konstrukt hat die Symbole X und Y als LAMBDAVariablen und bewirkt ebenfalls die obige Abbuchung. eval>

((LAMBDA

(X

Y)

(-

Y

X))

50.00

110.00)

==>

60.

;So

nicht!

Wird das LAMBDA-Konstrukt evaluiert, entsteht eine anonyme Funktion. Anonym, weil sie keinen Namen hat. Um die definierte Funktion zu benennen, wird ein Symbol an sie gebunden, d.h. der Wert eines Symbols ist das evaluierte LAMBDA-Konstrukt. Eine solche Bindung bewirkt in Scheme das DEFINEKonstrukt. eval> eval>

( D E F I N E ABBUCHEN (LAMBDA ( A L T E R _ S A L D 0 ABBUCHUNGSBETRAG) ( - A L T E R _ S A L D 0 A B B U C H U N G S B E T R A G ) ) ) ==> ABBUCHEN ==> #

ABBUCHEN

/Hinweis: Funktionsdefinition. Das Konstrukt mit dem der Benutzer eine Funktion definieren kann, hat in LISP-Systemen verschiedene Namen und unterschiedliche Wirkungen. In TLC-LISP heißt es DE, in Common LISP DEFUN. Allen gemeinsam ist das Verknüpfen eines Symbols mit einem Funktionsobjekt .] Das Symbol ABBUCHEN kann nun wie ein eingebautes Konstrukt, z.B. wie das Subtraktions-Konstrukt, angewendet werden. eval> eval>

( A B B U C H E N 1 1 0 . 0 0 5 0 . 0 0 ) ==> ( - 2 3 . 4 0 2 . 3 0 ) ==> 2 1 . 1

60.

Als leicht merkbares, begrenzt verallgemeinerbares Motto formuliert: „Form follwos function". Dieser beinahe klassische Leitsatz der (Industrie-)Designer gilt auch in LISP.

1. Einführung: Handhabung von Konstrukten (DEFINE

VERDOPPLUNG

23

(LAMBDA

(ZAHL)

Lfunction—I

(*

2

ZAHL)))

I—form—I

Das Anwenden einer Funktion auf Argumente ist mit dem APPLF-Konstrukt explizit notierbar. Es entspricht z.B.: eval> eval>

( ' < a r g _ l > ... ') ==> (APPLY < f u n k t i o n > ' ( < a r g _ l > . . . < a r g _ n > ) ) ==> < v a l u e >

Wir können daher auch formulieren: eval>

eval>

;; A p p l i k a t i o n e i n e r anonymen F u n k t i o n (APPLY (LAMBDA ( A L T E R . S A L D O ABBUCHÜNGSBETRAG) ( - ALTER_SALDO ABBUCHUNGSBETRAG)) •(110.00 5 0 . 0 0 ) ) ==> 60. ;; A p p l i k a t i o n d e r b e n a n n t e n F u n k t i o n ABBUCHEN (APPLY ABBUCHEN ' ( 1 1 0 . 0 0 5 0 . 0 0 ) ) = = > 6 0 .

Das explizit notierbare Applikationskonstrukt hat folgende Struktur: e v a l > (APPLY

)

==>

Das APPLY-Konstrukt evaluiert seine beiden Argumente, d.h. sowohl den Ausdruck als auch den Ausdruck . Die Evaluierung von muß zu einer Liste führen. Die Elemente dieser Liste sind die Werte an welche die LAMBDA-Variablen von gebunden werden und zwar in der genannten Reihenfolge. eval> eval>

( D E F I N E VORGANG-1 ' ( 1 1 0 . 0 0 (APPLY ABBUCHEN VORGANG-1)

50.00)) ==> ==> 60.

VORGANG-1

/Exkurs: Mehrere Argumente. Bei dem Ausdruck ( ) sei eine Funktion und der Wert von die Liste L. Ist für ein Argument definiert, dann wird auf L angewendet. Ist jedoch für mehrere Argumente definiert und sollten deren Werte durch die Elemente von L dargestellt werden, dann ist stattdessen (APPLY ) zu notieren. Im Fall der Liste ist zu unterscheiden zwischen ( ) für „single-argument functions" und (APPLY ) für „multi-argument functions"./ Da das Binden eines Symbols an eine anonyme Funktion, also die Namensvergabe für eine Funktion, eine häufige Aufgabe ist, gibt es in LISP-Systemen dazu eine syntaktische Vereinfachung. Das explizite Notieren von L A M B D A entfällt bei der Kurzschreibweise. Statt (DEFINE (LAMBDA ( . . . ) . . . ) ) kann verkürzt geschrieben werden

24

I. Konstrukte (Bausteine zum Programmieren)

(DEFI NE ( ... ) ...) Bezogen auf unser Beispiel ABBUCHEN können wir daher alternativ formulieren: eval> (DEFINE ABBUCHEN (LAMBDA (ALTEFLSALDO ABBUCHUNGSBETRAG) (- ALTER.SALDO ABBUCHUNGSBETRAG))) ==> ABBUCHEN eval> (DEFINE (ABBUCHEN ALTER_SALDO ABBUCHUNGSBETRAG) (- ALTER_SALDO ABBUCHUNGSBETRAG)) ==> ABBUCHEN /Hinweis: Syntax DEFINE-Konstrukt. Diese syntaktische Vereinfachung des DEFINE-Konstruktes wird im folgenden nicht genutzt. Die Langform dokumentiert unmißverständlich, daß die anonyme Funktion wie ein sonstiger Wert benannt wird. Sie verdeutlicht, daß im Rahmen dieser Benennung keine Unterscheidung zwischen verschiedenen Typen von symbolischen Ausdrücken gemacht wird. Ein Symbol wird stets mit der Syntax (DEFINE ) gebunden. (Zur Diskussion der Vor- und Nachteile einer Unterscheidung der Bindung eines Funktionswertes von einem sonstigen Wert vgl. z.B. Gabriel/Pitman, 1988 oder Allen, 1987). Zusätzlich besteht die Gefahr, daß eine LlSP-Implementation die syntaktische Vereinfachung nicht in diese Langform transformiert (z.B. Kyoto Common LISP, vgl. Yuasa/Hagiya, 1987, p. 105, oder ältere PC Scheme Versionen, die in ein NAMED-LAMBDA-Konstrukt transformieren).; Wir halten fest: Das LAMBDA-Konstrukt ist in zwei Rollen zu betrachten: 1. Funktionale Abstraktion, d.h. Definition einer Funktion anonyme Funktion: (LAMBDA . . . ) benannte Funktion: (DEFINE (LAMBDA . . . )) 2. Applikation, d.h. Anwendung einer Funktion anonyme Funktion: ((LAMBDA . . . ) . . . ) benannte Funktion: ( . . . ) Im folgenden erweitern wir die Funktion ABBUCHEN um die Fortschreibung des Kontos. Dazu wird ein Beispielkonto MEYER-AG mit dem Anfangswert 1000000.00 initialisiert. eval> (DEFINE MEYER-AG 1000000.00)

MEYER-AG

Wendet man die Funktion ABBUCHEN an, dann ist festzustellen, daß wir für die Abbuchung eine bleibende Zustandsänderung des Kontos benötigen. Die Zustandsänderung ist bisher nicht als Rechenvorschrift in dem ABBUCHENKonstrukt definiert.

1. Einführung: Handhabung von Konstrukten eval>

(ABBUCHEN MEYER-AG 3 0 0 0 0 0 . 0 0 ) ==> 700000. ; e r s t e Abbuchung

eval>

(ABBUCHEN

MEYER-AG 8 0 0 0 0 0 . 0 0 ) zweite Abbuchung - schön aber u n s i n n i g , denn w i r den Wert - 1 0 0 0 0 0 . 0 0 .

==>200000.

25

f ü r d i e MEYER-AG, erwarten

Das SET!-Konstrukt (engl, pronounced: „set-bang") ändert den Wert eines Symbols. eval>

(SET!

)

==>

Wenn ein Symbol (in den erreichbaren Umgebungen) existiert, ersetzt das SET!Konstrukt seinen Wert durch den Wert von . Da der bisherige Wert „zerstört" wird, bezeichnet man SET! als ein destruktives Konstrukt. Destruktive Konstrukte verändern Zustände. Wir sind allgemein nicht an ihrem (Rückgabe-)Wert interessiert, sondern an dem Nebeneffekt, d.h. an der Zustandsänderung. Der Wert eines destruktiven Konstruktes ist daher prinzipiell nicht spezifiziert. Abhängig vom jeweiligen LISP-System wird ein Wert zurückgegeben, um dem READ-EVAL-PRINT-Zyklus zu genügen (vgl. Abschnitt 1.2). Bewirkt ein Konstrukt einen Nebeneffekt, dann fügen wir der Bezeichnung dieses destruktiven Konstruktes ein „ ! " (Ausrufezeichen) hinzu. eval> eval> eval>

( D E F I N E SCHULZE-GMBH - 6 0 0 0 0 . 0 0 ) = = > SCHULZE-GMBH ( S E T ! SCHULZE-GMBH + 1 2 3 4 . 5 6 ) = = > 1 2 3 4 . 5 6 SCHULZE-GMBH ==> 1 2 3 4 . 5 6

Wir können die definierte Funktion ABBUCHEN, wie folgt, benutzen, um den Kontostand bleibend zu verändern: eval> eval> eval> eval> eval>

MEYER-AG ==> 1000000. ¡Anfangswert ( S E T ! MEYER-AG (ABBUCHEN MEYER-AG 3 0 0 0 0 0 . 0 0 ) ) = = > 7 0 0 0 0 0 . MEYER-AG ==> 700000. ¡ K o n t o s t a n d nach 1. A b b u c h u n g ( S E T ! MEYER-AG (ABBUCHEN MEYER-AG 8 0 0 0 0 0 . 0 0 ) ) = = > - 1 0 0 0 0 0 . MEXER-AG ==> - 1 0 0 0 0 0 . ; K o n t o s t a n d nach 2. Abbuchung

Diese Schreibweise ist recht umständlich. Wir ergänzen daher die Funktion zum Abbuchen direkt um die Zustandsänderung des Kontos, d.h. um den Nebeneffekt. Das Konstrukt ABBUCHEN nennen wir deshalb jetzt A B B U C H E N ! (am Ende mit einem Ausrufezeichen versehen). Um eine Funktion A B B U C H E N ! verstehen zu können, erläutern wir vorab einige Aspekte von EVAL und A P P L Y , bezogen auf die jeweilige Umgebung. Im Abschnitt 8 befassen wir uns eingehend mit der Funktion und ihrer Umgebung.

26

I. Konstrukte (Bausteine zum Programmieren)

1.4.2 Umgebung Wenn ein Symbol mit dem DEFINE-Konstrukt kreiert und an einen Wert gebunden wird, dann muß das LISP-System das Symbol und den Wert speichern. Die Assoziation des Symbols zum Wert wird in der Umgebung aufgebaut, die zum Zeitpunkt des Evaluierens des DEFINE-Konstruktes aktuell ist. Eine Umgebung ist konzeptionell als eine Liste vorstellbar, deren Elemente wieder Listen sind. Eine solche Liste ist eine sogenannte Assoziationsliste (vgl. Abschnitt 5.2). ((

)

...

(

))

Befindet man sich auf der Systemebene von PC Scheme, dann ist das Symbol USER-INITIAL-ENVIRONMENT an die aktuelle Umgebung gebunden. Verkürzt formuliert: Die aktuelle Umgebung heißt USER-INITIAL-ENVIRONMENT. Eingebaute Konstrukte, wie z.B. die arithmetischen Funktionen +, - , / und *, sind in PC Scheme in einer eigenen Umgebung, genannt USER-GLOBAL-ENVIRONMENT, gebunden. Mit dem ACCisSS-Konstrukt kann auf den Wert eines Symbols in einer angegebenen Umgebung zugegriffen werden. eval> eval> eval> eval> eval>

(DEFINE (ACCESS (ACCESS (ACCESS (ACCESS

MEYER-AG 1 0 0 0 0 0 0 . 0 0 ) ==> MEYER-AG MEYER-AG U S E R - I N I T I A L - E N V I R 0 N M E N T ) = = > 1 0 0 0 0 0 0 . 0TT0-AG USER-INITIAL-ENVIRONMENT) ==> #!UNASSIGNED MEYER-AG USER-GL0BAL-ENVIR0NMENT) ==> #!UNASSIGNED + USER-GLOBAL-ENVIRONMENT) ==> #

Die Beispiele MEYER-AG und OTTO-AG zeigen, daß ein Symbol als #!UNASSIGNED bezeichnet wird, wenn es in der Umgebung nicht vorkommt. Dabei kann es durchaus in einer anderen Umgebung existieren. Beim Evaluieren des DEFINE-Konstruktes auf Systemebene („top level") hat die Umgebung den Namen USER-INITIAL-ENVIRONMENT. Das EVALKonstrukt ermöglicht die explizite Angabe der gewünschten Umgebung. Wir können daher hier als gleichwertige Ausdrücke formulieren: eval>

(DEFINE

MEYER-AG

1000000.00)

==>

MEYER-AG

oder eval>

(EVAL ==>

'(DEFINE MEYER-AG

MEYER-AG 1 0 0 0 0 0 0 . 0 0 ) USER-INITIAL-ENVIR0NMENT) ;Zu b e a c h t e n i s t das Q u o t i e r e n des D E F I N E K o n s t r u k t e s im E V A L - K o n s t r u k t . Es i s t e r f o r d e r l i c h , weil das E V A L - K o n s t r u k t auch s e i n e r s t e s Argument e v a l u i e r t .

Das Evaluieren eines LAMBDA-Konstruktes führt zum Aufbau einer neuen Um-

1. Einführung: Handhabung von Konstrukten

27

gebung. Um diesen Vorgang zu verdeutlichen, definieren wir eine Funktion, deren Name keine Rolle spielt: Wir nennen sie FOO, so gut wie SINNLOS. /Hinweis: Bedeutungsloser Name. Das Symbol FOO ist eine Silbe ohne Bedeutung, allerdings mit einer langen LISP-Historie. FOO wird allgemein verwendet, wenn der Name des Konstruktes unerheblich ist. Reicht ein Symbol nicht aus, dann verwendet man häufig als weitere bedeutungslose Namen B A R und B A Z J eval>

eval> eval> eval>

( D E F I N E FOO (LAMBDA ( X ) ( D E F I N E MEIN-SYMBOL 0) ( S E T ! M E I N - S Y M B O L 1 0 ) ) ) = = > FOO FOO = = > # < P R O C E D U R E F 0 0 > (FOO 5 ) = = > 10 ; Rückgabewert des S E T ! - K o n s t r u k t e s MEIN-SYMBOL = = > ERROR Symbol i s t n i c h t d e f i n i e r t i n d e r a k t u e l l e n Umgebung USER-INITIAL-ENVIRONMENT

eval>

(ACCESS ==>

MEIN-SYMBOL

USER-INITIAL-ENVIRONMENT) Soll

#!UNASSIGNED

die

Fehler-Situation

mieden werden, nachgeschaut in

der

kann

werden,

Umgebung

mit

verACCESS

ob d a s

gebunden

Symbol

ist.

Weil ein LAMBDA-Konstrukt eine neue Umgebung auftaut, führt die folgende spontane Formulierung nicht zur gewünschten Zustandsänderung unseres Kontos. eval>

( D E F I N E ABBUCHEN! (LAMBDA (K0NT0_NAME ABBUCHUNGSBETRAG) ( S E T ! K0NT0_NAME ; h i e r w i r d d a s Symbol ;: EVAL, w e i l j e t z t der Wert von ; ; b e n ö t i g t wi r d . (-

(EVAL

K0NT0_NAME)

benötigt K0NT0_NAME

ABBUCHUNGSBETRAG)))) ==>

eval>

MEYER-AG ==>

eval>

(ABBUCHEN!

eval>

MEYER-AG = = >

ABBUCHEN!

1000000.

'MEYER-AG 1000000.

300000) ;Nicht

==>

700000.

verändert!

Die destruktive Veränderung des SET!-Konstruktes betrifft die vom L A M B DA-Konstrukt erzeugte Umgebung und nicht die USER-INITIAL-ENVIRONMENT, in der das Symbol M E Y E R - A G den assoziierten Wert hat, der zu verändern ist. Für die gewünschte Funktion A B B U C H E N ! benötigen wir ein Konstrukt zum

28

I. Konstrukte (Bausteine zum Programmieren)

Erzeugen einer Liste. Bisher haben wir Listen direkt notiert und nicht mit einem Konstrukt erzeugt. Jetzt nutzen wir den eingebauten Konstruktor LIST. LIST-Konstrukt:

Es hat als Wert eine Liste, gebildet aus den Werten seiner Argumente. eval>

(LIST

...

)

eval> eval> eval> eval> eval>

(LIST (LIST) (LIST (LIST (LIST

'A 'B ' C ) ==> (A B C) ==> ( ) ( ) ) ==> (()) 'SET! ' M E Y E R - A G 1 2 . 3 4 ) ==> ( S E T ! M E Y E R - A G 1 2 . 3 4 ) 'SET! ' M E Y E R - A G ( - 50 3 9 ) ) = = > ( S E T ! M E Y E R - A G 1 1 )

==>

(

...

)

Die Funktion ABBUCHEN! läßt sich mit dem LIST-Konstrukt und dem EVALKonstrukt bei explizit angegebener Umgebung, wie folgt, notieren: eval>

eval> eval> eval>

( D E F I N E ABBUCHEN! (LAMBDA (KONTCLNAME ABBUCHUNGS_BETRAG) (EVAL (LIST 'SET! KONTCLNAME (- ( E V A L K0NT0_NAME) ¡ W e r t von K0NT0_NAME ABBUCHUNGSBETRAG)) U S E R - I N I T I A L - E N V I R O N M E N T ) ) ) ==> ABBUCHEN! M E Y E R - A G ==> 1 0 0 0 0 0 0 . (ABBUCHEN! ' M E Y E R - A G 3 0 0 0 0 0 . 0 0 ) ==> 700000. (ABBUCHEN! ' M E Y E R - A G 8 0 0 0 0 0 . 0 0 ) ==> - 1 0 0 0 0 0 .

Das ABBUCHENI-Konstrukt, definiert auf der Basis des LAMBDA-Konstruktes, gibt nicht nur einen Wert zurück, sondern verändert auch den Wert seines ersten Arguments. Es entspricht dem Bild 1.4-1. Die Schnittstelle wird durch die beiden LAMBDA-Variablen KONTO. NAME und ABBUCHUNGS_ BETRAG dargestellt. Den Nebeneffekt ruft das enthaltene SET!-Konstrukt hervor. Die explizite Angabe der Umgebung beim EVAL-Konstrukt sorgt dafür, daß der Wert in der richtigen Umgebung verändert wird. Wir betrachten im folgenden das Bild 1.4.2-1, um die Symbol-Wert-Assoziation und ihre Implementation zu erläutern. Faßt man eine Symbol-Wert-Assoziation als die Benennung eines Wertes auf, dann hat das Symbol die Rolle einer Variablen. Wir bezeichnen ein Symbol als Variable, wenn wir seine Wert-Assoziation betonen wollen. In Programmiersprachen benennen Variable häufig Werte („Objekte") in zwei Schritten (vgl. z.B. Clinger, 1988, p. 224). Eine Variable verweist auf einen Speicherplatz (hier in LOCATIONS). Dieser Speicherplatz enthält umgangssprachlich den Wert; präzise formuliert: Den Zeiger, der auf den Wert zeigt. Gewöhnlich sprechen wir verkürzt von dem Wert der Variablen. Wesentlich ist, daß es aufgrund dieses zweistufigen Benennungsverfahrens zwei Wege gibt, um das zu ändern, was eine Variable benennt:

1. Einführung: Handhabung von Konstrukten

29

Legende: 9999 #!UNASSIGNED

::= Bezeichnet den Platz, der "ungebunden" entspri cht. ::= Bezeichnet den Wert ("Objekt"), der "nicht initialisiert" entspricht.

Bild 1.4.2-1. Zweistufiges

Verfahren

zur

Benennung

von

Merten

o Durch Bindung (engl.: binding) kann die Variable zu einem anderen Speicherplatz (hier: LOCATIONS) zeigen. o Durch Zuweisung (engl.: assignment) kann ein anderer Wert an dem Platz gespeichert werden, an den die Variable gebunden ist. Den Speicher (hier: STORE) kann man sich als ein Lexikon mit einem Schlagwort für jeden Platz (in LOCATIONS) vorstellen. Das Lexikon dokumentiert, welcher Wert an diesem Platz gespeichert ist. Viele Eintragungen weisen aus, daß kein Wert an ihrem Platz gespeichert ist, d.h. der Platz ist nicht initialisiert, weil er (noch) nicht benutzt wird. Eine Umgebung ist ebenfalls ein Lexikon. Es enthält Schlagwörter für jede ihrer Variablen, die ausweisen, an welchen Platz die Variable gebunden ist. Manche Eintragungen dokumentieren, daß die Variable ungebunden ist, d.h. die Variable ist an keinen eigenen Platz gebunden, sondern an den Platz, der „ungebunden" entspricht. Aus der zeitlichen Perspektive sind daher zwei Aspekte zu betrachten:

30

I. Konstrukte (Bausteine zum Programmieren)

o Namen, die ihre Werte („Quantitäten") ändern und o Werte, die ihre Namen ändern. In der klassischen Programmierung, z.B. in der COBOL-Welt, konzentriert man sich auf die Namen und verfolgt schrittweise ihre Werteänderung. Im Kontext der LAMBDA-Bindung ist die zweite Sicht zweckmäßig. Es geht um Werte, die neu benannt werden. Die LAMBDA-Variable ist ein neuer Name für den Wert eines Arguments. So gesehen ist das LAMBDA-Konstrukt ein UmbenennungsKonstrukt (vgl. Steele, 1976). /Exkurs: Aliasname. Die Möglichkeit einem existierenden Objekt mehrere Namen geben zu können, verursacht Transparenzprobleme. Ist das Objekt über jeden Namen modifizierbar, dann müssen wir stets alle Namen verfolgen, um eine Aussage über den Objektzustand machen zu können (vgl. z.B. Abschnitt 5.1). eval> eval> eval> eval>

(DEFINE F00 "Otto") ==> F00 (ALIAS BAR F00) ==> BAR (SET! BAR "Emma") ==> "Emma" F00 ==> "Emma" ¡ m o d i f i z i e r t mit BAR-Änderung

]

Wird in Scheme ein LAMBDA-Konstrukt evaluiert, dann kennt die entstandene Funktion in welcher Umgebung sie evaluiert wurde. Das durch # repräsentierte (Funktions-)Objekt ist verknüpft mit seiner Definitions-Umgebung. Die Umgebung und die Funktion bilden eine Einheit. Bildlich gesehen umhüllt die Umgebung die Funktion. Für die Verknüpfung von Umgebung und Funktion hat sich daher der englische Begriff „closure" (deutsch: Hülle oder auch Abschließung) durchgesetzt. Die Umgebung ist „geschlossen" im Hinblick auf die Variablen (engl.: closed over the variables), weil diese nicht außerhalb bzw. ohne diese Umgebung zugreifbar sind. In Scheme hat das THE-ENVIRONMENT-Konstrukt die aktuelle Umgebung als Wert. Hier ein kurzes Beispiel (Näheres vgl. Abschnitt 8). eval> (DEFINE F00 (LAMBDA () (DEFINE BAZ (LAMBDA (X Y) (+ X Y))) (THE-ENVIRONMENT))) ==> F00 eval> (EVAL '(BAZ 2 4) (F00)) ==> 6 Das CLOSURE-Konzept wurde 1964 von P. Landin im Zusammenhang mit seinem Vorschlag für die Auswertung von Lambda-Ausdrücken angegeben (vgl. Landin, 1964). Es ist die Lösung für Probleme beim Umgang mit Funktionen als Argumente und als Rückgabewerte von Funktionen. Diese sogenannten FUNARG-Probleme treten auf, wenn die Funktion beim Aufruf nicht mehr über ihre Definitionsumgebung verfügt (vgl. z.B. Moses, 1970).

1. Einführung: Handhabung von Konstrukten

31

/Hinweis: FUNARG-Probleme. Man spricht vom „downward funarg"-Problem, wenn eine LAMBDA-Variable an eine Funktion gebunden wird; d.h. eine Funktion ist Wert eines Argumentes. Das „upward funarg"-Problem entsteht, wenn der Wert einer Funktion eine Funktion ist, d.h. eine Funktion gibt eine anschließend anzuwendende Funktion zurück.7 Klassische LISP-Systeme können die Funktion nur mit der Umgebung abarbeiten, die bei ihrem Aufruf besteht. Die Definitions-Umgebung ist nicht mit der Funktion verknüpft. Da die Umgebung beim Aufruf von der Situation zum jeweiligen Aufrufungszeitpunkt abhängt, ist diese stets eine andere. Es besteht eine dynamische Situation; man spricht daher von einer „dynamischen Bindung". Das CLOSURE-Konzept ist demgegenüber eine „statische Bindung", da sich die Definitionsumgebung für die Funktion nicht ändert. Im Scheme-Kontext bezeichnet man eine „statische Bindung" als „lexikalische Bindung" und die „dynamische Bindung" als „fluid binding". Wenn eine Funktion angewendet wird, dann sind die Werte der Argumente in den Funktionskörper zu „übernehmen". Die folgenden drei Schritte stellen ein Erklärungsmodell für diese Übernahme dar (vgl. Bild 1.4.2-1): 1. Schritt: Binden der LAMBDA-Variablen o Es werden neue Plätze (in LOCATIONS) für jede LAMBDA-Variable der Funktion belegt. o Die Werte der Argumente des Funktionsaufrufes werden in den korrespondierenden neuen Plätzen gespeichert. Das führt zu einem neuen STORE. o Die LAMBDA-Variablen werden dann an die neuen Plätze (in LOCATIONS) gebunden. Dies entspricht einer Neubenennung von Werten. o Diese Bindung bewirkt eine neue Umgebung (ENVIRONMENT). In der neuen Umgebung zeigen die LAMBDA-Variablen auf die neuen Plätze (in LOCATIONS). 2. Schritt: Evaluieren in neuer Umgebung Die Rechenvorschrift der Funktion wird in der neuen Umgebung in Verbindung mit dem neuen STORE evaluiert. 3. Schritt: Aktualisieren der alten Umgebung Nach vollzogener Auswertung werden die geschaffenen Bindungen der LAMBDA-Variablen wieder aufgelöst. Die alte Umgebung wird wieder zur aktuellen Umgebung. Bildlich formuliert, entspringt die „lexikalische Bindung" folgender Überlegung: Wenn jemand einen Satz (Funktion) formuliert, dann unterstellt er seinen

32

I. Konstrukte (Bausteine zum Programmieren)

Worten (Symbolen) eine bestimmte Bedeutung. Er unterstellt z.B. die Erläuterungen im Sinne des Lexikons „Duden". Sein Satz wird möglicherweise falsch interpretiert, wenn der Leser ein anderes Lexikon annimmt. Die Verknüpfung der Funktion mit dem Definitionslexikon soll die korrekte Interpretation sichern.

1.5 Zusammenfassung: EVAL, APPLY und LAMBDA LISP (LISt Processing) ist eine formale Sprache, eine dialogorientierte Software-Entwicklungsumgebung, eine Menge vordefinierter Konstrukte und ein Formalismus zur Spezifikation. LISP-Konstrukte haben eine einfache Syntax (Grammatik) und (als Dialekt Scheme) eine einfache Semantik (Bedeutung). Symbole, Zahlen, Zeichen, Zeichenketten und Listen sind symbolische Ausdrücke. Jeder symbolische Ausdruck ist ein syntaktisch korrekt konstruiertes LISP-„Programm". Die Regeln zur Auswertung symbolischer Ausdrücke (EVAL-Regeln) definieren die Semantik. Sie bilden das EVAL-Konstrukt Den READ-EVAL-PRINT-Zyklus notieren wir wie folgt: eval> = >

Ist eine Liste zu evaluieren, dann bestimmt das erste Element, wie die restlichen Listenelemente behandelt werden. Benennt z.B. das erste Element eine Funktion, dann sind die restlichen Elemente Argumente, die vor der Übernahme in den Funktionskörper evaluiert werden. Abweichend von der üblichen Notation f(x,y) verschiebt LISP die Funktion f in die Klammer und verwendet als Trennzeichen das Leerzeichen statt dem Komma. Die Funktionsapplikation kann auch durch das APPLY-Konstrukt explizit angegeben werden. Ist das erste Element ein spezielles Symbol, z.B. DEFINE oder IF, dann hängt es vom jeweiligen Symbol ab, ob Argumente evaluiert werden oder nicht. Das spezielle Symbol QUOTE blockiert die Auswertung. Das Symbol EVAL führt zur Anwendung der EVAL-Regeln, wobei eine Umgebung explizit angebbar ist. Das EVAL-Konstrukt dient als Gegenstück zum QUOTE-Konstrukt. NIL repräsentiert die leere Liste und den Wahrheitswert „nicht wahr" (#F; engl.: false). Alle Werte ungleich NIL werden als Wahrheitswert „wahr" (#T; engl.: true) aufgefaßt. Ein Konstrukt ist ein symbolischer Ausdruck, gekennzeichnet durch Schnittstelle, (Rückgabe-) Wert(e) und gegebenenfalls durch Nebeneffekt(e). Das LAMBDA-Konstrukt dient zur Definition von eigenen Konstrukten. Die LAMBDA-Variablen bilden die Schnittstelle; die Rechenvorschrift setzt sich aus eigenen und/oder eingebauten Konstrukten zusammen. Der Wert eines Symbols ist von der Umgebung abhängig, in der das Symbol evaluiert wird. In Scheme hat die Ausgangsumgebung den Namen USER-INITIAL-ENVIRONMENT. Jedes LAMBDA-Konstrukt baut eine eigene Umgebung auf. Es übernimmt damit die Aufgabe der Neubenennung von Werten.

2. Kombinieren von Konstrukten Charakteristische

33

Beispiele fiir Abschnitt 1:

eval > eval> eval> eval> eval>

( D E F I N E MÜLLER-OHG 'KUNDE). = = > MÜLLER-OHG ( D E F I N E MEYER-AG (QUOTE MÜLLER-OHG)) = = > MEYER-AG (EVAL MEYER-AG) = = > KUNDE MEYER-AG = = > MÜLLER-OHG MÜLLER-OHG = = > KUNDE

eval> eval>

(EVAL ' ( E V A L ' ( E V A L ' ( E V A L ' ( + 1 2 ( + 1 ( + 2 ( + 3 ( + 4 ) ) ) ) = = > 10

eval> eval>

( D E F I N E ZUSTIMMUNG? " n i e und n i m m e r " ) = = > ZUSTIMMUNG? ( I F ZUSTIMMUNG? " W ä h l e n ! " " N i c h t w ä h l e n ! " ) Der Wert von " n i e und nimmer" i s t => " W ä h l e n ! " u n g l e i c h NIL. Die Bedingung g i l t a l s e r f ü l l t , daher w i r d der "True - F a l l " evalui e r t .

eval> eval>

( D E F I N E ZUSTIMMUNG? ( + 2 3 ) ) = = > ZUSTIMMUNG? ( I F ZUSTIMMUNG? "Wä l e n ! " " N i c h t w ä h l e n ! " ) ==> "Wählen!" ;Der Wert 5 i s t u n g l e i c h N I L . ; E v a l u i e r t w i r d daher der ; "True-Fall".

eval>

(DEFINE

3

4)))))

=> 10

EINZAHLEN!

(LAMBDA

(K0NT0_NAME

(EVAL

(LIST

EINZAHLUNGSBETRAG)

'SET!

K0NT0_NAME

( + (EVAL KONTO.NAME)

EINZAHLUNGSBETRAG))

USER-INITIAL-ENVIRONMENT))) e v a l > MEYER-AG = = >

==>

EINZAHLEN!

1000000.

eval>

(EINZAHLEN!

'MEYER-AG 3 0 0 0 0 0 . 0 0 )

==>

1300000.

eval>

(EINZAHLEN!

'MEYER-AG 8 0 0 0 0 0 . 0 0 )

==>

2100000.

eval>

(DEFINE + - ) ==> +

eval>

(+ 3 4) ==>

¡ A c h t u n g , es w i r d e i n e i n g e b a u t e s ; Symbol m o d i f i z i e r t !

-1

2. Kombinieren von Konstrukten Eine spezifizierte Aufgabe zu programmieren bedeutet, passende Konstrukte selbst zu definieren und diese mit fremden Konstrukten zu kombinieren. Solches „Handhaben" von Konstrukten im Sinne eines Problemlösungsvorganges ist nicht isoliert betrachtbar und bewertbar. Ob eine Lösung („Konstruktion") zweckmäßig ist oder nicht, zeigt sich nicht nur in Bezug zur spezifizierten Aufgabe, die es gilt (möglichst!) korrekt abzubilden. Zusätzlich soll die Konstruktion

34

I. Konstrukte (Bausteine zum Programmieren)

leicht an eine geänderte Situation anpaßbar sein. Die zu erreichende SoftwareQualität bezieht sich auf die Gesamtheit von Eigenschaften und Merkmalen; auf die Gebrauchstauglichkeit (funktionale Qualität), die Entwicklungsfähigkeit und die Übertragbarkeit (realisierungstechnische Qualität). Das Einbeziehen der Anforderungen des gesamten Software-Lebenszyklusses ist zwingend erforderlich (Näheres vgl. Abschnitt 12). Die Bewertung einer Konstruktion umfaßt alle Phasen, z.B.: Problemanalyse, Anforderungsdefinition, Konzeptionierung, logischer Entwurf, Detailentwurf, Programmierung, Testen, Implementation, Betrieb und Wartung/Pflege/ Fortentwicklung (vgl. Bild 2-1). Sie wirft unterschiedlichste Fragen auf: ökonomische, gesellschaftliche, soziale, organisatorische, juristische und viele mehr. Unsere Thematik „Konstruktion" zielt auf die software-technischen Fragen. W i r konzentrieren uns auf die Probleme, die man üblicherweise Software-Ingenieuren zur Lösung überträgt. Viele relevante Konstruktionsaspekte wie z.B. die Sozialverträglichkeit werden nicht diskutiert. Zieht man einen Vergleich zum Bau eines Fahrgastschiffes, dann werden Aspekte der Seetüchtigkeit und Sicherheit anhand der Dicke der Stahlwände geklärt, während die Frage, ob es überhaupt genügend Fahrgäste gibt, dabei nicht berücksichtigt wird. Schon diese eingegrenzte Betrachtung des klassischen Lebenszyklusses (vgl. z.B. Boehm, 1981) zwingt uns permanent zur Entscheidung: Ist diese Konstruktion oder eine ihrer möglichen Alternativen zu wählen? Beispiel:

Entscheidung für eine Lösung

In einem Programm zur Verwaltung von Kunden sei zu überprüfen, ob vorab die Frage nach dem Namen und dem Alter des Kunden beantwortet wurde. Welches der beiden Programmfragmente ist zu bevorzugen und warum? Lösung eval> eval> eval>

1: (DEFINE (DEFINE (DEFINE

eval>

(FOÜ'A

Lösung 2: eval> (DEFINE eval> (DEFINE

A T ) ==> A ; A n w o r t des Namens B ( ) ) ==> B ¡ A n t w o r t des A l t e r s F00 ( L A M B D A (X Y ) ( I F X Y ( ) ) ) ) ==>

B) ==>

F00

()

ANTWORT-NAME T R U E ) ==> A N T W O R T - N A M E A N T W O R T - A L T E R F A L S E ) ==> A N T W O R T - A L T E R

eval > (AND'ANTWORT-NAME

ANTWORT-ALTER)

==>

()

Offensichtlich ist Lösung 2 mit dem AND-Konstrukt leichter verstehbar als L ö sung 1, obwohl beide Lösungen ähnlich aufgebaut sind. Die Lösung 2 weist

2. Kombinieren von Konstrukten

35

einprägsame (mnemotechnisch besser gewählte) Symbole auf und greift auf das AND-Konstrukt der Booleschen Algebra zurück, das als häufig verwendetes Konstrukt schneller durchschaut wird als das hier selbstdefinierte Äquivalent, das als FOO-Konstrukt formuliert ist. Die Auswahlentscheidung zwischen Lösungsalternativen ist allgemein nicht trivial. Es ist schwierig festzulegen, welche Aufgabenteile (Leistungen) in einem Konstrukt zusammenzufassen sind, und wie das Konstrukt im Detail zu formulieren ist. Für das Problem, eine komplexe Aufgabe zweckmäßig in kleinere Einheiten (Konstrukte) zu modularisieren (gliedern), gibt es selten eine Lösung, die unstrittig gegenüber ihren möglichen Alternativen nur Vorteile bietet. Die Menge der konkurrierenden und zum Teil sogar sich widersprechenden Forderungen verhindert, daß es den eindeutigen „Favoriten" gibt. Die Entscheidungen zur Bildung eines Konstruktes sind daher Kompromißentscheidungen. Eine „tragfähige" Kompromißlösung ist einerseits von den allgemein bewährten und anerkannten Konstruktionsregeln und andererseits von individuellen Bevorzugungen und Erfahrungen geprägt. Der folgende Abschnitt stellt anhand des Beispiels einer Kontenverwaltung die Bildung von Konstrukten dar. Im Kapitel II wird mit diesem Beispiel die

36

I. Konstrukte (Bausteine zum Programmieren)

Modularisierung im Zusammenhang mit verschiedenen Abbildungsoptionen (Liste, Vektor, Zeichenkette etc.) weiter vertieft.

2.1 Interaktion: Operand - Operation Bei der Bildung eines Konstruktes orientiert man sich intuitiv an Fachbegriffen des jeweiligen Aufgabengebietes. Sie bezeichnen z.B. reale Objekte (Gegenstände) oder konzeptionelle Einheiten. In unserem Beispiel Kontenverwaltung könnten daher erste Orientierungspunkte sein: Ein Konto, eine Buchung oder ein realer Gegenstand wie der Aktenschrank im Büro des Buchhalters. Ein Konstrukt bildet dann Eigenschaften und ihre Veränderungen einer solchen „realen" Einheit in Wechselwirkung mit anderen „realen" Einheiten ab. Es modelliert die Eigenschaften und Aktionen. Die gedankliche Verknüpfung zwischen „realen" Einheiten und den Sprachkonstrukten ist die Basis für das Formulieren und Verstehen von Programmen. Ein direkter Bezug zwischen „realer" Einheit und Konstrukt, z.B. hergestellt durch eine Namensgleichheit, erleichtert daher das Verstehen (Näheres vgl. Abschnitt 11.1). Die Matrix (Bild 2.1-1) verdeutlicht Alternativen zur Bildung von Konstrukten. Sie skizziert in der Realität vorgefundene Arbeiten/Aktivitäten als Operatoren, bezogen auf Operanden, deren Zustand manipuliert wird. Entsprechend dieser Matrix kann man ein Konstrukt bilden, das ausgehend von einer bestimmten Operation alle möglichen Operanden zusammenfaßt. In diesem Fall ist die Matrixspalte die Klammer für die Zusammenfassung zu einem Konstrukt. Zu definieren sind dann die Konstrukte: NEUANLEGEN, LÖSCHEN, ANMAHNEN etc. .

Operanden KUNDE LIEFERANT KASSENKONTO

Bild 2.1-1. Matrix:

Operati onen NEUANLEGEN LOSCHEN xl x2 x3

Operanden

ANMAHNEN

yi y2 y3

-

Operationen

...

zl z2 Fehl er

- zeilen- oder s p a l t e n o r i e n t i e r t e Konstrukten -

Bildung

von

37

2. Kombinieren von Konstrukten

Das Konstrukt NEUANLEGEN ist auf KUNDE, LIEFERANT, KASSENKONTO etc. anzuwenden. Ist der Operand ein Kunde, dann ist die Operation xl auszuführen. Beim Operand vom Typ Lieferant ist es die Operation x2. Es ist daher zwischen den verschiedenen Typen von Operanden zu unterscheiden. Für diese Unterscheidung gibt es in LISP das COM)-Konstrukt (conrfitional expression). Diese Fallunterscheidung ersetzt eine tiefe Schachtelung von IF-Konstrukten. Bevor wir die Struktur von NEUANLEGEN definieren, wird vorab das COND-Konstrukt erläutert. Konditional-Konstrukt COND: Das COND-Konstrukt wählt die zutreffende Bedingung aus einer Menge beliebig vieler Bedingungen aus. Es entspricht einer Eintreffer-Entscheidungstabelle (vgl. Abschnitt 2.2.2) und hat folgende Struktur: eval > (COND ( < s e x p r n > ... ) ( ...

Eine Liste (

...), genannt Regel oder Klausel (engl.: clause), bildet einen Fall ab. Dabei ist

die Bedingung (das Prädikat) für den einzelnen Fall und die optionalen bis sind die symbolischen Ausdrücke, die der Reihe nach ausgewertet werden, wenn die Bedingung

erfüllt ist.

ist erfüllt, wenn der Wert von

ungleich NIL ist. Der Wert des COND-Konstruktes, also , wird dann nach Evaluierung von bis durch den zuletzt ermittelten Wert bestimmt. Falls j>0, also ein evaluiert wurde, ist der Wert von ; ist j=0, dann ist gleich dem Wert von

. Die Bedingungen werden der Reihe nach von beginnend geprüft, bis erstmals der Wert von

ungleich NIL ist, d.h. als wahr interpretiert wird. Die restlichen Bedingungen werden nicht ausgewertet. Ist keine Bedingung ungleich NIL, dann ist das CONDKonstrukt Undefiniert. Sein Wert wird dann durch NIL repräsentiert. Beispiele zum COND-Konstrukt: eval> (DEFINE ABSOLUTWERT (LAMBDA (X) ( C O N D ( ( < X 0 ) (* -1 X ) ) ("SONST" X)))) = = > ABSOLUTWERT eval> (ABSOLUTWERT -5) ==> 5 eval> (ABSOLUTWERT 5) = = > 5

38

I. Konstrukte (Bausteine zum Programmieren)

eval>

(DEFINE

eval>

;; Anonyme F u n k t i o n m i t N e b e n e f f e k t ( ( L A M B D A (X V ) ¡Anwendung d e r anonymen F u n k t i o n (COND ( ( = X Y ) ( S E T ! * W E G * " K l a u s e l = " ) (* X Y)) ( ( < X Y) ( S E T ! *WEG* " K l a u s e l x < y " ) ( + X Y ) ) ( ( < Y X) ( S E T ! *WEG* " K l a u s e l x > y " ) ( - X Y ) ) (T ( S E T ! *WEG* " K l a u s e l sonst") "Unbekannte Relation!"))) 12 2 3 ) ¡Argumente = = > 35 ¡ R ü c k g a b e w e r t *WEG* ==> " K l a u s e l x < y " ¡Bewirkter Nebeneffekt

eval> eval> eval>

*WEG*

'UNBENUTZT)

==>

*WEG*

( D E F I N E OTTO ( ) ) = = > OTTO ;; Keine K l a u s e l t r i f f t zu! (COND ( ' ( ) "Konstant Nichts") ( ( E V A L ( ) ) "Wert von N i c h t s (OTTO ( ) ) ) = = > ( )

ist

Nichts")

¡1. ¡2. ¡3.

Klausel Klausel Klausel

Beim letzten Beispiel ist nicht sofort erkennbar, ob der Wert des COND-Konstruktes NIL für das Zutreffen der 3. Klausel oder für das Nichtzutreffen aller drei Klauseln steht, d.h. den Undefinierten Fall von COND repräsentiert. Um dieses „Durchlaufen" zu vermeiden, ist es zweckmäßig, am Schluß eines COND-Konstruktes eine True-Klausel zu formulieren. Diese wird entsprechend dem historischen LISP durch das Symbol T abgebildet, wobei an das Symbol T ein Wert ungleich NIL gebunden ist. /Hinweis: T-Klausel. Jeder symbolische Ausdruck, dessen Wert ungleich NIL ist, kann als Prädikat verwendet werden, um eine Klausel zu definieren, die auf jeden Fall zutrifft, also die Aufgabe einer Sonst-Regel (otherwise case) übernimmt. Traditionell verwenden man das Symbol T, obwohl in Scheme der Wahrheitswert ,true' die Standardrepräsentation #T hat./ Für die Unterscheidung z.B. zwischen einem Kunden und einem Lieferanten entsprechend Bild 2.1-1, benötigen wir passende Prädikate, damit das CONDKonstrukt die Auswahl der jeweiligen Operationen übernehmen kann. Für die Typerkennung unterstellen wir hier, daß die Daten eines Kunden in Form einer Liste abgebildet sind, deren erstes Element das Symbol KUNDE ist. Für einen Lieferanten, ein Kassenkonto etc. gibt es entsprechende Listen. (Optionen für eine geeignete Abbildung vgl. Abschnitte 5 bis 10). Vorab definieren wir mit Programmfragment 2.1-1 zwei Kunden, einen Lieferanten und ein Kassenkonto: eval>

(DEFINE

MEYER-AG

'(KUNDE

eval>

(DEFINE

MÜLLER-OHG

eval>

(DEFINE

SCHULZE-GMBH

"Daten

'(KUNDE

zur

"Daten

'(LIEFERANT

Meyer

zur

AG"))

==> MEYER-AG OHG")) = = > MÜLLER-OHG zur SCHULZE-GMBH")) = = > SCHULZE-GMBH

Müller

"Daten

2. Kombinieren von Konstrukten eval>

(DEFINE

39

VERWAHRUNG-1 ' ( K A S S E N K O N T O " D a t e n zum V e r w a h r k o n t o - 1 " ) )

Programmfragment

2.1-1.

Beispiele Kassenkonto

==>

VERWAHRUNG-1

für Kunde, Lieferant gemäß Bild 2.1-1.

und

Um in diesem Beispiel das Symbol MEYER-AG als Kunde zu erkennen, benötigen wir einen Selektor für das erste Element einer Liste und ein Konstrukt, das auf Gleichheit testet. Im Zusammenhang mit dem benötigten Selektor erläutern wir zusätzlich den korrespondierenden Konstruktor. Selektoren CAR und CDR: Das klassische „Pure LISP" basiert auf den beiden Selektoren CAR (historisch bedingte Abkürzung für Contents of Address part of Register; phonetisch [ka:]) und CDR (Contents of Decrement part of Register; phonetisch [kAdd:]). Sie ermöglichen es, die Teile einer Liste zu selektieren. Dabei hat das CAR-Konstrukt das erste Element der Liste, d.h. den sogenannten Kopf, und das CDR-Konstrukt die Rest/wie als Wert. Diese CAR-CDR-Notation bezieht sich auf die klassische Repräsentation von Listen, d.h. auf Adressen im Rechner (vgl. Abschnitt 5). Sie sollte jedoch nicht den Blick dafür verstellen, daß Listen abstrakte Formalobjekte sind, deren Implementation zunächst untergeordnete Bedeutung hat. Plakativ formuliert: Bei der Konstruktion beschäftigen wir uns mit abstrakten Formalobjekten und nicht mit bekannten Adressen! eval> eval> eval> eval> eval> eval>

(CAR M E Y E R - A G ) = = > KUNDE (CAR (CDR M E Y E R - A G ) ) = = > " D a t e n z u r (CAR S C H U L Z E - G M B H ) = > LIEFERANT (CAR (CDR S C H U L Z E - G M B H ) ) = = > " D a t e n (CAR VERWAHRUNG-1) = = > KASSENKONTO (CAR (CDR V E R W A H R U N G - 1 ) ) = = > " D a t e n

Programmfragment

Meyer

AG"

zur

Schulze

GMBH"

zum

Verwahrkonto-1"

2 . 1 - 2 . S e l e k t i o n des Operanden-Typs und der zugehörenden Daten (von Programmfragment2.1 -1)

Beispiele zum CAR- und CDR-Konstrukt: eval> eval> eval> eval>

(CDR (CAR (CAR (CAR

'BREMEN) = = > ERROR . . . ¡ k e i n e L i s t e - QUOTE ! ! ! "BREMEN") = = > ERROR . . . ¡keine Liste - S t r i n g !!! (CDR ' ( E I N S ZWEI D R E I V I E R ) ) ) = = > ZWEI (CDR (CDR (CDR ' ( E I N S ZWEI D R E I V I E R ) ) ) ) ) = = > V I E R

Zur Reduzierung des Schreibaufwandes einer Kette geschachtelter Selektoren ermöglichen LISP-Systeme eine verkürzte Schreibweise in der Form C.. .R, z.B. CADR oder CDDDR. Üblicherweise können CAxR oder CDxR formuliert, wobei x eine Folge von bis zu drei Buchstaben A bzw. D sein kann. Zusätzlich

40

I. Konstrukte (Bausteine zum Programmieren)

existiert ein Selektor, der auf eine angegebene Elementeposition zugreift (vgl. LIST-REF-Konstrukt in Abschnitt 3.3). eval> (CADR '(EINS ZWEI DREI VIER)) ==> ZWEI eval> (CAAR '((EINS) ZWEI))) ==> EINS eval> (CDDDDDAAR ' ( 1 2 3 4 5 6 7 8 9 ) ) ==> ERROR . . . ¡CDDDDDAAR i s t nicht d e f i n i e r t ; und wäre auch unzweckmäßig Werden die Selektoren CAR und CDR auf die leere Liste angewendet, dann ist ihr Rückgabewert nicht definiert. Ihr Wert ist Undefiniert, so wie es der Wert bei der Division einer Zahl durch 0 ist. Abhängig von der jeweiligen LISP-Implementation wird jedoch NIL oder eine Fehlermeldung oder ein sonstiges Zeichen (z.B. ,,-L") zurückgegeben. In PC Scheme haben die Selektoren dann den Wert NIL. eval> eval> eval> eval> eval> eval >

(CAR (CDR (CAR (CDR (CAR (CDR

Konstruktor

( ) ) ==> () ( ) ) ==> () ' ( ) ) ==> () ' ( ) ) ==> () " ( ) ) ==> QUOTE " ( ) ) ==> ( ( ) ) CONS:

Während die beiden Selektoren CAR und CDR eine Liste in zwei Teile „zerlegen", „verbindet" der Konstruktor CONS (to construct) zwei symbolische Ausdrücke. Das CONS-Konstrukt hat eine Schnittstelle für zwei Argumente. Es hat als Wert eine neue Liste, wenn das zweite Argument eine Liste ist. Der Wert ist die neue Liste mit dem Wert des ersten Arguments als erstes Element dieser Liste. Kurz formuliert: CONS fügt einen symbolischen Ausdruck als Kopf an den Anfang einer Liste. (Häufige engl. LISP-Phrase: ,.1*11 cons something up"). Beispiele zum

CONS-Konstrukt:

eval> (CONS 'FREIE '(HANSESTADT BREMEN)) ==> (FREIE HANSESTADT BREMEN) eval> (CONS 1 NIL) ==> (1) eval> (CONS 10 '(20 30)) ==> (10 20 30)

Ist der Wert des zweiten Arguments keine Liste, dann hat das CONS-Konstrukt ein sogenanntes Punkt-Paar (engl.: dotted pair) als Wert, wobei der linke Teil der Wert des ersten Argumentes und dessen rechter Teil der Wert des zweiten Argumentes von CONS ist. Die Bezeichnung „dotted pair" leitet sich davon ab, daß zwischen die evaluierten Argumente ein Punkt eingefügt wird. eval> (CONS 'NOCH 'NICHT) ==> (NOCH . NICHT) Die externe Darstellung eines Punkt-Paares, die als Eingabe und Ausgabe verwendet wird, hat die Reihenfolge: öffnende Klammer, symbolischer Ausdruck,

2. Kombinieren von Konstrukten

41

Zwischenraum, Punkt, Zwischenraum, symbolischer Ausdruck und dann schließende Klammer. Die Zwischenräume sind erforderlich, um ein Punkt-Paar von einer Gleitkommazahl zu unterscheiden, bzw. um den Punkt nicht als Namensteil eines Symbols aufzufassen. eval> eval> eval>

(+ 1.2 3.4) ==> 4.6 (CONS 1 . 2 3 . 4 ) = = > ( 1 . 2 . 3 . 4 ) (CONS ' B R E M E R - L A G E R H A U S . A G ( ) ) ==> (BREMER-LAGERHAUS.AG)

¡Gleitkommazahlen ;Punkt-Paar! ¡Punkt als Namensteil

Das Punkt-Paar verweist auf die interne Abbildung eines symbolischen Ausdrukkes im Speicher des LISP-Systems. Bei der CONS-Anwendung wird (in den klassischen LISP-Implementationen) eine sogenannte LISP-Zelle (auch CONSZelle genannt) im Speicher mit den Adressen der Werte der beiden Argumenten generiert. Eine Liste ist damit ein Spezialfall einer Punkt-Paare-Struktur (vgl. Bild 2.1-2). Sie ist entweder: o ein NIL-Zeiger oder o ein Punkt-Paar, dessen CAR-Teil ein symbolischer Ausdruck ist und dessen CDR-Teil eine Liste ist. Für unser Thematik „Konstruktion" ist die Zusicherung entscheidend: Was ein Konstruktor zusammenfügt, ist ohne Verlust durch korrespondierende Selektoren „zerlegbar", d.h. wiedergewinnbar. eval> (CAR (CONS 'BAUTEIL-1 'BAUTEIL-2)) —> BAUTEIL-1 eval> (CDR (CONS 'BAUTEIL-1 'BAUTEIL-2)) —> BAUTEIL-2 Die Definition von eigenen Konstruktoren und passenden Selektoren vertiefen wir im Abschnitt 4. Prädikate: EQ?-Konstrukt und EQUAL?-Konstrukt Wenn zwei symbolische Ausdrücke miteinander verglichen werden, sind drei Fälle zu unterscheiden: o Identität: Beiden Angaben bezeichnen dasselbe „Objekt". o Gleichheit: Beide Angaben haben den gleichen Wert. Es sind aber prinzipiell zwei Abbildungen der „Objekte" (Werte) feststellbar, d.h. die Werte sind an zwei verschiedenen Stellen gespeichert (Merkformel: „Zwillinge"). o Ungleichheit. Die Gleichheit stellen wir häufig im Hinblick auf eine Austauschbarkeit fest. Zweck ist es „Gleiches durch Gleiches" zu ersetzen. Beispielsweise wollen wir den symbolischen Ausdruck (+ 1 2) durch die Zahl 3 ersetzen. Sprechen wir von Gleichheit im Sinne von NichtUnterscheidbarkeit, dann ha-

42

I. Konstrukte (Bausteine zum Programmieren)

Punkt-Paar-Struktur:

(A

. ((B

r?

0-

oV

A

,

V

o

. NIL)

. (C

.

NIL)))

NIL

V

C

NIL

v B

Legende: CAR- CDRPart Part

::=

o

::=

>

CONS-Zelle Zeiger

(Adresse)

::= NIL-Zeiger, Bild

2.1-2.

CONS-Zel1en-

entspricht:

Da rstel1ung

der

o—>

Liste

NIL (A (B)

C)

ben alle Tests für beide „Objekte" stets identische Resultate. Eine Identitätsfeststellung bei zwei Argumenten bedingt das Prüfen zweier Namen, die dasselbe „Objekt" benennen. Das Vergleichsprädikat im Sinne einer solchen Identitätsfeststellung heißt EQ (abgekürzt von £Qual; phonetisch [i:kju:]) oder ähnlich z.B. E Q ? in Scheme. Bei Symbolen nimmt das EQ?-Konstrukt Identität an, wenn beide Argumente sich auf dasselbe interne „Objekt" im LISP-System beziehen (Prüfung der Gleichheit von Adressen, vgl. Abschnitt 5). Bei Zahlatomen ist man an ihrer numerischen Wertgleichheit interessiert und nicht daran, ob das LISP-System die beiden Argumente (Zahlen) als dasselbe „Objekt" interpretiert oder nicht. Gelöst wird dieses Problem entweder durch ein eigenständiges Vergleichskonstrukt für Zahlatome (z.B. = bei Scheme) oder durch entsprechende „Erweiterung" von EQ (vgl. z.B. TLC-LISP). In modernen LISP-Systemen ist dieses Vergleichen häufig als eine sogenannte generische Funktion realisiert. Eine solche Funktion stellt selbst fest von welchem Typ die übergebenen Werte sind und aktiviert dann das zugeordnete Vergleichskonstrukt. Sind die symbolischen Ausdrücke im Sinne der Ersetzbarkeit „Gleiches durch Gleiches" zu prüfen, dann ist das EQUAL?-Konstrukt anzuwenden. Hat schon das EQ?-Konstrukt für zwei Argumente den Wert „wahr", dann hat auch das

2. Kombinieren von Konstrukten

43

EQUAL?-Konstrukt für diese Argumente den Wert „wahr". (Achtung: Die Umkehrung gilt nicht!). Mit dem EQ?-Konstrukt sind wir in der Lage, Prädikate für die Erkennung von Kunden, Lieferanten, Kassenkonten etc. zu definieren. Da diese Prädikate zum Unterscheiden zwischen verschiedenen „Objekten" dienen, bezeichnet man sie auch als Diskriminatoren. eval>

;; S y m b o l K u n d e ist (EQ? ( C A R M E Y E R - A G )

eval>

;; D i e S y m b o l e s i n d i d e n t i s c h , d i e I n f o r m a t i o n , d a ß s i e ;; E l e m e n t e e i n e r L i s t e s i n d , i s t z w e i m a l a b g e b i l d e t , ;; d a h e r k e i n e I d e n t i t ä t . (EQ? M E Y E R - A G ' ( K U N D E " D a t e n z u r M e y e r A G " ) = = > ()

eval>

;; G l e i c h h e i t v o n L i s t e n s t r u k t u r e n u n d L i s t e n e l e m e n t e n (EQUAL? M E Y E R - A G '(KUNDE "Daten zur M e y e r AG") ==> #T

eval>

;; E i n S y m b o l ist n i c h t g l e i c h e i n e r ( E Q U A L ? ' K U N D E " K U N D E " ) = = > ()

Programmfragment

in S c h e m e n u r e i n m a l 'KUNDE) ==> #T

gespeichert

Zeichenkette

2 . 1 - 3 . E r k e n n e n des Operandentyps KUNDE (von P r o g r a m m f r a g m e n t 2 . 1 - 1 )

In Scheme haben Prädikate üblicherweise als Kennzeichen am Ende ihres Namens ein Fragezeichen, daher nennen wir die Prädikate KUNDE?, LIEFERANT?, KASSENKONTO? etc. Entsprechend der Matrix-Spalte (Bild 2.1-1.) kann NEU ANLEGEN mit der Struktur von Programmfragment 2.1-4 definiert werden. eval>

(DEFINE

KUNDE?

eval>

(DEFINE

LIEFERANT?

eval>

(DEFINE

K A S S E N K O N T O ? ( L A M B D A (X) (EQ? ( C A R X) ' K A S S E N K O N T O ) ) )

eval>

eval> eval> eval>

(DEFINE (COND

(LAMBDA

(X)

(LAMBDA

(EQ? (X)

( C A R X) (EQ?

'KUNDE)))

( C A R X)

==> KUNDE? 'LIEFERANT))) ==> LIEFERANT? ==>

KASSENKONTO?

NEUANLEGEN (LAMBDA (OPERAND) ((KUNDE? OPERAND) 'XI) ((LIEFERANT? OPERAND) 'X2) ( ( K A S S E N K O N T O ? O P E R A N D ) 'X3) (T ( W R I T E L N " O p e r a t i o n e n f ü r : " O P E R A N D "noch nicht d e f i n i e r t ! " ) ) ) ) ) ==> NEUANLEGEN ( N E U A N L E G E N S C H U L Z E - G M B H ) = = > X2 ( N E U A N L E G E N V E R W A H R U N G - 1 ) = = > X3 ( N E U A N L E G E N '(A B C ) ) = = >

44

I. Konstrukte (Bausteine zum Programmieren) Operation

für:

(A B C) n o c h n i c h t

Programmfragment2.1-4.

definiert!

Operation NEUANLEGEN als Konstrukt-Basis ( v g l . B i l d und Programmmfragment

2.1-1).

Im Programmfragment 2.1-4 sind die Operationen xl, x2 und x3 nicht ausformuliert, sondern nur als Konstanten angegeben. Zur Ausgabe einer Fehlermeldung nutzen wir das WRITELN-Konstrukt (write /ine newline). Es evaluiert seine Argumente, faßt die Werte zu einer Zeile zusammen, gibt diese (am aktuellen Ausgabegerät) aus und bewirkt einen Zeilenvorschub. Bei einem komplexen Problem führt die Zusammenfassung von Operationen entsprechend der Matrix-Spalten (Bild 2.1-1) zu einer Hierarchie von Konstrukten. Diese Hierarchie ist geprägt durch die Operationen (Aktionen) des jeweiligen Aufgabengebietes. Beispielsweise käme man zur Operation NEUANLEGEN über ein hierarchisch höheres Konstrukt. Wir nehmen an, daß diese Auswahloperation den Namen VERWALTEN habe. Das Konstrukt VERWALTEN ermittelt dann anhand seines Operanden, daß NEUANLEGEN auszuführen sei. So entsteht eine operations-geprägte Konstruktion. Sie ist gegliedert (modularisiert) in Operationen. Im Mittelpunkt der symtema(naly)tischen Überlegungen stehen die Aktionen (Prozesse, Vorgänge). Wären nach Fertigstellung der Konstruktion weitere Operanden, z.B. Sachkonten, einzubeziehen, dann sind in den Operationen NEUANLEGEN, LÖSCHEN etc. jeweils die entsprechenden Funktionen für die Sachkonten einzubauen. Dies ist im Falle vieler bestehender Operationen ein sehr aufwendiges und fehleranfälliges Unterfangen, weil fertiggestellte Konstrukte z.B. NEUANLEGEN modifiziert werden müssen. Damit sind neue Fehler für schon programmierte Operandentypen nicht ausschließbar. Sind solche Änderungen wahrscheinlich, dann empfiehlt es sich, die Konstrukte aus der Matrixzeilen-Sicht zu entwickeln, statt sie aus der MatrixspaltenSicht zu bilden. Exemplarisch ist das Konstrukt KUNDE als Programmfragment 2.1-5 definiert. Es skizziert die Operanden-Sicht in stark vereinfachter Art. eval>

eval>

( D E F I N E KUNDE (LAMBDA ( O P E R A T I O N ) (COND ((NEUANLEGEN? OPERATION) ' X l ) ((LÖSCHEN? OPERATION) ' Y l ) ((ANMAHNEN? OPERATION) 'ZD (T (WRITELN " F ü r Kunden i s t : " OPERATION "noch n i c h t d e f i n i e r t ! " ) ) ) ) ) (DEFINE

NEUANLEGEN?

(LAMBDA

(XHEQ?

(CAR X)

==>

KUNDE

'NEUANLEGEN))) = = > NEUANLEGEN?

2. Kombinieren von Konstrukten eval>

( D E F I N E MEYER-AG (KUNDE ' ( N E U A N L E G E N

Programmfragment

2.1-5.

45

"Daten

zur

Operand KUNDE ( v g l . B i l d und

Meyer

AG")))

==>

MEYER-AG

als Konstrukt-Basis Programmfragment 2 . 1 - 1 )

Auch hier führen die Abstraktionsschritte zu einer Hierarchie von Konstrukten. Beim Ergänzen um den Operanden SACHKONTO werden die bestehenden Konstrukte wie KUNDE, LIEFERANT etc. nicht berührt. Das Zusammenfassen aller Operationen bietet daher ein Modularisierungs-Konzept, das ein Hinzufügen von neuen Konstrukten im Sinne von Operanden erleichtert. Ist allerdings eine neue Operation z.B. UMBENENNEN später zu ergänzen, dann sind die Konstrukte KUNDE, LIEFERANT, KASSENKONTO etc. zu modifizieren. So entsteht eine operanden-geprägte Konstruktion. Sie hat einen objekt-orientierten Aufbau. Im Mittelpunkt der symtema(naly)tischen Überlegungen stehen die Operanden im Sinne von Objekten (Näheres dazu vgl. Abschnitt 10). Die Frage, ob die Lösung „Operation als Konstrukt-Basis" (Programmfragment 2.1-4) oder die Lösung „Operand als Konstrukt-Basis" (Programmfragment 2.1-5) zweckmäßiger ist, bedingt eine Prognose über die Fortentwicklung des Softwareproduktes. Das skizzierte Beispiel Kontenverwaltung verdeutlicht, daß unsere Konstruktions-Entscheidungen im Kontext des gesamten SoftwareLebenszyklus zu treffen sind. Die Erfüllung einer funktionalen Anforderung z.B. das korrekte Abbilden einer Buchung ist notwendig, aber nicht hinreichend. Hinzukommen muß die Abdeckung von erwartbaren Anforderungen aus den späteren Phasen des Lebenszyklus. In unserem Kontext „Konstruktion" ist stets zu unterscheiden, ob es um die Realisierung relativ kleiner Konstrukte (Menge einzelner „Anweisungen/Befehle") oder um große Konstrukte (ganze Programmeinheiten) geht. Im ersten Fall ist die Reihenfolge der Aktivierung einzelner „Anweisungen/Befehle" zu diskutieren. Im zweiten Fall geht es um die Koordination von ganzen Programmteilen (Moduln), also um abgeschlossene Folgen von „Anweisungen/Befehlen". Zunächst widmen wir uns der Realisierung kleiner Konstrukte, die häufig als „Programmierung im Kleinen" (engl.: programming-in-the-small) bezeichnet wird (vgl. z.B. DeRemer/Kron, 1976). Wir fangen also „klein" an und skizzieren mit wachsendem LISP-Fundus Verknüpfungs-Techniken für das „Programmieren im Großen" (engl.: programming-in-the-large).

2.2 Kontrollstrukturen Legen wir fest, wann, unter welchen Bedingungen und wie oft einzelne Konstrukte auszuwerten sind, dann definieren wir die Kontrollstruktur (engl.: control structure). Sie beschreibt, ob und in welcher Reihenfolge Programm(teil)e ausge-

46

I. Konstrukte (Bausteine zum Programmieren)

führt werden. Man bezeichnet sie korrekterweise auch als Steuerungsstruktur oder auch als Ablaufsteuerung. In der Terminologie imperativgeprägter Programmiersprachen (z.B. COBOL oder Pascal, Näheres vgl. Abschnitt 4) spricht man vom Steuerungsfluß oder Kontrollfluß (engl.: flow of control). Kombinierbar sind Konstrukte durch eine Hintereinanderausführung. Diese Verkettung nennt man im Zusammenhang mit Funktionen Komposition. In der Mathematik wird die Komposition zweier Funktionen f und g notiert als „fog" und gelesen als „f verkettet mit g" oder „g eingesetzt in f ' oder „f nach g". Diese Komposition ist in LISP als (F (G X)) zu formulieren mit F und G als Funktionen, die für ein Argument definiert sind (engl.: unary functions). Der Wert dieser Komposition ist der Wert von (F Y), wobei Y der Wert von (G X) ist. Die Komposition (F (G X)) ist nur dann sinnvoll, d.h. nicht Undefiniert, wenn X die Schnittstelle von G und Y die Schnittstelle von F erfüllen. Anders formuliert: Die Komposition bedingt, daß X in der Definionsmenge von G und die Wertemenge von G in der Definitionsmenge von F enthalten sind. Beispiele für die Komposition: eval> (DEFINE ADD2 (LAMBDA (ZAHL) (+ 2 ZAHL)))==>ADD2 eval> (DEFINE MUL3 (LAMBDA (ZAHL) (* 3 ZAHL))) ==> MUL3 eval> (ADD2 (MUL3 1)) ==> 5 eval> (MUL3 (ADD2 1)) ==> 9 eval> (DEFINE A2M3 (LAMBDA (ZAHL) (ADD2 (MUL3 ZAHL)))) ==> A2M3 eval> (A2M3 1) ==> 5 eval> ;; Komposition zweier anonymer Funktionen ((LAMBDA (ZAHL) (+ 2 ZAHL)) ((LAMBDA (ZAHL) (* 3 ZAHL)) 1)) ==> 5 Elementare Kontrollstrukturen sind: o die Folge (Sequenz) o die Auswahl (Selektion) und o die Wiederholung (Iteration). Bevor wir ihre Realisierung diskutieren, sind nochmals die bisher erläuterten Konstruktionsmittel in Backus-Naur-Form (vgl. folgenden Exkurs) als Tabelle 2.2-1 zusammengestellt. Sie zeigt atomare Komponenten (wie Symbol oder Zahl), Grundprozeduren (wie CONS, CAR oder CDR) und zusätzlich elementare Kontrollstrukturen. Z.B. ermöglicht das LAMBDA-Konstrukt das Verknüpfen von Konstrukten als Sequenz. ^Exkurs: Backus-Naur-Form. Die von John W. Backus und Peter Naur definierte Notation ist geeignet, die Syntax einer Programmiersprache zu spezifizieren. Eine BNF-Metasprach-Variable, auch syntaktische Einheit genannt, ist eine Variable, deren Wert auf Zeichen zurückführbar ist, die in der gegebenen Sprache (hier LISP) zulässig sind. Aus Klarheitsgründen sind die BNF-Metasprach-Variablen in spitze Klammern „" eingeschlossen.

2. Kombinieren von Konstrukten

47

Damit unterscheiden sich diese „nonterminal symbols" (kurz „nonterminals") von den Symbolen in LISP, die auch als „terminal symbols" (kurz „terminals") bezeichnet werden. Die BNF-Metasprach-Gleichheit, dargestellt durch „::=", kann aufgefaßt werden als „ist konstruierbar aus". Dabei wird eine Wahlmöglichkeit im Sinne einer Oder-Verknüpfung durch den senkrechten Strich „ | " dokumentiert. Eckige Klammern [ ... ] kennzeichnen die Möglichkeit, das Eingeklammerte weglassen zu können. Geschweifte Klammern { ... } dokumentieren, daß das Eingeklammerte beliebig oft wiederholt oder aber auch ganz weggelassen werden kann.7

=



=

({()

=

|

=



|

...

;und w e i t e r e T y p e n ; z.B. Zeichen oder ; Zeichenkette |

|

|

| |

=



=

( < z i f f e r > | I ... ;und wei t e r e T y p e n ; z.B. 12.34

=

| +

| -

A | B | C | D | E | F | G | H | I | J | K IM IN 10 | P IQ I R IS IT I U IV I W I X I Y I Z I a I b ' | .!. |'z L



0

I


I ?

| 1

| 2

| 3

!

| § ;und w e i t e r e T y p e n z B & | 4'| 5 j 6 | 7 | 8 | 9

Legende: vgl. obigen Exkurs B a c k u s - N a u r - F o r m ; Z e i c h e n h i n t e r d e m S e m i k o l o n in e i n e r Tab.2.2-1.

Zeile

Erörterte Konstruktions-Mittel (Teil - d a r g e s t e l l t in B a c k u s - N a u r - F o r m -

sind

Kommentar

1)

Die Tabelle 2.2-1 definiert auszugsweise ein LISP-System; es umfaßt keinesfalls alle Kontrakte eines LISP-Dialektes wie Scheme. Wir können jedoch mit ihr wesentliche Charakteristika diskutieren. So kann z.B. kein Konto mit dem

48

I. Konstrukte (Bausteine zum Programmieren)

::=

(QUOTE

)

)

¡Kreieren

einer

Konstanten

¡Einfügen

in e i n e

|

(CONS

|

( C A R )

¡Selektion

des

ersten

|

( C D R )

¡Selektion

der

Restliste

|

(LIST

¡Bilden

einer

Liste

|

(DEFINE

¡Binden

eines

Symbols

|

(SET!

|

(COND {(

|

(LAMBDA (KsymbolX)

|

(APPLY

|

(EVAL

|

(

I))



< s e x p r >)

)

¡Modifizieren

¡Defintion ( )) )

einer

¡Applikation

[])

( (}

einer

Elementes

Bindung

;Fallunterschei dung

KsexprX)})

Liste

¡Auswerten

))

Funktion

einer

Funktion

in e i n e r

Umgebung

¡Komposition

Legende: siehe Exkurs Backus-Naur-Form ; ::= K o m m e n t a r Tab.2.2-1.

Erörterte Konstruktions-Mittel (Teil - d a r g e s t e l l t in B a c k u s - N a u r - F o r m -

2)

Namen 1234 entsprechend unserem Konto MEYER-AG (vgl. Abschnitt 2.1) gebildet werden. eval>

(DEFINE 1234 ( K U N D E ' ( N E U A N L E G E N " D a t e n zu = = > E R R O R ... ¡ F e h l e r h a f t e s S y m b o l f ü r

Konto 1234"))) DEFINE

Die Zahlatome sind von Symbolen (auch Literalatome genannt) zu unterscheiden. Nur mit Symbolen ist das Kreieren einer Wert-Assoziation in einer Umgebung möglich. Zahlatome haben sich selbst als Wert; stellen aber keine Variable dar (vgl. Abschnitt 1.4.2). Eine Funktion kann daher nicht mit einem Zahlatom benannt werden. Auch die Schnittstelle der eigenen Konstrukte, gebildet durch die LAMBDA-Variablen, erfordert Symbole. Zahlatome sind dort nicht verwendbar. Wir stellen fest: Für Zahlatome gibt es Restriktionen im Vergleich zu Literalatomen (Symbolen). Zahlatomen fehlt die sogenannte „first-class"-Eigenschaft (vgl. folgenden

2. Kombinieren von Konstrukten

49

Exkurs "First-class"-Objekt). So kann die arithmetische Funktion, die den Wert ihres Argumentes um 1 erhöht, nicht +1 heißen, weil +1 als Zahlatom interpretiert wird. Die Umkehrung dieser Zeichenfolge ist in Scheme ein Symbol. Die Funktion ist daher mit 1+ benennbar. e v a l > (1+ 2) ==> 3 e v a l > (+1 2) ==> ERROR . . . e v a l > (+ 1 2) ==> 3

;1+ i s t e i n Symbol ;+l i s t k e i n Symbol ;+ i s t ein Symbol

/Exkurs: "First-class-Objekt" (auch „first-class-citizen" genannt). Im LISPKontext haben „Objekte" (Werte) dann „first-class"-Eigenschaften, wenn folgende Bedingungen erfüllt sind (vgl. z.B. dinger, 1988, p. 224): o Objekte haben eine Identität. Diese ist unabhängig von jedem Namen, unter dem Objekte gekannt werden können. So kann ein Objekt auch mehrere Namen haben. (Diese Eigenschaft fehlt Zahlatomen.) o Objekte können anonym bleiben. Z.B. evaluiert das LAMBDA-Konstrukt zu einer anonymen Funktion. o Objekte können ohne Verlust ihrer Identität überall gespeichert werden; als Werte von Variablen oder in komplexen Datenstrukturen. o Objekte können sowohl Argument als auch Rückgabewert einer Funktion sein. o Objekte „sterben" nicht. Ein Objekt wird unbenutzbar („unzugreifbar") und damit zwecklos. Der von ihm belegte Speicherplatz kann vom LISP-System als wieder „verfügbar" erklärt werden, vorausgesetzt, die Unbenutzbarkeit wird erkannt. Das LISP stellt fest,ob ein Objekt als „Müll" (engl.: garbage) zu betrachten ist. Algorithmen, die dies erkennen, bezeichnet man als „garbagecollection"-Algorithmen. R. Popplestone spricht in diesem Kontext von „items", die fundamentale Rechte besitzen (Näheres vgl. Allen, 1978).y

2.2.1 Sequenz Wir nehmen an, ein symbolische Ausdruck GESAMT setzt sich aus den Teilen TEIL-1, TEIL-2 und TEIL-3 zusammen, wobei diese Teile ebenfalls symbolische Ausdrücke sind. Vorstellbar ist, daß GESAMT ein Programm ist, das aus den Moduln TEIL-1, TEIL-2 und TEIL-3 besteht. Aufgrund der Abbildungsgleichheit von Programm und Daten ist auch annehmbar, daß GESAMT eine Datenstruktur abbildet z.B. eine Anschrift, die aus der Folge Straße (TEIL-1), Postleitzahl (TEIL-2) und Wohnort (TEIL-3) besteht. Eine Sequenz mit dem Namen GESAMT und den Teilen TEIL-1, TEIL-2, TEIL-3 stellt Bild 2.2.1-1 graphisch dar.

50

I. Konstrukte (Bausteine zum Programmieren)

Diese Darstellungsform wurde von M. A. Jackson insbesondere für den Programmentwurf vorgeschlagen (vgl. Jackson, 1979). Zur Erläuterung der Selektion (Abschnitt 2.2.2) und der Iteration (Abschnitt 2.2.3) wird ebenfalls die Jackson-Notation genutzt.

Die abgebildete Sequenz sei die Zerlegung (Modularisierung) eines Programms. Das „Laufenlassen" des Programms GESAMT bedeutet das Abarbeiten der Moduln TEIL-1, TEIL-2 und TEIL-3. Wir formulieren daher eine Funktion GESAMT aus den Teilen TEIL-1, TEIL-2 und TEIL-3, wie folgt: eval > eval > eval> eval> eval>

(DEFINE (DEFINE (DEFINE (DEFINE (GESAMT)

G E S A M T ( L A M B D A () T E I L - 1 T E I L - 2 T E I L - 3 ) ) = = > G E S A M T T E I L - 1 " < s e x p r > für Modul T E I L - 1 " ) = = > T E I L - 1 T E I L - 2 " < s e x p r > für M o d u l T E I L - 2 " ) = = > T E I L - 2 T E I L - 3 " < s e x p r > für M o d u l T E I L - 3 " ) = = > T E I L - 3 = = > " < s e x p r > für Modul T E I L - 3 " ; R ü c k g a b e w e r t ist d e r W e r t v o n T E I L - 3

Programmfragment

2.2.1-1.

Sequenz

von

Symbolen

Im Programmfragment 2.2.1-1 werden die Werte von TEIL-1, TEIL-2 und TEIL-3 zum Zeitpunkt ihrer Definition ermittelt. Stände an der Stelle von " für Modul TEIL-3" z.B. der symbolischen Ausdruck (WRITELN "Der Sinus ist: " (SIN (+ 0.2 0.3))) dann wird dieses Sinus-Berechnungsprogramm beim Evaluieren des DEFINE-Konstruktes ausgeführt, wobei TEIL-3 an den Rückgabewert des WRITELN-Konstruktes gebunden wird, hier NIL. Die Ausgabe der Druckzeile ist auf dem Bildschirm erfolgt. Die Anwendung von GESAMT führt nur zum Ermitteln der Bindung von TEIL-3. Dieser wahrscheinlich ungewollten Effekt ist vermeidbar, indem wir an den Modul einen „gequoteten" symbolischen Ausdruck (eine Konstante) binden; z.B. für TEIL-3:

51

2. Kombinieren von Konstrukten eval>

(DEFINE TEIL-3 '(WRITELN "Der

Sinus

ist:

" (SIN

(+ 0 . 2 0 . 3 ) ) ) )

==>

TEIL-3

Verfahren wir für TEIL-1 und TEIL-2 entsprechend, dann ist die Sequenz GESAMT wie folgt zu definieren: eval>

(DEFINE GESAMT ( L A M B D A () (EVAL TEIL-1)

(EVAL TEIL-2)

(EVAL TEIL-3)))

==>

GESAMT

Statt das QUOTE-Konstrukt einzusetzen, können wir die Moduln TEIL-1, TEIL-2 und TEIL-3 auch als LAMBDA-Konstrukte definieren, so daß erst die Applikation von GESAMT die Auswertung der Moduln bewirken soll. ;;; G E S A M T u n d d i e M o d u l n T E I L - 1 , T E I L - 2 ;;; s i n d a l s F u n k t i o n e n d e f i n i e r t .

und

eval>

(DEFINE

GESAMT

(LAMBDA

()

(TEIL-1)

(TEIL-2)

eval>

(DEFINE TEIL-1

(LAMBDA

()

"

eval>

(DEFINE

TEIL-2

(LAMBDA

()

"

eval>

(DEFINE TEIL-3

(LAMBDA

()

"

Programmfragment

2.2.1-2.

Sequenz

von

TEIL-3

(TEIL-3))) ==> GESAMT f ü r Modul T E I L - 1 " ) ) ==> TEIL-1 für Modul T E I L - 2 " ) ) ==> TEIL-2 für Modul T E I L - 3 " ) ) ==> TEIL-3 Funktionsanwendungen

Im Programmfragment 2.2.1-2 werden die Moduln TEIL-1, TEIL-2 und TEIL-3 nur abgearbeitet. Ihnen werden vom (Haupt-)Modul GESAMT keine Werte übergeben. Um Werte über eine Schnittstelle von GESAMT für die sequentielle Abarbeitung der Moduln TEIL-1, TEIL-2 und TEIL-3 bereitzustellen, definieren wir GESAMT mit LAMBDA-Variablen, z.B. mit den Parametern P _ X und P_ Y. eval> eval>

(DEFINE GESAMT ( L A M B D A (P_X P _ Y ) ( T E I L - 1 ) ( T E I L - 2 ) ( T E I L - 3 ) ) ) = = > G E S A M T (GESAMT ARGUMENT-1 ARGUMENT-2) = = > " für Modul T E I L - 3 "

Wir können die Abarbeitungsfolge TEIL-1, TEIL-2 und TEIL-3 auch so interpretieren, daß ihr Ergebnis den Namen GESAMT hat. Der Wert von GESAMT repräsentiert die vollzogene Sequenz. eval>

(DEFINE GESAMT ( ( L A M B D A (P_X

P_Y)

(TEIL-1) (TEIL-2) (TEIL-3)) ARGUMENT-1 ARGUMENT-2)) ==>

GESAMT

Jetzt wird die anonyme Funktion auf den Wert der beiden Argumente ARGU-

52

I. Konstrukte (Bausteine zum Programmieren)

MENT-1 und ARGUMENT-2 angewendet, wobei alle Nebeneffekte z.B. WRITELN-Konstrukte sofort zum Tragen kommen. Der Wert der Sequenz wird durch das DEFINE-Konstrukt mit dem Symbol GESAMT assoziiert. Auch hier können wir durch QUOTE- und EVAL-Konstrukte den Zeitpunkt der Ausführung festlegen (vgl. Abschnitt 2.3). e v a l > (DEFINE GESAMT •((LAMBDA (P_X P_Y)

(TEIL-1) (TEIL-2) (TEIL-3)) ARGUMENT-1 ARGUMENT-2)) ==> GESAMT e v a l > (EVAL GESAMT) ==> " < s e x p r > f ü r Modul T E I L - 3 "

Bei der Anwendung einer anonymen Funktion, insbesondere wenn diese durch ein umfangreiches LAMBDA-Konstrukt mit vielen LAMBDA-Variablen dargestellt wird, ist die Zuordnung der Argumente zu den LAMBDA-Variablen unübersichtlich. In LISP gibt es deshalb eine syntaktische Verbesserung in Form des LET-Konstruktes. e v a l > (LET ( ( < v a r i a b l e _ l >

)

( < v a r i a b l e _ n > )) ) ==> < v a l u e >

ist zurückführbar auf: e v a l > ((LAMBDA

(

) ) < a r g u m e n t _ l > . . . ) ==> < v a l u e >

/Hinweis: LET-Implementation. Das LET-Konstrukt ist häufig als Makro-Konstrukt realisiert. Makros werden in zwei Schritten abgearbeitet. Zunächst wird aus einem Makro ein neuer symbolischer Ausdruck konstruiert. Diesen Schritt nennt man „Expandieren". Im zweiten Schritt wird der entstandene Ausdruck evaluiert. In PC Scheme ist das Ergebnis des ersten Schrittes mittels EXPANDMACRO darstellbar (Näheres vgl. Abschnitt 8.3). Damit können wir zeigen in welchen Ausdruck das LET-Konstrukt expandiert, e v a l > (EXPAND-MACRO ' ( L E T ( ( A 1) (B 2 ) ) (F00 A B ) ) ) ==> ((LAMBDA (A B) (F00 A B ) ) 1 2)

]

Das L£T-Konstrukt entspricht der Applikation eines LAMBDA-Konstruktes. Wir können es als ein Konstrukt betrachten, das zunächst lokale Variablen definiert und mit diesen Bindungen seinen Funktionskörper auswertet. (LET ( ( < 1 o k a l e _ v a r i a b l e _ l >

)

;Binden der 1. ; Variablen

lokalen

2. Kombinieren von Konstrukten

53

( )) ) ==>

¡Bindung der n-ten ; lokalen Variablen

Den syntaktischen Vorteil des „LET-Schreibstils" gegenüber dem "LAMBDASchreibstil" verdeutlicht das folgende Beispiel: eval> ((LAMBDA (W X Y Z) (LIST (CAR (CDR W)) (CAR (CDR X)) (CAR (CDR Y)) (CAR (CDR Z)))) '(MEANS M) '(OF 0) '(REQUIREMENTS R) '(ENGINEERING E)) ==> ( M O R E ) eval> (LET ((W '(MEANS M)) (X '(OF 0)) (Y '(REQUIREMENTS R)) (Z '(ENGINEERING E))) (LIST (CAR (CDR W)) (CAR (CDR X)) (CAR (CDR Y)) (CAR (CDR Z)))) ==> ( M O R E ) Intuitiv vermutet man in der Bindung der lokalen Variablen eines LET-Konstruktes die Möglichkeit, eine Sequenz zu konstruieren. Da das LET-Konstrukt einer Applikation eines LAMBDA-Konstruktes entspricht, ist der Vorgang der Auswertung der Argumente vom Vorgang der Bindung der LAMBDA-Variablen an die Werte zu unterscheiden. Bezogen auf das LET-Konstrukt formuliert: Es werden zunächst alle Werte der lokalen Variablen in einer prinzipiell beliebigen Reihenfolge ermittelt, dann findet die Bindung der jeweiligen Variablen statt. Da die Reihenfolge dieser Wertermittlung nicht allgemeingültig festliegt (sondern sogar als Ansatz für eine Parallelverarbeitung in LISP dient), sollten wir beim LET-Konstrukt keine Sequenz unterstellen, wie im folgenden Beispiel: eval> (LET ((F00 (WRITELN "1. Phase")) (BAR (WRITELN "2. Phase"))) (LIST F00 BAR)) ==> 1. Phase 2. Phase (() ( ) ) Zu berücksichtigen ist, daß die Ermittlung aller Werte vor der Bindung an die LAMBDA-Variablen erfolgt. Beim LET-Konstrukt sind sie daher bei der Wertermittlung nicht zugreifbar. eval> (LET ((X 3) (Y (* 2 X))) (+ X Y)) ==> ERROR . . .

;Das Symbol X i s t nicht in der ; aktuellen Umgebung d e f i n i e r t .

Hilfsweise können wir die Sequenz durch Schachtelung von LET-Konstrukten

54

I. Konstrukte (Bausteine zum Programmieren)

abbilden, die nur eine lokale Variable ausweisen (vgl. Curryfizieren einer Funktion, Abschnitt 8.1.2): eval> (LET ((X 3)) (LET C C Y (* 2 X))) (+ X Y))) ==> 9 In LISP-Systemen gibt es eine übersichtlichere Lösung durch das L£T*-Konstrukt. Dieses bindet die lokalen Variablen der Reihe nach. Zunächst wird der Wert für die erste Variable ermittelt und dann wird diese gebunden. Danach wird jede neue Bindung in der Umgebung ausgewertet, die durch die vorhergehenden lokalen Variablen erweitert wurde. eval> (LET* ((X 3) (Y (* 2 X))) (+ X Y)) ==> 9 /Hinweis: LET*-Implementation. Das LET*-Konstrukt ist häufig wie das LETKonstrukt als Makro-Konstrukt implementiert (Näheres vgl. Abschnitt 8.3). Es expandiert unser obiges Beispiel, wie folgt: eval > (EXPAND-MACRO '(LET* ((X 3) (Y (* 2 X))) (+ X Y))) ==> ((LAMBDA (X) (LET* ((Y (* 2 X))) (+ X Y))) 3) ] Das LET*-Konstrukt ermöglicht die Abbildung der Sequenz TEIL-1, TEIL-2 und TEIL-3 (vgl. Bild 2.2.1-1.), wie folgt: eval> (DEFINE GESAMT (LET* ((TEIL-1 (TEIL-2 (TEIL-3 TEIL-3 ))

" f ü r TEIL-1") " f ü r TEIL-2") " f ü r TEIL-3")) ==> GESAMT

Entsprechend unserer Diskussion über die funktionale Alternative ist die Sequenz auch, wie folgt, definierbar: eval> (DEFINE GESAMT (LET* ((TEIL-1 (TEIL-2 (TEIL-3 (TEIL-1)

(LAMBDA (LAMBDA (LAMBDA (TEIL-2)

() " () " () " (TEIL-3)))

für für für ==>

TEIL-1")) TEIL-2")) TEIL-3"))) GESAMT

Solange bei der Ermittlung des Wertes für eine lokale Variable kein Bezug auf die Variable selbst genommen wird, ist die Reihenfolge „erst Wertermittlung (Auswertung des Argumentes), dann Bindung" ausreichend. Soll jedoch ein rekursiver Bezug möglich sein, dann muß die Variable bei ihrer Wertermittlung in der Umgebung der Auswertung schon existieren. Notwendig sind dann folgende Schritte:

2. Kombinieren von Konstrukten

55

1. Erweiterung der Auswertungsumgebung um die Variable (dabei kann die Variable an einen beliebigen Wert gebunden sein), 2. Ermittlung des Wertes für diese Variable und 3. Bindung der Variablen an den Wert in derselben Umgebung. Diese Schrittfolge simuliert das folgende LET-Konstrukt: eval> (LET ((F00 ^UNDEFINIERT*)) (SET! F00 (LAMBDA ( . . . ) . . . F00 . . . ) ) (F00 . . . ) . . . ) ==> . . .

¡Schritt 1 ¡ S c h r i t t e 2 und 3

In vielen LISP-Dialekten, so auch in Scheme, heißt das äquivalente Konstrukt LETREC, wobei der Postfix REC die Abkürzung für "rekursiv" ist. Wir behandeln das LETREC-Konstrukt bei der Erörterung der Rekursion (vgl. Abschnitt 3.3). Mit dem Fixpunktoperator Y skizzieren wir eine Implementationsalternative (vgl. Abschnitt 8.1.2). Das LETREC-Konstrukt deckt die Sequenz ebenso ab wie das L E T - K o n strukt. Zu beachten ist dabei die Reihenfolge für die Bindung einer Variablen. Im ersten Schritt wird die Variable in der Umgebung vermerkt und überdeckt damit eine globale Defintion, obwohl ihr „Startwert" im Prinzip nicht zugreifbar sein sollte. In PC Scheme ist dieser „Startwert" jedoch gleich NIL. Folgende Formulierungen bauen auf diesem „Startwert" auf. Wir vermeiden derartige Konstruktionen, zumal sie darüber hinaus leicht zu Fehler führen. eval> (DEFINE F00 'BAR) ==> F00 eval> (LETREC ((F00 (LIST F00 F00))) F00) ==> ( ( ) ( ) ) eval> (LETREC ((F00 (* 2 F00))) . . . ) ==> ERROR . . . ;Nichtnumerischer Operand f ü r ; a r i t h m e t i s c h e Funktion Die bisherige Diskussion der Sequenz fußt auf dem LAMBDA-Konstrukt. Gezeigt wurde, daß die Sequenz als Applikation der anonymen Funktion formulierbar ist: eval> ((LAMBDA (P_X P_Y) TEIL-1 TEIL-2 TEIL-3) ARGUMENT-1 ARGUMENT-2) ==> . . . Ein LISP-System verfügt für die Sequenz über ein „spezielles" Konstrukt (im Sinne von EVAL-Regel 3, vgl. Abschnitt 1.2). In Scheme ist es das BEGINKonstrukt. Ist das Symbol BEGIN das erste Listenelement, dann werden die Argumente der Reihe nach ausgewertet. Der Wert des letzten Argumentes ist der Wert des gesamten BEGIN-Konstruktes.

56 eval>

I. Konstrukte (Bausteine zum Programmieren) (BEGIN

... ) — >

entspricht damit: eval>

((LAMBDA ()

... )) — >

Wir können uns daher das LAMBDA-Konstrukt als ein Konstrukt vorstellen, das ein implizites BEGIN-Konstrukt enthält. Eine implizit formulierte Sequenz ist in einigen LISP-Konstrukten implementiert, z.B. im COND-Konstrukt (nicht jedoch im IF-Konstrukt, vgl. Programmfragment 2.2.1-3). Die Sequenz ist auch aufgrund der festen Reihenfolge, die EVAL-Regel 2 vorgibt, abbildbar (vgl. Abschnitt 1.2). Danach sind erst die Argumente auszuwerten, ehe die Funktion darauf angewendet wird. Wir definieren daher ein LAMBDA-Konstrukt mit einem formalen Parameter *DUMMY*, so daß die Angabe eines Argumentes möglich ist. Die Applikation dieser anonymen Funktion bildet dann die Sequenz dadurch ab, daß das Argument bildet und der Funktionskörper ist. eval>

((LAMBDA

(*DUMMY*)

)

)

==>

Sicherzustellen ist allerdings, daß die Bindung von *DUMMY* an keinen Einfluß auf die Berechnung von hat. Anders formuliert: Die LAMBDA-Variable *DUMMY* darf nicht als freie Variable im Ausdruck vorkommen. Durch die Komposition entsprechener LAMBDA-Konstrukte ist eine Sequenz mit n-Elementen abbildbar. Eine Sequenz mit drei Elementen ist dann, wie folgt, zu notieren: eval>

;;;; ;;;; eval> eval>

;;;; ;;;; eval>

((LAMBDA (*DUMMY*) ((LAMBDA (*DUMMY*) ) ) Sequenz - B e i s p i e l m i t e x p l i z i t notierendem BEGIN-Konstrukt

)

==>

zu

( D E F I N E *KUNDEN-ANZAHL* 0) = = > *KUNDEN-ANZAHL* ( D E F I N E * L I E F E R A N T E N - A N Z A H L * 0) = = > * L I E F E R A N T E N - A N Z A H L *

Fall

1:

E i n e S e l e k t i o n mit S e q u e n z e n , BEGIN-Konstrukt erfordern.

( D E F I N E ZÄHLUNG (LAMBDA (OPERAND) ( I F (KUNDE? OPERAND) ( B E G I N ( S E T ! *KUNDEN-ANZAHL*

die ein

explizites

(+ 1 *KUNDEN-ANZAHL*))

2. Kombinieren von Konstrukten

(IF

eval> ;;;;

(ZÄHLUNG Fall

2:

(WRITELN "Bearbeite Kunden!")) (LIEFERANT? OPERAND) (BEGIN (SET! ^ L I E F E R A N T E N - A N Z A H L * (+ 1 * L I E F E R A N T E N - A N Z A H L * ) ) (WRITELN "Bearbeite Lieferant!")) (WRITELN "Weder Kunde noch Lieferant"))))) ==> ZÄHLUNG

SCHULZE-GMBH)

Nutzung

eval>

(DEFINE (COND

eval>

(ZÄHLUNG

57

der

==>

"Bearbeite

impliziten

Sequenz

Lieferanten!" im

COND-Konstrukt

ZÄHLUNG (LAMBDA (OPERAND) ((KUNDE? OPERAND) ( S E T ! * K U N D E N - A N Z A H L * (+ 1 * K U N D E N - A N Z A H L * ) ) (WRITELN "Bearbeite Kunden!")) ((LIEFERANT? OPERAND) (SET! * L I E F E R A N T E N - A N Z A H L * (+ 1 * L I E F E R A N T E N - A N Z A H L * ) ) (WRITELN "Bearbeite Lieferant!")) (T ( W R I T E L N " W e d e r K u n d e n o c h L i e f e r a n t " ) ) ) ) } ==> ZÄHLUNG SCHULZE-GMBH)

Programmfragment2.2.1-3.

==>

"Bearbeite

Sequenzmi (vgl.

Lieferanten!"

tBEGIN-Konstrukt

Programmfragment

2.1-4)

Im Zusammenhang mit dem BEGIN-Konstrukt ist das ßZiG/TVO-Konstrukt zu nennen. Es wertet seine Argumente der Reihe nach aus und gibt als Rückgabewert den Wert des ersten Argumentes zurück. Da die Zählung der Argumentente intern üblicherweise mit Null beginnt, wurde an den Namen BEGIN einfach eine Null gehängt. eval>

(BEGINO

)

— >

Das BEGINO-Konstrukt läßt sich als syntaktische Vereinfachung von folgendem LET-Konstrukt auffassen: (BEGINO

...

)

entspricht: (LET

((X < s e x p r _ 0 > ) (Y ( L A M B D A () < s e x p r _ l > ( B E G I N (Y) X ) )

...

)))

/Hinweis: BEGINO-Implementation. Das BEGINO-Konstrukt ist häufig als Makro-Konstrukt realisiert. Das BEGINO-Makro führt zu einem LAMBDA-Konstrukt mit einem vom LISP-System generiertem Symbol als LAMBDA-Variable. eval>

( E X P A N D - M A C R O '(BEGINO 'TEIL-1 'TEIL-2 'TEIL-3)) ==> ( ( L A M B D A (S500000) (BEGIN (QUOTE TEIL-2) (QUOTE TEIL-3) %00000)) (QUOTE TEIL-1))

]

I. Konstrukte (Bausteine zum Programmieren)

58

Beispiel zum BEGINO-Konstrukt: Definiert ist ein Konstrukt zum Stapeln von Werten („Objekten") nach dem Prinzip „ L a s t /n, First Out" (kurz LIFO-Prinzip oder auch Stack-Prinzip genannt). Die Zugriffsfunktion POP! gibt das oberste Stack-Element, während die Funktion PUSH! das Objekt "kellert", d.h. in den Stack einspeichert. eval> eval>

eval>

eval> eval> eval>

(DEFINE (DEFINE

* S T A C K * '(MUELLER-OHG SCHULZE-GMBH) ==> * S T A C K * PUSH! (LAMBDA (OBJEKT) ( S E T ! * S T A C K * (CONS OBJEKT * S T A C K * ) ) " D u r c h g e f ü h r t ! " ) ) = = > PUSH! ( D E F I N E POP! (LAMBDA ( ) (BEGINO (CAR * S T A C K * ) ( S E T ! * S T A C K * (CDR * S T A C K * ) ) ) ) ) = = > POP! (PUSH! 'MEYER-AG) ==> "Durchgeführt!" (POP!) ==> MEYER-AG ( P O P ! ) ==> MUELLER-OHG

Wird beim BEGIN-Konstrukt eine Sequenz von symbolischen Ausdrücken notiert, die keine Nebeneffekte bewirken, dann ist nur die Auswertung des letzten symbolischen Ausdruckes notwendig. Die Auswertung aller vorhergehenden Ausdrücke ist einsparbar. Bei effizienten LISP-Implementationen wird diese Optimierungsmöglichkeit genutzt, so auch bei PC Scheme. eval>

(BEGIN

(CDR

*STACK*)

(CDR

(CDR (CDR

*STACK*)) (CDR (CDR

*STACK*))))

==>

()

Dieses BEGIN-Beispiel läßt sich auf den folgenden Ausdruck optimieren: eval>

(CDR

(CDR

(CDR

*STACK*)))

==>

()

2.2.2 Selektion Umfaßt ein Modul zwei oder mehr Teile, von denen im Anwendungsfall nur ein Teil zutrifft, dann ist eine Selektion (Auswahl) abzubilden. Das Bild 2.2.2-1 zeigt für den symbolischen Ausdruck GESAMT eine Selektion in Jackson-Notation (vgl. Jackson, 1979). Sein Wert ergibt sich entweder aus dem Modul ALTERNATIVE-1 oder ALTERNATIVE-2 oder ALTERNATIVE-3. Der Kreis in der rechten oberen Ecke unterscheidet die Selektion von der Sequenz. Für die Abbildung bietet sich das COND-Konstrukt an. Liegt nur eine Selektion zwischen zwei Strukturen vor, ist das IF-Konstrukt geeignet (vgl. Programmfragment 2.2.1-3). Anhand der Entscheidungstabelle (engl.: decision table), die Bild 2.2.2-2 darstellt, erörtern wir die Selektion. Eine Entscheidungstabelle (kurz ET) besteht aus einem Bedingungsteil und einem Aktionsteil. Die Bedingungen (im Beispiel B l , B2, B3 und B4) werden für den SelektionsVorgang der Reihe nach auf ihren Wahrheitswert hin geprüft. Die Regeln (im Beispiel R l , R2 bis R7) verknüpfen

2. Kombinieren von Konstrukten

59

den Bedingungsteil mit dem Aktionsteil. Sie dokumentieren, bei welchen Wahrheitswerten welche Aktionen (im Beispiel AI, A2 bis A5) durchzuführen sind. Trifft stets nur eine Regel nach Prüfung der Bedingungen zu, dann handelt es sich um eine Eintreffer-ET, andernfalls um eine Mehrtreffer-ET (vgl. z.B. Bild 11.3-1). Beschränken sich die Wahrheitswerte auf „Ja" (Abkürzung "J"), „Nein" (Abkürzung „N") und „Irrelevant" (Abkürzung „-"), dann handelt es sich um eine begrenzte ET; begrenzt, weil die Definition der Bedingungen sich nicht in den Regelanzeigerteil hineinzieht. Eine „ET Verkehrsampel" mit Zuständen wie „ROT", "GELB" und „GRÜN" als Angaben in den Regeln wäre nicht begrenzt.

Für eine begrenzte ET wird folgende Notation verwendet (vgl. DIN 66241): o im Entscheidungsteil: J

: : = Die Bedigung i s t

N

: : = Die Bedingung i s t

#

erfüllt. nicht

erfüllt.

: : = Die Anwendung der Regel i s t n i c h t davon a b h ä n g i g , ob d i e Bedingung e r f ü l l t oder n i c h t e r f ü l l t i s t . Man kann d i e s e s Z e i c h e n a l s Dokumentation der I r r e l e v a n z bet r a c h t e n . Die Bedingung kann g e p r ü f t werden, nur i s t das P r ü f u n g s e r g e b n i s u n e r h e b l i c h . ::= Ausschlußanzeiger, d.h. für die Entscheidung i s t die Bedingung n i c h t d e f i n i e r t ; i h r W a h r h e i t s w e r t kann n i c h t e r m i t t e l t werden. ( I s t h i e r wegen der V o l l s t ä n d i g k e i t angegeben und w i r d im B e i s p i e l n i c h t v e r wendet .)

o im Aktionsteil. X blank

: : = Die A k t i o n i s t

durchzuführen,

: : = Die A k t i o n i s t

nicht

durchzuführen.

60

I. Konstrukte (Bausteine zum Programmieren)

ET-Auszahlung-buchen

Rl

R2 R3

R4 R5

R6 R7

B1

Kontonummer

angegeben?

J

J

J

J

N

N

N

B2

Kontonummer

= 50.00

40000 DM

genannt? vorlegen

X X

Konto

Vorgang abgeben

an A b t e i l u n g

Buchung

vollziehen

Formale

X

X 2 X X

X

X

Vol1ständigkeitsüberprüfung:

2 e i n w e r t i g e R e g e l n (R2 u n d R3) 3 z w e i w e r t i g e R e g e l n (Rl, R6 u n d 2 v i e r w e r t i g e R e g e l n (R4 u n d R5)

R7)

— > — > — >

Summe: Bild

X

2.2.2-2.

Begrenzte

2 6 8 16

Fälle Fälle Fälle Fälle =

24-Regeln

Eintreffer-Entscheidungstabel1e

Bei einer begrenzten Eintreffer-Entscheidungstabelle ist feststellbar, ob für alle möglichen Bedingungskombinationen auch Regeln genannt sind (formale Vollständigkeit). Bei einer solchen ET mit 4 Bedingungen sind 24-Regeln notwendig, wenn nur die Wahrheitswerte „J" und „N" verwendet werden. Enthält die ET Regeln mit Irrelevanz-Zeichen („-"), dann können diese „mehrwertigen" Regeln leicht auf mehrere „einwertige" zurückgeführt werden (vgl. Bild 2.2.2-2). Die ET-Auszahlung-buchen im Bild 2.2.2-2 definiert, welche Aktionen in einem „aktuellen" Fall durchzuführen sind. Um die Daten eines „aktuellen" Falles zu erhalten, wird eine einfache Dialogschnittstelle konstruiert. Dazu verwenden wir das READ-Konstrukt und das WRITELN-Konstrukt. Bei einer Antwort „N" für die Bedingung B1 ist es nicht mehr entscheidungsrelevant, den „aktuellen" Wert der Bedingung B2 zu erfragen. Es sollten daher nicht die aktuellen Werte aller Bedingungen der Reihe nach abgefragt werden. Ist eine Bedingung nicht relevant für den weiteren Auswahlprozeß zur Bestim-

61

2. Kombinieren von Konstrukten

mung der zutreffenden Regel, dann ist sie zu übergehen. Anders formuliert: Um ungewollte Nebeneffekte (hier: Abfragen) zu vermeiden, dürfen irrelevante Bedingungen nicht evaluiert werden. ;;;; ;;;;

Begrenzte E i n t r e f f e r - E n t s c h e i d u n g s t a b e l 1 e ET-AUSZAHLUNG-BUCHEN

; ;;;

Bedingungen

e v a l > ( D E F I N E B1 (LAMBDA ( ) (WRITELN " I s t d i e Kontonummer a n g e g e b e n ? ( J / N ) " ) ( R E A D ) ) ) = = > B1 e v a l > ( D E F I N E B2 (LAMBDA ( ) (WRITELN " I s t d i e Kontonummer k l e i n e r g l e i c h 4 0 0 0 0 ? ( J / N ) " ) ( R E A D ) ) ) = = > B2 e v a l > ( D E F I N E B3 (LAMBDA ( ) (WRITELN " I s t d e r B e t r a g g r o e s s e r g l e i c h 5 0 . 0 0 DM ? ( J / N ) " ) ( R E A D ) ) ) = = > B3 e v a l > ( D E F I N E B4 (LAMBDA ( ) (WRITELN " I s t der Zweck a n g e g e b e n ? ( J / N ) " ) ( R E A D ) ) ) = = > B4 ;;;; eval> eval> eval> eval> eval> ;; ;; eval>

Aktionen ( D E F I N E A I (LAMBDA ( ) (WRITELN "Dem C h e f v o r l e g e n " ) ) ) = = > A I ( D E F I N E A2 (LAMBDA ( ) (WRITELN " Z u r u e c k an d i e F a c h a b t e i l u n g s c h i c k e n " ) ) ) = = > A2 ( D E F I N E A3 (LAMBDA ( ) (WRITELN " U m k o n t i e r e n a u f K o n t o S O N S T I G E S " ) ) ) = = > A3 ( D E F I N E A4 (LAMBDA ( ) (WRITELN " V o r g a n g an A b t e i l u n g 2 a b g e b e n " ) ) ) = = > A4 ( D E F I N E A5 (LAMBDA ( ) (WRITELN " B u c h u n g v o l l z i e h e n " ) ) ) = = > A5 Regeln (DEFINE ET-AUSZAHLUNG-BUCHEN (LAMBDA ( ) (COND ( ( E Q ? ( B l ) ' J ) (COND ( ( E Q ? ( B 2 ) ' J ) (COND ( ( E Q ? ( B 4 ) ' J ) ( A 5 ) ) (T (COND ( ( E Q ? ( B 3 ) ' J ) ( A D ) (T ( A 3 ) ( A 5 ) ) ) ) ) ) (T ( A 4 ) ) ) ) (T (COND ( ( E Q ? ( B 3 ) ' J ) ( A 2 ) ) (T (COND ( ( E Q ? ( B 4 ) ' J ) (A3) (A5)) (T ( A D ) ) ) ) ) ) ) ) = = > ET-AUSZAHLUNG-BUCHEN

; ; ; ; Zwei B e i s p i e l e ;;; Benutzerantworten

in

Fettdruck

e v a l > (ET-AUSZAHLUNG-BUCHEN) = = > I s t d i e Kontonummer a n g e g e b e n ? ( J / N ) J I s t d i e Kontonummer k l e i n e r g l e i c h 4 0 0 0 0 V o r g a n g an A b t e i l u n g 2 abgeben ()

?

(J/N)

N

62

I. Konstrukte (Bausteine zum Programmieren)

eval> (ET-AUSZAHLUNG-BUCHEN) ==> Ist d i e K o n t o n u m m e r a n g e g e b e n ? ( J / N ) N Ist d e r B e t r a g g r o e s s e r g l e i c h 5 0 . 0 0 DM ? ( J / N ) N Ist d e r Z w e c k a n g e g e b e n ? ( J / N ) N Dem Chef vorlegen () P r o g r a m m f r a g m e n t 2 . 2 . 2 - 1 . Abbildung der ET von Bild 2.2.2-2 - B e d i n g u n g e n und A k t i o n e n a u ß e r h a l b des K o n s t r u k t e s E T - A U S Z A H L U N G - B U C H E N als F u n k t i o n e n d e f i n i e r t -

Die obige Lösung wirft die Frage auf: Kann eine tiefe Schachtelung von CONDKonstrukten durch Nutzung der logischen Verknüpfungen AND und OR vermieden werden? Zunächst erläutern wir diese logischen Verknüpfungen, indem wir sie für zwei Argumente (engl.: binary predicates) mit dem COND-Konstrukt abbilden. eval>

(DEFINE MEIN-AND (LAMBDA (ARG_1 ARG_2) (COND (ARG_1 ARG_2) (T N I L ) ))) = = > M E I N - A N D

;1. ;2.

Klausel Klausel

Ist ARG_ 1 wahr, d.h. ist ARG_ 1 an einen Wert ungleich NIL gebunden, dann wird die 1. Klausel des COND-Konstruktes ausgeführt. Es wird der Wert von ARG_ 2 festgestellt und als Wert von MEIN-AND zurückgegeben. Da ARG_ 1 wahr ist, entscheidet ARG_ 2, ob die AND-Verknüpfung insgesamt wahr ist. Ist ARG_ 1 jedoch gleich NIL, d.h. nicht wahr, dann kann sofort (ohne den Wert von ARG_2 nachzufragen) das gesamte AND-Konstrukt den Wert NIL erhalten. Analog dazu ist das OR-Konstrukt für zwei Argumente formulierbar: eval>

(DEFINE MEIN-OR (LAMBDA (ARG_1 ARG_2) ( C O N D ( A R G _ 1 T) (T A R G _ 2 ) ) ) ) = = >

MEIN-OR

Wir können die Definition von MEIN-OR noch kürzer formulieren. eval>

(DEFINE MEIN-OR (LAMBDA (ARG_1 ARG_2) (COND (ARG_1) (ARG_2))))

==>MEIN-0R

Bei dieser Definition nutzen wir die Möglichkeit, eine Klausel nur mit dem symbolischen Ausdruck für das Prädikat zu definieren (vgl. Abschnitt 2.1). Ist sein Wert ungleich NIL hat das gesamte COND-Konstrukt diesen Wert. Solche Notationen können für „Denkübungen" formuliert werden. Wir sollten jedoch genau prüfen, wann sie im Praxiseinsatz zweckmäßig sind.

63

2. Kombinieren von Konstrukten

/Exkurs: AND (bzw. OR) und CAND (bzw. COR). Die obigen Definitionen verweisen auf die in der Informatik gebräuchliche Unterscheidung zwischen AND (bzw. OR) und CAND (bzw. COR). Bei AND bzw. OR sind beide Argumente auszuwerten, auch wenn der logische Wert sich aus dem ersten ergibt (TRUE bei OR bzw. FALSE bei AND). Grund: Mögliche Nebeneffekte der Operanden sollen ausgeführt werden. Bei CAND und COR will man diese möglichen Nebeneffekte vermeiden, z.B. damit folgende Formulierung zu keiner Division durch 0 führt: (COR ( = 0 X)

(>

(/ 1 X)

7))

; b e i X = 0 kommt der z w e i t e ; n i c h t zur Ausführung

Teil

Für die logische Verknüpfung von A mit B können wir definieren: CAND : : = AND ::= COR ::= OR ::=

(COND (COND (COND (COND

(A B) (T N I L ) ) ( A B ) (B N I L ) (T N I L ) ) (A T) (T B ) ) (A B T) (B T ) (T N I L ) )

]

Die in PC Scheme eingebauten AND- und OR-Konstrukte entsprechen CANDund COR-Konstrukten, die nicht auf zwei Argumente beschränkt sind; sie können beliebig viele Argumente haben. eval>

(AND 1 2 3 4 5 6) = = > 6

eval>

(AND T ( )

eval>

(OR (OR (OR 1 N I L )

(8 1 2 ) )

==>

;wahr, ()

da a l l e

Werte u n g l e i c h

NIL

obwohl d a s 3 . A r g u m e n t n i c h t a u s w e r t b a r i s t , da 8 k e i n e n Operator a b b i l d e n kann.

(AND 2 N I L ) )

3) ==> 1

Die Negation eines Wahrheitswertes, das NOT-Konstrukt, läßt sich ebenfalls auf das COND-Konstrukt zurückführen: eval> eval> eval> eval>

( D E F I N E MEIN-NOT (LAMBDA (ARG) (COND (ARG N I L ) (MEIN-AND ' J A ' ( ) ) ==> () ( M E I N - O R ' ( ) ' J A ) ) = = > JA ( M E I N - N O T 5) = = > ( )

(T T ) ) ) )

==>

MEIN-NOT

/Hinweis: NOT und NULL?. Da NIL die leere Liste und den Wahrheitswert „false" repräsentiert, haben die Funktionen NOT und NULL? stets den gleichen Wert. Im Sinne einer überschaubaren Konstrukte-Menge könnten wir auf ein Konstrukt verzichten. Zur besseren Lesbarkeit verwenden wir trotzdem beide: Das NULL?-Konstrukt zum Prüfen einer leeren Liste und das NOT-Konstrukt, wenn es um den Wahrheitswert „false" geht, eval>

(DEFINE

NULL?

(LAMBDA

(LISTE)

(EQ?

LISTE N I L ) ) )

= = > NULL?

]

Wie dargelegt, wird in Scheme die Auswertung von OR beim ersten Wert ungleich NIL beendet, das gesamte OR-Konstrukt hat diesen Wert. Die Aus-

64

I. Konstrukte (Bausteine zum Programmieren)

wertung von AND wird beim ersten Wert NIL beendet, das gesamte ANDKonstrukt hat NIL als Wert. Das AND- und das OR-Konstrukt sind damit noch nicht vollständig spezifiziert. AND und OR können in einigen LISP-Systemen, z.B. in Scheme, auch ohne Argument angewendet werden. AND ohne Argument gibt den Wert #T zurück, weil kein Argument als kein falsches Argument interpretiert wird. OR ohne Argument führt zu NIL, da kein Argument als kein wahres Argument interpretiert wird. eval> (AND) ==> #T eval> (OR) ==> () Man könnte spontan mit AND-Konstrukten die ET (Bild 2.2.2-2) nach der „Regel-für-Regel-Programmierung" folgendermaßen notieren: eval> (DEFINE ET-AUSZAHLUNG-BUCHEN (LAMBDA () (COND : Regel 1 (AND (Bl) ( B2 ) ( B4 ) ) ( A5 ) ) : Regel 2 (AND (Bl) ( B2 ) ( B3 ) (NOT ( B4 ) ) ) (AI) ) ; Regel 3 (AND (Bl) (B2) (NOT ( B3 ) ) (NOT (B4) )) (A3) (A5) ; Regel 4 (AND (Bl) (NOT (B2 ) ) ) (A4) ) ; Regel 5 (AND (NOT (Bl)) ( B3 ) ) (A2) ) ; Regel 6 (AND (NOT (Bl)) (NOT ( B3 ) ) ( B4) ) (A3) (A5)) ; Regel 7 (AND (NOT (Bl)) (NOT ( B3 ) ) (NOT (B4) ) ) (AI)))) ) ==> ET-AUSZAHLUNG-BUCHEN Die Mängel dieser „Regel-für-Regel-Programmierung" sind: o das Nichtberücksichtigen des aktuellen Wertes einer Bedingung für die Auswahl der nächsten Bedingung und o das wiederholte Abfragen von Bedingungen (Wiederholtes Erzeugen der Nebeneffekte!). Beispielsweise fragt das obige Konstrukt ET-AUSZAHLUNG-BUCHEN siebenmal nach der Kontonummer, wenn die Regel R7 zutrifft. Um zumindest nicht jeweils über die Benutzerschnittstelle erneut die Eingaben abzufordern, d.h. die Nebeneffekte der Bedingungen zu vollziehen, könnten die Bedingungen statt als LAMBDA-Konstrukte direkt als Wahrheitswert definiert werden: eval> (DEFINE B1 (BEGIN (WRITELN " I s t die Kontonnummer angegeben? (J/N)") (READ))) ==> B1 Damit wird die Eingabe allerdings schon zum Zeitpunkt der Definition der Be-

2. Kombinieren von Konstrukten

65

dingung gefordert. Eine Zeitpunktverschiebung der Auswertung wäre erforderlich (Näheres vgl. Abschnitt 2.3). Für komplexe Fallunterscheidungen gibt es im allgemeinen weitere eingebaute Konstrukte, die gegenüber geschachtelten COND-Konstrukten eine vereinfachte Notation ermöglichen. Ein Beispiel ist das CA5£-Konstrukt (auch in PC Scheme vorhanden). Es erlaubt die Angabe von „Treffern" in einer Liste. Definiert ist es, wie folgt: eval > (CASE

{(

{}

)}

) — >

Zunächst wird der Ausdruck ausgewertet. Sein Wert wird sukzessiv mit jedem verglichen, wobei nicht evaluiert wird! Das CASE-Konstrukt heißt deshalb konsequenterweise bei einigen LISP-Systemen SELECTQ (z.B. in TLC-LISP), wobei Q für QUOTE steht. Die Art des Vergleiches wird vom Typ des -Ausdruckes bestimmt. Ist eine Liste, dann wird jedes Element mit dem Wert von verglichen. Ist ein Symbol außer ELSE, dann wird Identität bzw. Wertgleichheit bei Zahlatomen, Strings etc. geprüft. Ist das Symbol ELSE, dann ist der Vergleich sofort erfolgreich. Bei einem erfolgreichen Vergleich stoppt der Vergleichsprozeß, und die zugehörende Sequenz der wird evaluiert. Der Wert des CASE-Konstruktes ist der Wert des zuletzt evaluierten Ausdrucks (entspricht der Definition des COND-Konstruktes). Beispiel: eval>

eval>

CASE- und

COND-Konstrukt

( D E F I N E ZUSTIMMUNG? (LAMBDA ( ) (CASE (READ) ( ( J J A JAWOHL JAWOLL Y Y E S KLAR Z U S T I M M U N G G E W I S S SICHER F R E I L I C H SELBSTREDEND SELBSTVERSTAENDLICH A L L E R D I N G S GERN X T TRUE O U I Q U I T S I ) # T ) ( E L S E N I L ) ) ) = = > ZUSTIMMUNG? ( D E F I N E ZUSTIMMUNG? (LAMBDA ( ) ( L E T ((ANWORT ( R E A D ) ) ) (COND ( ( O R ( E Q ? ANWORT ' J ) ( E Q ? ANWORT ' J A ) ( E Q ? ANWORT ' J A W O H L )

(EQ? (T eval>

(ZUSTIMMUNG?)

ANWORT

NIL)))))

==>

'SI)

#T)

ZUSTIMMUNG?

==>

JAWOHL

¡Eingabe

#T

¡Wert

/Hinweis: CASE-und COND-Implementation. Beide Konstrukte basieren in PC Scheme auf dem IF-Konstrukt. Sie sind eine syntaktische Vereinfachung. Die

66

I. Konstrukte (Bausteine zum Programmieren)

folgende EXPAND-MACRO-Anwendungen zeigen, wie CASE- und COND als Makros definiert sind (Näheres zu Makros vgl. Abschnitt 8.3) eval>

eval>

(EXPAND-MACRO ' ( C A S E (READ) ( ( J ((LAMBDA UOOOOO) ( I F (MEMV % 0 0 0 0 0 ' ( J J A ) ) (BEGIN 1 2 ) (BEGIN 3 4 ) ) ) (READ)) (EXPAND-MACRO (IF

'(COND

JA)

1 2)

(ELSE

3 4)))

==>

¡Das M E M V - K o n s t r u k t p r ü f t , ; ob d e r W e r t v o n % 0 0 0 0 0 ; Element der L i s t e (J JA) ; ist.

((EQ? 'J (READ)) ( T 3 4 ) ) ) ==>

1 2)

(EQ? ' J (READ)) (BEGIN 1 2) (COND

(T 3 4 ) ) )

7

2.2.3 Iteration Eine Iteration (auch Répétition genannt) ermöglicht das wiederholte Durchlaufen bzw. Anwenden eines Konstruktes. Bild 2.2.3-1 zeigt die graphische Darstellung einer Iteration in Jackson-Notation (vgl. Jackson, 1979). Der Modul GESAMT besteht aus einer beliebig langen Folge von Moduln WIEDERHOLUNGS-TEIL. Dabei ist zu beachten: o GESAMT ist die Iteration und o die Häufigkeit des Auftretens von WIEDERHOLUNGS-TEIL ist eine Eigenschaft von GESAMT und nicht von WIEDERHOLUNGS-TEIL. Der Stern in der rechten oberen Ecke dokumentiert das mehrfache Auftreten von WIEDERHOLUNGS-TEIL innerhalb von GESAMT. Eine Iteration wird beendet: o entweder durch Zutreffen einer Abbruchbedingung, wobei die Ereignisprüfung vor oder nach einem Durchlauf erfolgen kann, o oder nach Vollzug einer vor Beginn der Iteration festgelegten Anzahl von Durchläufen. In vielen Programmiersprachen werden zur Kennzeichnung einer Iteration Bezeichner wie DO, WHILE, REPEAT, UNTIL oder FOR benutzt. In LISP lassen sich entsprechende Konstrukte definieren, falls diese im jeweiligen LISP-System nicht als eingebaute Konstrukte zur Verfügung stehen. Charakteristisch für LISP ist die Konstruktion einer Iteration in Form einer rekursiven Definition. Iterationen können als Spezialfälle der Rekursion betrachtet werden. Rekursive Darstellungen sind für LISP prägend, z.B. sind sowohl die Syntax als auch die Semantik der symbolischen Ausdrücke rekursiv definiert (vgl. Abschnitte 1.1 und 1.2). Wegen ihrer großen Bedeutung ist der Rekursion in diesem Kapitel ein eigener Abschnitt gewidmet.

2. Kombinieren von Konstrukten

67

In L I S P unterscheiden wir im Zusammenhang mit Wiederholungsstrukturen drei Abbildungskonzepte: o die üblichen Iterationskonstrukte, z.B. das DO-Konstrukt, o die Anwendung von MAP-Konstrukten („mapping functions") und o die Iteration als Spezialfall der Rekursion. Zunächst behandeln wir als bedingte Wiederholung das DO-Konstrukt. Nach diesem vielfältig nutzbaren Iterationskonstrukt wird die „klassische" Iteration mit MAP-Konstrukten erläutert. MAP-Konstrukte realisieren die Iteration, indem sie eine Funktion wiederholt auf eine Liste von Argumenten anwenden. Das DO-Konstrukt gliedert sich in drei Definitionsbereiche: (DO ( Zuständsvariablen ( Endebehandlung ) Verarbeitungskörper

) )

Zustandsvariablen: Die Definition einer Zustandsvariablen gliedert sich in die folgenden drei Teile:

: : = Namen der

: : = S y m b o l i s c h e r A u s d r u c k , an d e s s e n Wert d i e < v a r i a b l e > zu B e g i n n der I t e r a t i o n gebunden w i r d . Es h a n d e l t s i c h um den I n i t i a l w e r t f ü r .

Variablen

die

: : = S y m b o l i s c h e r A u s d r u c k , der bei j e d e r W i e d e r h o l u n g e v a l u i e r t w i r d und an d e s s e n Wert < v a r i a b l e > gebunden w i r d . Es h a n d e l t s i c h um den I t e r a t i o n s w e r t ( I n k r e m e n t oder Dekrement) f ü r die .

Endebehandlung-.

Die Definition der Endebehandlung entspricht einer Klausel

des COND-Konstruktes: Zuerst wird die Abbruchbedingung evaluiert und, falls ihr Wert ungleich N I L ist, wird die Sequenz von symbolischen Aus-

68

I. Konstrukte (Bausteine zum Programmieren)

drücken ... der Reihe nach von links nach rechts evaluiert. Der zuletzt ermittelte Wert ist der Wert des gesamten DOKonstruktes. Verarbeitungskörper. Eine Sequenz von symbolischen Ausdrücken, ... , die der Reihe nach ausgewertet werden. Sie dienen auch zur Manipulation der Variablen und des Abbruchtestes. Der Rückgabewert des DO-Konstruktes wird nur durch die Endebehandlung definiert. Die Definitionsbereiche müssen nicht zwingend einen symbolischen Ausdruck aufweisen. Das DO-Konstrukt ist, wie folgt, definiert: eval>

(DO ( { ( < v a r i a b l e > { < i n i t _ s e x p r > { < i t e r _ s e x p r > } } ( {} ) {} ) ==>

)} )

Anhand von zwei Beispielen zeigen wir die Arbeitsweise und Wirkung des DOKonstruktes. Zunächst bilden wir eine Zählschleife ab, indem wir den Satz „LISP hat ein dia-logisches Konzept" siebenmal schreiben. Danach summieren wir alle Elemente einer Liste, vorausgesetzt, es handelt sich um Zahlen. Beispiel: Siebenmal dieselbe Zeichenkette ausgeben Lösung 1\ Zählschleife mit Verarbeitung im eval>

LISP LISP LISP LISP LISP LISP LISP #T

(DO

hat hat hat hat hat hat hat

( ( Z A E H L E R 7 (- Z A E H L E R 1 ) ) ) ((= 0 ZAEHLER)) (WRITELN "LISP hat ein d i a - l o g i s c h e s K o n z e p t ! " ) ) ein d i a - l o g i s c h e s Konzept! ein d i a - l o g i s c h e s Konzept! ein d i a - l o g i s c h e s Konzept! ein d i a - l o g i s c h e s Konzept! ein d i a - l o g i s c h e s Konzept! ein dia-logisches Konzept! ein dia-logisches Konzept! ; R ü c k g a b e w e r t = Wert des A u s d r u c k s ( = 0 ZAEHLER)

==>

Lösung 2: Zählschleife mit Verarbeitung im eval>

LISP

#T

(DO

((ZAEHLER

7 (BEGIN (WRITELN "LISP hat ein d i a - l o g i s c h e s (- Z A E H L E R 1)))) ((= 0 Z A E H L E R ) ) ) = = > hat ein d i a - l o g i s c h e s K o n z e p t !

Konzept!")

2. Kombinieren von Konstrukten

69

Lösung 3: Zählschleife mit Verarbeitung im -Ausdruck eval>

LISP

(DO

hat

((ZAEHLER 6 ( - ZAEHLER 1 ) ) ) ( ( B E G I N (WRITELN " L I S P hat ein d i a - 1 o g i s c h e s (= 0 ZAEHLER)))) ==> ein dia-logisches Konzept!

Konzept!")

#T Die Lösungen 2 und 3 stellen eine „trickreiche" Nutzung des DO-Konstruktes dar. Sie sind weniger transparent als die Lösung 1. Hier sind sie nur zum Verstehen des DO-Konstruktes formuliert. Unstrittig ist die Lösung 1 den beiden Lösungen 2 und 3 vorzuziehen. Sie hat die vom Leser (gewohnte) einfache DOStruktur und wird daher schneller verstanden.

Beispiel: Zahlen in einer Liste summieren Lösung 1: Aufaddieren der Zahlen im Verarbeitungskörper des DO-Konstruktes eval>

eval>

( D E F I N E SUMME-DER-ZAHLEN (LAMBDA ( L ) (DO ( ( L I S T E L ( C D R L I S T E ) ) (AKKUMULATOR 0 ) ) ¡ H i l f s v a r i a b l e f ü r d a s E r g e b n i s ( ( N U L L ? (CAR L I S T E ) ) AKKUMULATOR) ¡Endebedingung (COND ( ( N U M B E R ? ( C A R L I S T E ) ) ( S E T ! AKKUMULATOR ( + AKKUMULATOR ( C A R L I S T E ) ) ) ) (T " N a e c h s t e s Listenelement"))))) ==> SUMME-DER-ZAHLEN (SUMME-DER-ZAHLEN ' ( A 3 B 1 C 2 ) ) ==> 6

Lösung 2: Aufaddieren der Zahlen im Iterationsteil des DO-Konstruktes eval>

( D E F I N E SUMME-DER-ZAHLEN (LAMBDA (DO ( ( L I S T E L ( C D R L I S T E ) ) (AKKUMULATOR

(L)

0

(COND

eval>

((NUMBER? (CAR L I S T E ) ) ( + AKKUMULATOR ( C A R L I S T E ) ) ) (T " N a e c h s t e s Listenelement" AKKUMULATOR)))) ( ( N U L L ? (CAR L I S T E ) ) A K K U M U L A T O R ) ) ) ) ==> SUMME-DER-ZAHLEN ( S U M M E - D E R - Z A H L E N ' ( 1 2 3 4 A B C D ) ) = = > 10

/Hinweis: DO-Implementation. Anhand der DO-Implementation wird deutlich, daß die Iteration mit dem DO-Konstrukt sich auf die Rekursion mittels L E T R E C abstützt. Wie das LET-Konstrukt (vgl. Abschnitt 2.2.1), so ist auch das DOKonstrukt in PC Scheme als Makro-Konstrukt (vgl. Abschnitt 8.3) realisiert. Analog zum BEGINO-Makro (vgl. Abschnitt 2.2.1) wird auch hier eine lokale Variable generiert. Mit Hilfe des EXPAND-MACRO-Konstruktes ist das Ergebnis des Expandierens darstellbar. Dabei formulieren wir die der obigen DO-Definition als Zeichenketten:

70

I. Konstrukte (Bausteine zum Programmieren)

eval> (DEFINE F00 (EXPAND-MACRO '(DO ((V -1 "" "") C V-2 "" "")) ((OR (EQ? V-l 'ENDE) (EQ? V-2 'ENDE)) "" "") ""))) ==> FOO eval> FOO ==> ((LETREC ((%00000 (LAMBDA (V-l V-2) (IF (OR (EQ? V-l 'ENDE) (EQ? V-2 'ENDE)) (BEGIN "" "") (BEGIN "" (%00000 "" "")))))) %00000) "" "") Zum Verständnis-Training expandieren wir die Lösungen 1 und 3 des obigen Beispiels „Siebenmal eine konstante Zeichenkette ausgeben": eval> (EXPAND-MACRO '(DO ((ZAEHLER 7 (- ZAEHLER 1))) ((= 0 ZAEHLER)) (WRITELN "LISP hat ein dia -1ogisches Konzept!") )) ==> ((LETREC («00000 (LAMBDA (ZAEHLER) (LET ( « 0 0 0 0 1 (= 0 ZAEHLER))) (IF %00001 %00001 (BEGIN (WRITELN "LISP hat ein dia-1ogisches Konzept!") «00000 (- ZAEHLER 1 ) ) ) ) ) ) ) ) %00000) 7) eval> (EXPAND-MACRO (DO ((ZAEHLER 6 (- ZAEHLER 1))) ((BEGIN (WRITELN "LISP hat ein dia -1ogisches Konzept!") (= 0 ZAEHLER))) )) ==> ((LETREC («00000 (LAMBDA (ZAEHLER) (LET («00001 (BEGIN (WRITELN "LISP hat ein dia -1ogisches Konzept!") (= 0 ZAEHLER)))) (IF %00001 %00001

%00000)

6)

(BEGIN «00000 (- ZAEHLER 1 ) ) ) ) ) ) ) )

]

2. Kombinieren von Konstrukten

71

Im Rahmen des Entwurfes von linearen Kontrollstrukturen werden Iterationen oft in der von I. Nassi und B. Shneiderman 1973 vorgeschlagenen Form dargestellt (vgl. Bild 2.2.3-2). Die Iteration mit der Bedingungsprüfung vor jedem Wiederholungsdurchlauf wird auch abweisende Schleife oder salopp: „kopfgesteuerte" Schleife genannt. Sie ist zu unterscheiden von der Iteration mit der Bedingungsprüfung nach jedem Wiederholungsdurchlauf. Diese wird als nichtabweisende Schleife oder salopp: „fußgesteuerte" Schleife bezeichnet. Im ersten Fall, dem while-Konstrukt, wird so lange wiederholt, bis die Bedingung erfüllt ist. Im zweiten Fall, dem until-Konstrukt, wird der Wiederholungsvorgang beendet, wenn die Bedingung erfüllt ist.

GESAMT

GESAMT

Wiederholung mit vorausgehender Bedingungsprüfung (kurz: w h i l e - K o n s t r u k t )

Wiederholung mit nachfolgender Bedingungsprüfung (kurz: until-Konstrukt)

Notiert whi1 e p do Bild

B od

2.2.3-2.

in e i n e m

Pseudocode: do B until

q

od

Bedingte Schleifen - Graphische Darstellung n a c h DIN 6 6 2 6 1 (Struktogramm, vgl. N a s s i / S h n e i d e r m a n , 1973)

In den beiden folgenden Beispielen wird die Situationsänderung für die Bedingungsprüfung über Zufallszahlen bewirkt. Das RANDOM-Konstmkt gibt als Wert Pseudozufallszahlen zurück und zwar in dem Wertebereich von Null bis Wert des Argumentes minus 1. eval>

(RANDOM

5) = = >

3

Beispiele: Bedingte Schleifen eval> eval> eval>

;0 < = P s e u d o z u f a l 1 s z a h l

< 5

(while-/until-Konstrukte)

(DEFINE BLOCK ( L A M B D A () ( W R I T E L N " B l o c k v e r a r b e i t e t " ) ) ) = = > B L O C K (DEFINE WIEDERHOLUNGS-BEDINGUNG ( L A M B D A () (< ( R A N D O M 5) 3 ) ) ) = = > W I E D E R H O L U N G S - B E D I N G U N G (DEFINE ABBRUCH-BEDINGUNG ( L A M B D A () ( N O T ( W I E D E R H O L U N G S - B E D I N G U N G ) ) ) )

72

I. Konstrukte (Bausteine zum Programmieren)

e v a l > (DEFINE W H I L E - B E I S P I E L (LAMBDA ( ) (DO ((P (WIEDERHOLUNGS-BEDINGUNG) (WIEDERHOLUNGS-BEDINGUNG))) ((NOT P)) ( B L O C K ) ) ) ) ==> WHILE-BE I S P I EL

==> ABBRUCH-BEDINGUNG Bedi ngung D O - I n i t i a l i s i e rung D O - I t e r a t i onswert DO-Exit-Bedingung

e v a l > (DEFINE U N T I L - B E I S P I E L (LAMBDA ( ) (DO ((Q NIL ; Um BLOCK zumindest einmal (ABBRUCH-BEDINGUNG))) (Q) ; DO-Exit-Bedingung ( B L O C K ) ) ) ) ==>

abzuarbeiten.

e v a l > (WHILE-BEI S P I EL) ==> Block v e r a r b e i t e t Block v e r a r b e i t e t #T ¡Rückgabewert der Bedingung (NOT P) e v a l > (WH I LE - BEI S P I EL) ==>

#T

e v a l > ( U N T I L - B E I S P I EL) ==> Block v e r a r b e i t e t Block v e r a r b e i t e t Block v e r a r b e i t e t Block v e r a r b e i t e t #T ¡Rückgabewert der Bedingung

(Q)

e v a l > (SET! WI ED ERHOLUNGS-BED 1NGUNG (LAMBDA () N I L ) ) ==> NIL e v a l > (WHILE - BEI S P I EL) ==> #T e v a l > ( U N T I L - B E I SP I EL) ==> Block v e r a r b e i t e t #T

Das obige Konstrukt UNTIL-BEISPIEL entspricht einer Sequenz aus BLOCK und anschließendem while-Konstrukt. Die Beziehungen zwischen while- und until-Konstrukten zeigt Bild 2.2.3-3. Aufgrund der Listen-Dominanz bezieht sich in LISP die Wiederholung häufig auf die Abarbeitung aller Elemente einer Liste. Ist eine Funktion auf jedes Element einer Liste anzuwenden, dann bilden wir diese Iteration mit einem A/Af-Konstrukt („mapping function") ab. LISP-Systeme haben ein ganzes Bündel von „mapping functions". Einige sammeln die Werte der wiederholten Funktionsapplikation und geben diese als Wert zurück, andere führen die Funktionsapplikation nur zur Erzielung von Nebeneffekten durch; ihr Rückgabewert ist unerheblich. eval> (

) — >

2. Kombinieren von Konstrukten

73

mit

::= A b h ä n g i g v o n d e r j e w e i l i g e n < m a p p i n g function> entsteht ein Rückgabewert. Stets wird die A p p l i k a t i o n der < u s e r - f u n c t i o n > auf d i e e i n z e l n e n A r g u m e n t e von < a r g u m e n t s > durchgeführt : ( ) ==>

(

)

==>

G e g e b e n e n f a l l s w i r d a n s c h l i e ß e n d aus < v a l u e _ l > bis < v a l u e _ n > der R ü c k g a b e w e r t gebildet, z.B. eine Liste.

Fall

1: Unti 1 - K o n s t r u k t

ersetzt

durch

ein

whi1e-Konstrukt BLOCK-X

BLOCK-X

while

(NOT

q)

BLOCK-X until Fall

q

2: W h i 1 e - K o n s t r u k t

ersetzt

durch

ein

unti1 - K o n s t r u k t

whi le p Tr BLOCK-Y



F

BLOCK-Y until

(NOT

p)

Legende: Vgl. Bild 2.2.3-2 ./. ::= I d e n t i t ä t s f u n k t i o n ( z w e c k m ä ß i g daß e i n e B l o c k e i n t r a g u n g n i c h t Bild

2.2.3-3

Iterati

zur K e n n z e i c h n u n g , vergessen wurde).

onskonstrukte

Bei den MAP-Konstrukten besteht die eigentliche Iteration in der Wiederholung der Anwendung von auf die einzelnen Argumente. Dabei ist eine Funktion mit einer LAMBDA-Variablen, d.h. eine Funk-

74

I. Konstrukte (Bausteine zum Programmieren)

tion definiert für ein Argument (engl.: unary function). Da das MAP-Konstrukt diese als Argument hat, ist es selbst eine Funktion höherer Ordnung. Eine Funktion höherer Ordnung (engl.: higher-order function) ist eine Funktion, die entweder eine Funktion als Argument hat und/oder eine Funktion als (Rückgabe-)Wert. Der Vorteil dieser MAP-Iteration ist das Abstrahieren von den Details, wie die einzelnen Argumente der Reihe nach selektiert werden und die Abbruchbedingung geprüft wird. Das folgende Beispiel Sachmittelplanung einer Behörde demonstriert die beiden in PC Scheme eingebauten „mapping functions": Das MAP-Konstrukt und das FO/?-£AC//-Konstrukt. eval> eval>

(MAP L I S T (FOR-EACH

' ( 1 2 3 ) ) ==> ( ( 1 ) (2) L I S T ' ( 1 2 3 ) ) = = > #T

(3))

Beide wenden eine Funktion, die für ein Argument definiert ist, auf eine Liste von Argumenten an. Der Wert des MAP-Konstruktes ist eine Liste, die die Resultate der einzelnen Funktionsanwendung enthält. Der Wert des FOR-EACHKonstruktes ist #T. /Hinweis: Mapping functions. In Common LISP umfaßt das Repertoire an „mapping functions" auch Konstrukte, bei den sich die Funktionsapplikation auf die jeweilige Restliste bezieht, z.B. MAPLIST oder MAPCON. Die beiden hier verwendeten „mapping-functions" sind in Scheme-Notation, wie folgt, definierbar (Näheres zur Rekursion vgl. Abschnitt 3): eval>

eval>

( D E F I N E FOR-EACH (LAMBDA (FUNKTION (COND ( ( N U L L ? L I S T E ) N I L ) (T (FUNKTION (CAR L)

LISTE)

(FOR-EACH FUNKTION (CDR L ) ) ) ) ) ) = = > FOR-EACH ( D E F I N E MAP (LAMBDA (FUNKTION L I S T E ) (COND ( ( N U L L ? L I S T E N I L ) (T (CONS (FUNKTION (CAR L) (MAP FUNKTION (CDR L ) ) ) ) ) ) ) = = > MAP

Wir betrachten eine Behörde und deren Sachmittelbedarf. Für das Jahr 1990 hatte die Abteilung W einen Bedarf pro Quartal in folgender Höhe: eval>

(DEFINE SACHMITTEL-QUARTALSBEDARF-ABTEILUNG-W-1990 ' ( 1 5 0 0 0 0 100000 80000 1 2 0 0 0 0 ) ) ==> SACHMITTEL-QUARTALSBEDARF-ABTEILUNG-W-1990

Für das Jahr 1991 ist eine Steigerung von 2,45 Prozent vorzusehen. Jeder Quartalsbetrag ist mit 1.0245 zu multiplizieren, um die Werte für 1991 zu erhalten. Die Berechnung der neuen gerundeten Werte kann als wiederholte Anwendung der anonymen Funktion (LAMBDA (BETRAG) (ROUND (* 1.0245 BETRAG))) definiert werden.

]

2. Kombinieren von Konstrukten eval>

eval>

75

( D E F I N E SACHM I T T E L - Q U A R T A L S B E O A R F - A B T E I L U N G - W - 1 9 9 1 (MAP ( L A M B D A ( B E T R A G ) (ROUND ( * 1 . 0 2 4 5 B E T R A G ) ) ) SACHMITTEL-OUARTALSBEDARF-ABTEILUNG-W-1989)) ==> SACHMITTEL-QUARTALSBEDARF-ABTEILUNG-W-1991 SACHMITTEL-QUARTALSBEDARF-ABTEILUNG-W-1991 ==> (153675 102450 81960 122940)

Wir nehmen nun an, daß die Quartalswerte für 1990 für alle Abteilungen der Behörde in folgender Matrix-Struktur vorliegen: eval>

(DEFINE SACHMITTEL-QUARTALSBEDARF-BEH0ERDE-1990 '((LEITUNG ( 10000 10000 10000 10000)) ( A B T E I LUNG-E (450000 423000 411000 567230)) (ABTEILUNG-T (159000 156000 85000 121300)) (ABTEILUNG-W (150000 100000 80000 120000)) (AUSSENSTELLE-HAMBURG ( 34500 32900 25000 55000)))) ==> SACHMITTEL-QUARTALSBEDARF -BEH0ERDE-1990

Zur Berechnung der Werte für das Jahr 1991 sind zwei geschachtelte Iterationen notwendig. Es ist über die 5 Zeilen und die 4 Betragsspalten der Matrix-Struktur zu iterieren. eval>

(DEFINE SACHMITTEL-QUARTALSBEDARF-BEH0ERDE-1991 (MAP ( L A M B D A ( O R G A N I S A T I O N ) ( L I S T (CAR O R G A N I S A T I O N ) (MAP ( L A M B D A ( B E T R A G ) (ROUND ( * 1 . 0 2 4 5 B E T R A G ) ) ) (CAR (CDR O R G A N I S A T I O N ) ) ) ) ) SACHMITTEL-QUARTALSBEDARF-BEHOERDE-1990)) ==> SACHMITTEL-QUARTALSBEDARF-BEHOERDE-1991

eval>

SACHMITTEL-QUARTALSBEDARF-BEHOERDE-1991 ==> ((LEITUNG ( 10245 10245 10245 (ABTE I LUNG-E (461025 433364 421070 (ABTEILUNG-T (162896 159822 87083 (ABTEILUNG-W (153675 102450 81960 (AUSSENSTELLE-HAMBURG ( 35345 33706 25613

10245)) 581127)) 124272)) 122940)) 56348)))

Um den Gesamtbedarf an Sachmitteln für die Behörde für das Jahr 1991 zu berechnen, sind alle Beträge in der Matrix-Struktur zu summieren. Dazu ist in den geschachtelten Iterationen ein Akkumulator erforderlich. Dieser ist als lokale Variable mit Hilfe des LET-Konstruktes definierbar. Da der Endwert des Akkumulators die gesuchte Lösung darstellt, sind die Rückgabewerte der beiden MAP-Konstrukte nutzlos. Wir ersetzen daher die MAP-Konstrukte durch FOREACH-Konstrukte. Das FOR-EACH-Konstrukt unterscheidet sich von dem MAP-Konstrukt dadurch, daß die Iterationsergebnisse nicht den Rückgabewert bilden. eval>

(DEFINE GESAMTBEDARF-BEHOERDE-1991 (LET ((AKKUMULATOR 0 ) ) (F0R-EACH (LAMBDA ( O R G A N I S A T I O N )

76

I. Konstrukte (Bausteine zum Programmieren) (FOR-EACH (LAMBDA (BETRAG) (SET! AKKUMULATOR (+ AKKUMULATOR BETRAG))) (CAR (CDR ORGANISATION)))) SACHMITTEL-QUARTALSBEDARF-BEHOERDE-1991) AKKUMULATOR)) ==> GESAMTBEDARF-BEHOERDE-1991

eval> GESAMTBEDARF-BEHOERDE-1991 ==> 3083676

2.3 Zeitpunkt einer Wertberechnung Im Rahmen der Selektion (Abschnitt 2.2.2) wurde anhand einer Entscheidungstabelle (Bild 2.2.2-2) deutlich, daß bei Nebeneffekten der Zeitpunkt einer Wertberechnung bedeutsam ist. Wir wollen explizit den Zeitpunkt festlegen, wann die einzelnen Bedingungen ihren Wert berechnen. Zum Zeitpunkt der Definitionen von B l , B2, B3 und B4 dürfen die Abfragen, d.h. die Nebeneffekte durch die WRITELN- und READ-Konstrukte, noch nicht zur Ausführung kommen. Wir haben daher die Bedingungen als Funktionen definiert und ihren Berechnungszeitpunkt durch den Funktionsaufruf terminiert (vgl. Programmfragment 2.2.2-1). Die Wertberechnung, hier die Abfrage, wurde auf den Applikationszeitpunkt verschoben. Zur Verschiebung des Zeitpunktes der Wertberechnung eines beliebigen symbolischen Ausdrucks notieren wir diesen innerhalb eines LAMBDA-Konstruktes, das keine LAMBDA-Variablen besitzt. Mit dem Evaluieren dieses LAMBDA-Konstruktes entsteht eine Funktion, kurz „thunk" genannt. /Hinweis: Thunk. Eine Funktion, definiert für kein Argument, wird als „thunk" bezeichnet. Dieses Wort stammt aus der ALGOL-Welt. Welche ursprüngliche Bedeutung es hatte ist unbekannt. Es gibt nur scherzhafte Interpretationen, wie den Verweis auf „Registergeräusche". ] Zum gewünschten Zeitpunkt applizieren wir „thunk" (unsere Funktion) und veranlassen damit die Wertberechnung. Die Kombination von Funktions-Definition (LAMBDA-Konstrukt) und Funktions-Applikation (implizites oder explizites APPLY-Konstrukt) bewirkt die Zeitpunktverschiebung der Wertberechnung. eval> ;; , a l s o der Wert von , ;; wird j e t z t b e r e c h n e t . (DEFINE F00 ) ==> F00 eval> ;; Der berechnete Wert wird j e t z t ;; nur gesucht und ausgegeben F00 ==>

2. Kombinieren von Konstrukten

77

eval>

;; D e r W e r t d e s L A M B D A - K o n s t r u k t e s w i r d b e r e c h n e t . ;; < v a l u e > , a l s o d e r W e r t v o n < s e x p r > , ist n o c h ;; n i c h t b e r e c h n e t . ( D E F I N E F00 ( L A M B D A () < s e x p r > ) ) = = > F00

eval>

;; < v a l u e > w i r d b e r e c h n e t (F00) ==>

und

ausgegeben.

Potentiell besteht mit der Kombination QUOTE und EVAL eine Alternative zur Kombination LAMBDA und APPLY. Die Auswertung des symbolischen Ausdrucks wird durch das QUOTE-Konstrukt blockiert, so daß sich wie eine „Konstante" verhält, die mit dem EVAL-Konstrukt zum gewünschten Zeitpunkt „aktiviert" wird (vgl. Abschnitt 1.3). eval>

;; < v a l u e > , a l s o d e r W e r t v o n < s e x p r > , ;; w i r d n i c h t b e r e c h n e t . D i e A u s w e r t u n g ;; v o n < s e x p r > ist b l o c k i e r t . (DEFINE F00 ') ==> F00

eval>

;; D e r W e r t v o n F 0 0 , a l s o ;; w i r d a u s g e w e r t e t . (EVAL F00) = = > < v a l u e >

,

Im folgenden sind beide Möglichkeiten für die Entscheidungstabelle von Bild 2.2.2-2 dargestellt. Das erste Programmfragment (2.3-1) nutzt den funktionalen Ansatz. Das zweite Programmfragment (2.3-2) verwendet die Auswertungsblokkierung in Verbindung mit dem EVAL-Konstrukt. Die Benutzerschnittstelle ist in der ET-Abbildung des Abschnittes 2.2.2 (vgl. Programmfragment 2.2.2-2) außerhalb des ET-Konstruktes definiert. Hier sind in beiden Fällen die Bedingungen und Aktionen mit Hilfe des LET-Konstruktes als lokale Variable des Konstruktes ET-AUSZAHLUNG-BUCHEN formuliert, also innerhalb der ET definiert. Damit wird einerseits ein ungewolltes Überschreiben im Falle bestehender oder neuer namensgleicher Konstrukte vermieden und andererseits die Zugehörigkeit zum ET-Konstrukt unmittelbar sichtbar. Zusätzlich bewahren wir uns dadurch die leichtere Überschaubarkeit der „top level"-Umgebung, weil diese dann weniger Einträge hat. ;;;; ;;;;

Begrenzte Eintreffer-Entscheidungstabel1e ET-AUSZAHLUNG-BUCHEN mit lokalen Fuktionen

eval> (DEFINE (LET

ET-AUSZAHLUNG-BUCHEN

I. Konstrukte (Bausteine zum Programmieren)

78

;;;

Bedingungen ((Bl (B2

(B3

(B4

; ;;

Aktionen (AI (A2 (A3 (A4 (A5

;;;

( L A M B O A () (WRITELN "Ist die K o n t o n u m m e r a n g e g e b e n ? (J/N)") (READ))) ( L A M B D A () (WRITELN " I s t d i e K o n t o n u m m e r k l e i n e r g l e i c h 4 0 0 0 0 ? (J/N)"l (READ))) ( L A M B D A () (WRITELN " I s t d e r B e t r a g g r o e s s e r g l e i c h 5 0 . 0 0 DM ? ( J / N ) " ) (READ))) ( L A M B D A () (WRITELN "Ist der Zweck a n g e g e b e n ? (J/N)") (READ)))

( L A M B D A () ( L A M B D A () (WRITELN ( L A M B D A () (WRITELN ( L A M B D A () (WRITELN ( L A M B D A ()

(WRITELN

"Dem Chef

"Zurueck

an d i e

"Umkontieren "Vorgang (WRITELN

auf

vorlegen")))

Fachabteilung Konto

schicken")))

SONSTIGES")))

an A b t e i l u n g 2 a b g e b e n " ) ) ) "Buchung vollziehen"))))

Regeln

( LAMBDA (COND

( ) ( ( E Q ? (Bl) 'J) ( C O N D ( ( E Q ? (B2) 'J) ( C O N D ( ( E Q ? (B4) 'J) C A 5 ) ) (T ( C O N D ( ( E Q ? (B3) 'J) ( A D ) (T (A3) ( A 5 ) ) ) ) ) ) (T ( A 4 ) ) ) ) (T ( C O N D ( ( E Q ? (B3) 'J) ( A 2 ) ) (T ( C O N D ( ( E Q ? ( B 4 ) 'J) (A3) ( A 5 ) ) (T ( A I ) ) ) ) ) ) ) ) ) ) ==> ET-AUSZAHLUNG-BUCHEN

P r o g r a m m f r a g m e n t 2 . 3 - 1 . Abbildung der ET von Bild 2.2.2-2 - B e d i n g u n g e n und A k t i o n e n i n n e r h a l b des E T - K o n s t r u k t e s a l s lokale Funktionen definiert -

;;;; ;;;;

Begrenzte Eintreffer-Entscheidungstabel1e ET-AUSZAHLUNG-BUCHEN mit lokalen Konstanten

eval> (DEFINE (LET ;;;;

ET-AUSZAHLUNG-BUCHEN

Bedi n g u n g e n ((Bl

'(BEGIN "Ist die (B2 ' ( B E G I N

(WRITELN Kontonummer (WRITELN

angegeben?

(J/N)")

(READ)))

2. Kombinieren von Konstrukten "Ist die Kontonummer

79 kleiner gleich 40000 ? (J/N)") (READ)))

(B3 '(BEGIN (WRITELN "Ist der Betrag groesser gleich 50.00 DM ? (J/N)") (READ))) (B4 '(BEGIN (WRITELN "Ist der Zweck angegeben ? (J/N)") (READ))) Akti o nen (AI (A2 (A3 (A4 (A5

'(WRITELN '(WRITELN '(WRITELN '(WRITELN '(WRITELN

Regel n (LAMBDA () (COND ((EQ? (EVAL Bl) 'J) (COND ((EQ? (EVAL B2) 'J) (COND ((EQ? (EVAL B4) 'J) (EVAL A5)) (T (COND ((EQ? (EVAL B3) 'J) (EVAL A D ) (T (EVAL A3) (EVAL A5)))))) (T (EVAL A4)))) (T (COND ((EQ? (EVAL B3) 'J) (EVAL A2)) (T (COND ((EQ? (EVAL B4) 'J) (EVAL A3) (EVAL A5)) (T (EVAL A D ) ) ) ) ) ) ) ) ==> E T - A U S Z A H L U N G - B U C H E N P r o g r a m m f r a g m e n t 2 . 3 - 2 . A b b i l d u n g der ET von Bild 2.2.2-2 - Bedingungen und A k t i o n e n als lokale Konstanten definiert -

2.4 Typische Fehler Ob LISP-Anfanger oder LISP-Profi („LISP-wizard"), alle machen Fehler, der eine mehr, der andere weniger. Mißverständliche Vorgaben, Unkenntnis der Zusammenhänge, logische Fehlschlüsse und Flüchtigkeit sind häufige Ursachen. Nicht selten führt daher erst die Fehlersuche und Fehlerkorrektur zu einem tieferen Verständnis des Problems und seiner Lösung. Die folgenden fehlerhaften Konstrukte bieten daher die Möglichkeit, die bisher skizzierte LISP-Welt nochmals zu durchdenken. Es handelt sich um klassische Anfängerfehler, die gleichermaßen als Flüchtigkeitsfehler den LISP-Profis unterlaufen. In der Praxis wird jeder unter derartigen „Denkbeulen" selbst leiden müssen, ehe er die Souveränität gewinnt, um solche Fehler spontan zu erkennen und „lächelnd" beseitigen zu können.

80

I. Konstrukte (Bausteine zum Programmieren)

Fehler bei der Funktionsdefinition: Gewünschtes Konstrukt: eval>

( D E F I N E VERDOPPLUNG (LAMBDA ( Z A H L ) ( * ZAHL 2 ) ) )

==>

VERDOPPLUNG

Notierte Konstrukte: eval>

( D E F I N E M U R K S - 1 LAMBDA ( Z A H L ) ( * ZAHL 2 ) ) => ERROR Zu v i e l e s y m b o l i s c h e A u s d r ü c k e f ü r d a s D E F I N E - K o n s t r u k t , da d i e Klammern f ü r das LAMBDA-Konstrukt f e h l e n .

eval>

( D E F I N E MURKS-2 = = > ERROR . . .

eval>

( D E F I N E (MURKS= = > ERROR . . .

( * ZAHL 2 ) ) ) S y n t a k t i s e h e Kurzform des DEFINE-Kons t r u k t e s e r w a r t e t an d e r S t e l l e von ( * ZAHL 2) d a s Symbol ZAHL. Das Symbol ZAHL f e h l t und e i n e s c h l i e ß e n d e Klammer s t e h t an d e r falschen Stelle.

eval>

( D E F I N E MURKS-4 = = > ERROR . . .

(LAMBDA ( Z A H L ) ) ( * ZAHL 2 ) ) ;Zu v i el e s y m b o l i s e h e A u s d r ü c k e f ü r d a s ; D E F I N E - K o n s t r u k t , da d a s LAMBDA-Kon; s t r u k t s c h o n g e s c h l o s s e n wurde b e v o r ; der F u n k t i o n s k ö r p e r n o t i e r t i s t .

eval>

( D E F I N E MURKS-5 = = > ERROR . . .

(LAMBDA ( Z A H L ) ) ) ( * ZAHL 2) ;Zu w e n i g e s y m b o l i s c h e A u s d r ü c k e f ü r d a s L A M B D A - K o n s t r u k t . Da d a s D E F I N E - K o n s t r u k t schon beendet w i r d , bevor ( * ZAHL 2 ) g e l e s e n w i r d , f e h l t d e m LAMBDA-Konstrukt der F u n k t i o n s k ö r p e r .

eval>

( D E F I N E MURKS-6 (LAMBDA ( 1 ) ( * ZAHL 2 ) ) ) ;E i n Zahlatom, hier 1, i s t k e i n e = = > ERROR . . . g ü l t i g e V a r i a b l e . Zu b e a c h t e n i s t d e r U n t e r s c h i e d z w i s c h e n e i n e m Symbol ( L i t e r a l a t o m ) und e i n e m Z a h l a t o m .

(LAMBDA ( * ZAHL 2 ) ) ) ; D i e A n g a b e der LAMBDA-Variablen f e h l t . ; Daher h a t d a s L A M B D A - K o n s t r u k t zu w e n i g e ; symbolische Ausdrücke.

81

2. Kombinieren von Konstrukten Fehler beim Argument: Gewünschtes Konstrukt: eval>

(DEFINE NEUER-LISTENKOPF (LAMBDA ( L ) (CONS 'KONTO

eval>

(NEUER-LISTENKOPF

'(HUEL

(CDR L ) ) )

12 1 3 ) ) = = >

==>

NEUER-LISTENKOPF

(KONTO 12

13)

Notierte Konstrukte: eval>

( N E U E R - L I S T E N K O P F ' H U E L 12 13) = = > ERROR . . . Die F u n k t i o n i s t f ü r e i n Argument d e f i n i e r t . Si e w i r d aber mit d r e i Argumenten a u f g e r u f e n .

eval>

( N E U E R - L I S T E N K O P F "HUEL 12 13) ==> Warten ! . . . Der R E A D - P r o z e ß i s t n i c h t b e e n d e t , da d a s Ende e i n e r Z e i c h e n k e t t e , d a s d o p p e l t e Hochkomma, f e h l t .

eval>

(NEUER-LISTENKOPF = = > ERROR

eval>

(NEUER-LISTENKOPF) = = > ERROR . . K e i n A r g u m e n t a n g e g e b e n , obwohl d i e F u n k t i o n f ü r e i n Argument d e f i n i e r t wurde. B e a c h t e : H i e r i s t k e i n e Angabe ungleich NIL! e v a l > ( N E U E R - L I S T E N K O P F ( ) ) = = > (KONTO)

"HUEL 12 1 3 " ) F a l s c h e r Typ des A r g u m e n t s . Das CDR-Konstrukt i s t für eine Zeichenk e t t e n i c h t d e f i n i e r t . Es e r w a r t e t e i n e L i s t e bzw. e i n P u n k t - P a a r .

Fehler beim COND-Konstrukt: Gewünschtes Konstrukt: eval>

(DEFINE KLARE-ENTSCHEIDUNG? (LAMBDA (ANTWORT) (COND ( ( E Q ? ANTWORT ' N E I N ) ) ( ( E Q ? ANTWORT ' J A ) ) (T N I L ) ) ) ) = = > K L A R E - E N T S C H E I D U N G ?

eval> eval>

(KLARE-ENTSCHEIDUNG? (KLARE-ENTSCHEIDUNG?

Notiertes Konstrukt: eval>

( D E F I N E MURKS-7 (LAMBDA (ANTWORT)

'JEIN) 'NEIN)

==> () = = > #T

82

I. Konstrukte (Bausteine zum Programmieren) (COND ( E Q ? ANTWORT ' N E I N ) ( E Q ? ANTWORT ' J A ) (T N I L ) ) ) ) = = > MURKS-7 A n d e r S t e l l e des P r ä d i k a t e s e i n e r Klausel steht nicht eine Funktionsa n w e n d u n g , al s o e i n e L i s t e , s o n d e r n e i n S y m b o l , h i e r : E Q ? . Da d a s Symbol EQ? al s e i n g e b a u t e F u n k t i o n e i n e n Wert ungleich NIL hat, t r i f f t s t e t s die 1. K l a u s e l z u . D e r Wert i h r e s l e t z t e n symbolischen Ausdruckes i s t NEIN. Der Wert v o n MURKS-7 i s t d a h e r s t e t s NEIN.

e v a l > (MURKS-7

'JEIN)

e v a l > (MURKS-7

' J A ) ==>

==>

NEIN

NEIN

Gewünschtes Konstrukt: eval>

eval>

( D E F I N E GEWINN-PROGNOSE (LAMBDA ( S E X P R ) (COND ((NOT (NUMBER? S E X P R ) ) " K e i n e Z a h l " ) ((NUMBER? SEXPR) (WRITELN " D o p p e l t e r G e w i n n : " (* 2 (WRITELN " D r e i f a c h e r G e w i n n : " ( * 3 ) ) = = > GEWINN-PROGNOSE

SEXPR)) SEXPR))))

(GEWINN-PROGNOSE 1 0 0 0 ) = = > D o p p e l t e r Gewinn: 2000 D r e i f a c h e r G e w i n n : 3000 () ¡Wert d e s l e t z t e n W R I T E L N - K o n s t r u k t e s

Notiertes Konstrukt: eval>

( D E F I N E MURKS-8 (LAMBDA ( S E X P R ) (COND ((NOT (NUMBER? S E X P R ) ) " K e i n e Z a h l " ) ((NUMBER? S E X P R ) (WRITELN " D o p p e l t e r G e w i n n : " (* 2 SEXPR))) (WRITELN " D r e i f a c h e r G e w i n n : " ( * 3 S E X P R ) ) ) ) ) ==> MURKS-8 D i e Klammer nach dem K o n s t r u k t (WRITELN " D o p p e l t e r G e w i n n : " . . . ) s c h l i e ß t di e 2. K l a u s e l desCOND-Kons t r u k t e s . Das f o l g e n d e W R I T E L N - K o n s t r u k t i s t d a m i t d i e 3 . K l a u s e l m i t dem Symbol WRITELN an d e r S t e l l e d e s P r ä d i kates ( e r s t e Posi t i o n ) . ( U n ) g l ü c k l i cherweise wird die 3.Klausel nicht err e i c h t , da d i e b e i d e n v o r h e r g e h e n d e n Klauseln a l l e möglichen F ä l l e abfangen.

2. Kombinieren von Konstrukten

eval>

(MURKS-8

"1000")

eval>

(MURKS-8

1000)

Doppelter

83

==> " K e i n e

Zahl"

==>

Gewinn:

2000

()

Fehler mit dem QUOTE-Konstrukt: Gewünschtes Konstrukt: eval>

((LAMBDA

(X Y)

(LIST

X V))

'KONTO " S c h u l z e A G " ) = = > (KONTO " S c h u l z e

AG")

Notierte Konstrukte eval>

((LAMBDA (X Y) ( L I S T "X ' Y ) ) KONTO " S c h u l z e A G " ) = = > ERROR .. D i e V a r i a b l e KONTO i s t i n d e r a k t u e l l e n Umgebung n i c h t d e f i n i e r t . D i e A r g u m e n t e d e r anonymen F u n k t i on werden z u n ä c h s t e v a l u i e r t . Dabei i s t f ü r KONTO k e i n Wert ermi t t e l b a r .

eval>

((LAMBDA (X Y) ( L I S T ' X Y ) ) 'KONTO " S c h u l z e A G " ) = = > (X Y ) Die Argumente des L I S T - K o n s t r u k t e s s i n d aufgrund i h r e r Q u o t i e r u n g Konstanten. S i e e n t s p r e c h e n n i c h t den Werten an d i e d e f i n i e r t e n L A M B D A - V a r i a b l e n X und Y gebunden werden.

eval>

((LAMBDA ( ' X = = > ERROR

'Y) ...

( L I S T X Y ) ) 'KONTO " S c h u l z e A G " ) ; Es s i n d k e i n e S y m b o l e a l s LAMBDAV a r i a b l e n d e f i n i e r t . D u r c h d i e Quot i e r u n g von X u n d Y b e s t e h t d i e S c h n i t t s t e l l e des L A M B D A - K o n s t r u k t e s a u s K o n s t a n t e n . D i e s e können k e i n e Symbol-Wert-Assoziationen abbilden.

2.5 Zusammenfassung: LET, DO und MAP Wir definieren ein eigenes Konstrukt (Programm) indem wir eingebaute LISPKonstrukte und/oder selbstdefinierte Konstrukte miteinander kombinieren. Ob das Konstrukt „zweckmäßig" definiert ist, zeigt sich nicht nur in Bezug auf die spezifizierte Aufgabe, die es korrekt abzubilden gilt, sondern auch daran, ob das Konstrukt später einfach änderbar und ergänzbar ist. Der gesamte SoftwareLebenszyklus ist Bewertungsmaßstab. Für den symbolischen Ausdruck Liste verfügt ein LISP-System über den eingebauten Konstruktor CONS mit den zugehörenden Selektoren CAR und CDR sowie über ein Prädikat, das feststellt, ob die leere Liste vorliegt. Das Prädikat

84

I. Konstrukte (Bausteine zum Programmieren)

EQ? prüft die Identität zweier symbolischer Ausdrücke. Für den Test auf Gleichheit von zwei Listen gibt es das Prädikat EQUAL?. Konstrukte sind verknüpfbar als Sequenz, Selektion und Iteration. Zur Abbildung der Sequenz dienen die Komposition von Funktionen und Konstrukte wie BEGIN oder BEGINO. Das LAMBDA-Konstrukt hat in seinem Teil Rechenvorschrift eine implizit definierte Sequenz, da es dort eine Folge symbolischer Ausdrücke der Reihe nach auswertet. Das LET-Konstrukt als syntaktisch vereinfachte Anwendung einer anonymen Funktion (Applikation eines LAMBDA-Konstruktes) bindet seine lokalen Variablen nicht in einer Sequenz. Erst das LET*-Konstrukt vollzieht diese Bindung sequentiell. Die Selektion ist mit Konstrukten wie IF, COND oder CASE realisierbar. Die Iteration ist einerseits mit dem umfassenden DO-Konstrukt und andererseits mit MAP-Konstrukten („mapping functions") abbildbar. Die Iteration ist als Sonderfall der Rekursion betrachtbar. Sie wird daher auch als rekursive Formulierung notiert. Charakteristische

Beispiele für Abschnitt

2:

eval> eval> eval> eval>

(COND (1 2 3) (4 5 6)) ==> 3 (CADDR '(LAMBDA (X Y) (- Y X))) ==> (- Y X) (CONS 23.45 67.89) ==> (23.45 . 67.89) (EQ? (CONS 'MEYER-AG '(MUELLER-OHG)) '(MEYER-AG MUELLER-OHG)) ==> () eval> (EQUAL? (CONS 'MEYER-AG '(MUELLER-OHG)) '(MEYER-AG MUELLER-OHG)) ==> #T eval> (1+ 12) ==> 13 eval> (DEFINE *3 (LAMBDA (WERT) (LET ((DREIFACH (LAMBDA (X) (* 3 X))) (DREIFACH WERT)))) ==> *3 eval> (*3 5) ==> 15 eval> (LET* ((X 2) (Y (+ X 1))) (* X Y)) ==> 6 eval> (AND (NOT (AND (NOT 3 ) ) ) ) ==> #T eval> (CASE 'M ((MEIN DEIN SEIN) #T) (ELSE "Nicht d a b e i ! " ) ) ==> "Nicht dabei!" eval> (DO ( ( I 2 (- I 1))) ((BEGIN (WRITELN "Ja") (= 0 I ) ) ) ) ==> Ja Ja Ja #T eval> (MAP LIST '(A B C D)) ==> ((A) (B) (C) (D)) eval> (FOR-EACH WRITELN '("Gestern" "Heute" "Morgen")) ==> Gestern Heute Morgen #T ¡Rückgabewert von FOR-EACH

85

3. Rekursion als Problemlösungsmethode

3. Rekursion als Problemlösungsmethode Nimmt eine Definition auf sich selbst Bezug, dann liegt direkte Rekursion vor. Ist der Selbstbezug über eine Aufruffolge von Definitionen gegeben, dann spricht man von indirekter oder gegenseitiger Rekursion. Problemlösungen über rekursive Definitionen sind für LISP-Programme kennzeichnend. Schon bei der Definition von Syntax und Semantik zeigte sich die Einfachheit und Kürze rekursiver Definitionen. Eines der oft erwähnten Beispiele für die Erläuterung der Rekursion ist die Definition der Funktion LAENGE (vgl. z.B. Tanimoto, 1987, S.27ff oder Müller, 1985, S.63ff). Sie bestimmt die Länge einer Liste, d.h. die Anzahl der Elemente. eval>

eval> eval>

( D E F I N E LAENGE (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) 0 ) (T ( + 1 (LAENGE (CDR L I S T E ) ) ) ) ) ) (LAENGE (LAENGE

==>

' ( M E Y E R - A G MUELLER-OHG SCHULZE-GMBH)) ' ( ( A ) (B (C ( D ) ) ) ) = = > 2

P r o g r a m m f r a g m e n t 3 - 1 . Beispiel einer rekursiven - B e s t i m m u n g der Länge e i n e r L i s t e , d . h . Anzahl i h r e r Elemente -

LAENGE ==> 3

Definition E r m i t t l u n g der

Das JV£/ZZ?-Prädikat im LAENGE-Konstrukt (Programmfragment 3-1) stellt fest, ob eine leere Liste vorliegt. Es entspricht folgender Definition: eval>

(DEFINE

NULL?

(LAMBDA

(X)

(EQ?

X NIL)))

==>

NULL?

Die rekursive Methode ist immer dann nützlich, wenn eine Familie "verwandter" Probleme vorliegt, von denen eines so einfach ist, daß sich die Lösung direkt angeben läßt. Diesen trivialen Fall bezeichnet Douglas R. Hofstadter treffend mit „Embryonal-Fall" (vgl. Hofstadter, 1983), da sich die Lösung des Gesamtproblems aus ihm „entwickelt". So läßt sich die Länge der leeren Liste sofort mit 0 angeben. Die Länge einer Liste mit z.B. 3 Elementen ist zurückführbar auf das einfachere Problem der Ermittlung der Länge einer Liste mit 2 Elementen, da die 3-elementige Liste um 1 Element länger ist. Generell stellen sich die beiden Kernprobleme für das Auffinden einer ,Jtekursions-Route":

86

I. Konstrukte (Bausteine zum Programmieren)

o Definition von trivialen Lösungen und o Definition der Verknüpfung von Lösungsschritten. 1. Definition von trivialen Lösungen. Welches ist der einfache Fall (bzw. welches sind die einfachen Fälle), bei dem (denen) die Lösung(en) direkt angebbar ist (sind)? o Woran erkennt man einen einfachen Fall? o Wie lautet die zugehörende Lösung? 2. Definition der Verknüpfung von Lösungsschritten. Welches ist die Verknüpfung zwischen einem typischen und dem nächst einfacheren Fall? o Wie kommt man von einem typischen Fall genau einen Schritt näher zu einem einfachen Fall? o Wie konstruiert man die Lösung für den vorliegenden Fall aus der „unterstellten" Lösung für den einfacheren Fall? Beim Problem LAENGE lauten die Antworten auf diese Fragen: o Der einfache Fall ist gegeben, wenn die Liste leer ist, d.h. das NULL?-Konstruk liefert den Wert #T. o Bei der leeren Liste ist die Länge gleich 0. o Verkürze die gegebene Liste um ein Element, o Addiere 1 zur Lösung der um ein Element verkürzten Liste. Entscheidend ist, daß die Rekursions-Route tatsächlich zu trivialen Fällen führt. Anhand eines rekursiven Akronyms soll der umgekehrte, in der Regel der ungewollte Weg aufgezeigt werden. Man nehme zum Spaß an, LAMBDA stehe für folgendes rekursive Akronym: LAMBDA ::= ¿AMBDA Auch Mein ßestens Definierbares Arbeitsmittel Ersetzt man das LAMBDA der rechten Seite durch die Definition, dann erhält man nach zweimaliger Anwendung folgendes Ungetüm: (((LAMBDA Auch Mein Bestens Definierbares Arbeitsmittel) Auch Mein Bestens Definerbares Arbeitsmittel) Auch Mein Bestens Definierbares Arbeitsmittel). Bei der Anwendung einer rekursiven Definition sind zunächst Zwischenlösungen für die Teilprobleme zu vermerken, bis eine triviale Lösung erreicht wird. Von dieser können dann „rückwärts" die vermerkten Teilprobleme gelöst werden. Im allgemeinen Rekursionsfall wächst daher der Speicherbedarf mit der Zahl der erforderlichen Zwischenschritte. Die Anzahl der geschachtelten Aufrufe wird als Rekursionstiefe der Funktion bezeichnet. Im speziellen Fall der sogenannten Restrekursion (engl.: tail-recursion) ist der Speicherbedarf konstant. Er ist unabhängig vom Wert des Argumentes und damit unabhängig von der Rekursionstiefe. Bei der Restrekursion ist das Teilproblem-Ergebnis der größten Rekursionstiefe das Problem-Gesamtergebnis (vgl. Hinweis Restrekursion).

87

3. Rekursion als Problemlösungsmethode

Effiziente LISP-Systeme erkennen, ob eine Definition eine Restrekursion darstellt, und arbeiten diese dann mit konstantem Speicherbedarf entsprechend einer Iteration ab. Für Scheme-Systeme wird diese Eigenschaft gefordert (vgl. Rees/Clinger, 1986). Der Benutzer kann sich somit auf rekursive Formulierungen konzentrieren, ohne ein unnötiges Wachsen des Speicherbedarfs im Restrekursions-Fall in Kauf nehmen zu müssen. Ein Beispiel ist die Funktion SÄGEZAHN (vgl. Programmfragment 3-3). /Hinweis: Restrekursion. Das obige LAENGE-Konstrukt (vgl. Programmfragment 3-1) ist nicht restrekursiv definiert. Mit Hilfe eines Akkumulators ist eine restrekursive Lösung leicht definierbar, wie das folgende Beispiel zeigt: eval> eval>

eval>

( D E F I N E RR-LAENGE (LAMBDA ( L I S T E ) (ZAEHLUNG L I S T E 0 ) ) ) = = > RR-LAENGE ( D E F I N E ZAEHLUNG (LAMBDA ( L AKKUMULATOR) (COND ( ( N U L L ? L ) A K K U M U L A T O R ) (T ( Z A E H L U N G ( C D R L) ( + 1 A K K U M U L A T O R ) ) ) ) ) ) (RR-LAENGE ' ( A B C D)) ==> 4

]

Zum Verständnis einer rekursiven Definition ist es hilfreich, die Aufruffolgen mit ihren Argumenten und den zugehörenden Rückgabewert darzustellen. Das 77?AC£-Konstrukt ermöglicht eine entsprechende Ausgabe bei der Applikation. (PC SCHEME: TRACE-ENTRY protokolliert jeden Aufruf und TRACE-EXIT jedes Verlassen der Funktion. TRACE-BOTH dokumentiert beides; den Aufruf und das Verlassen der Funktion.7 Bezogen auf das LAENGE-Konstrukt erhält man folgende Unterstützung: e v a l > ( T R A C E - B O T H L A E N G E ) = = > OK e v a l > (LAENGE ' ( M E Y E R - A G MUELLER-OHG SCHULZE-GMBH)) A r g u m e n t 1: (MEYER-AG MUELLER-OHG SCHULZE-GMBH) > > > E n t e r i n g # A r g u m e n t 1: (MUELLER-OHG SCHULZE-GMBH) >>> E n t e r i n g #

88

I. Konstrukte (Bausteine zum Programmieren)

> > > E n t e r i n g # < P R O C E D U R E LAENGE> A r g u m e n t 1: ( ) < < < L e a v i n g # < P R O C E D U R E LAENGE> w i t h v a l u e 0 A r g u m e n t 1: ( ) < < < L e a v i n g # < P R O C E D U R E LAENGE> w i t h v a l u e 1 A r g u m e n t 1: ( S C H U L Z E - G M B H ) < < < L e a v i n g # < P R O C E D U R E LAENGE> w i t h v a l u e 2 Argument 1: (MUELLER-OHG SCHULZE-GMBH) < < < L e a v i n g # < P R O C E D U R E LAENGE> w i t h v a l u e 3 A r g u m e n t 1: ( M E Y E R - A G M U E L L E R - O H G S C H U L Z E - G M B H ) Programmfragment

3-2.

Protokoll der Funktionsaufrufe beim Anwenden des rekursiv definierten LAENGEKonstruktes ( v g l . Programmfragment 3 - 1 )

Rekursive Definitionen sind in der Mathematik gebräuchlich. Man notiert z.B. eine Sägezahn-Kurve wie folgt: 0 X SÄGEZAHN

falls falls

0

(DEFINE

BGB-§7-l

'(WER

SICH

AN

EINEM

ORTE

STÄNDIG

N I E D E R L Ä ß T B E G R Ü N D E T AN D I E S E M ORTE S E I N E N W O H N S I T Z ) ) ==> BGB-§7-l eval> eval>

(WORTZÄHLUNG (WORTZÄHLUNG

' O R T E B G B - § 7 - l ) ==> 'WOHNSITZ B G B - § 7 - l )

2 ==>

1

Entsprechend der rekursiven Problemlösungs-Methode suchen wir zunächst triviale Fälle und ihre Lösungen. Danach ermitteln wir eine Konstruktionsvorschrift, die das Problem in Richtung auf trivale Fälle vereinfacht (Bestimmung der Rekursions-Route). Der triviale Fall ist gegeben, wenn der Text kein Wort enthält. Dann ist die

90

I. Konstrukte (Bausteine zum Programmieren)

Lösung der Wert 0. Enthält der Text Wörter, dann soll zunächst das erste Wort des Textes mit dem vorgegebenen Wort verglichen werden. Liegt Übereinstimmung vor, dann ist die gesuchte Lösung um 1 größer als das Ergebnis der Wortzählung im Resttext. (Die bisherige Definition entspricht dem erläuterten LAENGE-Konstrukt). Ist keine Übereinstimmung gegeben, dann ist die gesuchte Lösung gleich dem Ergebnis der Wortzählung im Resttext. Der Resttext ist die Restliste, d.h. der Text ohne das erste Wort.

WORTZÄHLUNG

[

]

Enthält kein W o r t ? _

True

— E r s t e WORTZÄHLUNG :- 0

True

W o r t in = ?

W O R T Z Ä H L U N G := 1 + W O R T Z Ä H L U N G [ ] RETURN

Legende:

WORTZÄHLUNG

WORTZÄHLUNG

False

W O R T Z Ä H L U N G [ ]

WORTZÄHLUNG

:= 0::= Der Wert der Funktion ist 0.

[ ]

RETURN W O R T Z Ä H L U N G Bild 3.1-1.

False

::= Die Funktion w i r d angewendet auf die A r g u m e n t e und . ::= Der F u n k t i o n s w e r t wird gegeben .

aus-

Graphisehe Darstel1ung einer Rekursion als Struktogramm (Notation vgl. N a s s i / S h n e i d e r m a n , 1973)

Das Bild 3.1-1 stellt diese Lösung als Struktogramm dar. Die Beschriftung orientiert sich dabei an der von I. Nassi und B. Shneiderman für ihr Fakultätsbeispiel gewählten Notation (vgl. Nassi/Shneiderman, 1973). Diese Notation läßt allerdings die wesentliche Unterscheidung zwischen der Applikation einer Funktion und der Darstellung des Funktionswertes nur schwer erkennen, wie auch die Legende in Bild 3.1-1 zeigt. Entsprechend dem Struktogramm ist die Funktion, wie folgt, zu definieren:

3. Rekursion als Rroblemlösungsmethode eval>

91

(DEFINE W O R T Z Ä H L U N G (LAMBDA (WORT TEXT) (COND ((NULL? TEXT) 0) ((EQ? WORT (CAR TEXT)) (+ 1 (WORTZÄHLUNG WORT (CDR TEXT)))) (T (WORTZÄHLUNG WORT (CDR T E X T ) ) ) ) ) ) ==> W O R T Z Ä H L U N G

Die Lösung kann auch durch eine Entscheidungstabelle (ET) mit einer Aktion „Wiederhole ET" dargestellt werden (Bild 3.1-2). Eine solche Notation verdeutlicht jedoch nicht die rekursive Definition. Diese zeigt Bild 3.1-3. Hier ist allerdings wie beim Struktogramm das Problem, die Applikation der Funktion vom Rückgabewert der Funktion deutlich zu unterscheiden. AKKUMULATOR

:= 0

ET-Wortzählung

R1 R2 R3

B1

Enthält kein

B2

Erste Wort im =

AI

AKKUMULATOR

A2

< t e x t > : = reduziert um erstes

A3

Wiederhole

A4

Gebe A K K U M U L A T O R

:= 1 +

Wort? ?

J

N

N

-

J

N

X

AKKUMULATOR Wort

ET-Wortzählung

X

X

X

X

aus

Legende: ET-Symbole vgl. A b s c h n i t t

X

2.2.2

Bild 3 . 1 - 2 . I t e r a t i o n dargestellt in - Funktion W O R T Z Ä H L U N G -

ET-Technik

Daß die Iteration als Sonderfall von selbstbezüglichen Funktionen betrachtbar ist, verdeutlicht die Rekursion mit der Funktionsanwendung auf die Restliste. Wir können mit dieser Notation die Konstrukte für das klassische „Mapping" (vgl. Abschnitt 2.2.3) definieren. eval>

;; Das e i n g e b a u t e F O R - E A C H - K o n s t r u k t hat den Wert „wahr" (DEFINE FOR-EACH (LAMBDA (FUNKTION LISTE) (COND ((NULL? LISTE) T) (T (FUNKTION (CAR LISTE)) (FOR-EACH FUNKTION (CDR LISTE)))))) ==> FOR-EACH

eval>

(FOR-EACH W R I T E L N '("Zeile 1" "Zeile 2" "Zeile 3") ==> Zeile 1 Zeile 2 Zeile 3 #T ¡Rückgabewert von FOR-EACH

92

I. Konstrukte (Bausteine zum Programmieren)

ET-Wortzählung

[

R2

R3

Wort?

J

N

N

-

J

N

Enthält

B2

Erste Wort

AI

Wortzählung

A2

W o r t z ä h l u n g := 1 + CALL ET-Wortzählung [ ]

A4

CALL

kein

R1

B1

A3

]

im < t e x t > = := 0

X

ET-Wortzählung ]

Wortzählung

X

Legende: E T - S y m b o l e vgl. A b s c h n i t t C A L L ::= V e r w e i s t a u f d i e

2.2.2 Funktionsapplikation

B i l d 3 . 1 - 3 . R e k u r s i o n dargestellt in - Funktion WORTZÄHLUNG -

X

X

ET-Technik

Das MAP-Konstrukt sammelt die Ergebnisse der Funktionsanwendung und gibt sie als Liste zurück. Dieses Sammeln bilden wir mit dem CONS-Konstrukt ab. eval>

eval>

(DEFINE MAP (LAMBDA (FUNKTION LISTE) (COND ((NULL? LISTE) NIL) (T ( C O N S ( F U N K T I O N ( C A R L I S T E ) ) (MAP FUNKTION (CDR L I S T E ) ) ) ) ) ) ) ==> (MAP W R I T E L N ' ( " Z e i l e 1" " Z e i l e 2" " Z e i l e 3 " ) = = > Zeile 1 Zeile 2 Zeile 3 (() () ()) ; R ü c k g a b e w e r t v o n M A P

eval>

(MAP

(LAMBDA

(X)

(* X X))

'(1 2 3 4 ) ) = = >

(14

9

MAP

16)

Die rekursive Definition des MAP-Konstruktes zeigt eine häufig vorkommende Lösungsstruktur. Salopp „LISP-isch" formuliert: "CDRing down the list and CONSing up the answer".

3.2 Verknüpfung von Teilproblem-Lösungen Beispiel: Änderung eines Textes, der als Baumstruktur vorliegt Wir definieren eine Funktion ERSETZUNG, die in einem Text ein bestimmtes

3. Rekursion als Problemlösungsmethode

93

Wort durch ein neues Wort ersetzt. Der Text ist als Baum strukturiert, d.h. als eine Liste, die wiederum Listen als Elemente enthält. Als Textbeispiel dient wie in Abschnit 3.1 wieder §7 BGB. eval > ( D E F I N E BGB-§7 ' ( ( ( 1 ) WER S I C H AN EINEM ORTE STÄNDIG N I E D E R L Ä ß T BEGRÜNDET AN D I E S E M ORTE S E I N E N WOHNPLATZ) ( ( 2 ) DER WOHNPLATZ KANN G L E I C H Z E I T I G AN MEHREREN ORTEN BESTEHEN) ( ( 3 ) DER WOHNPLATZ WIRD AUFGEHOBEN WENN D I E N I E D E R L A S S U N G MIT DEM WILLEN AUFGEHOBEN WIRD S I E A U F Z U G E B E N ) ) ) = = > B G B - § 7

In diesem Text ist das Wort WOHNPLATZ falsch; es ist durch WOHNSITZ zu ersetzen. Für das zu definierende Konstrukt wird offensichtlich eine Schnittstelle mit drei Parametern benötigt, die wir, wie folgt, benennen: OBJEKT ERSATZ STRUKTUR

: a a s zu e r s e t z e n d e : : = d a s neue Wort : : = der s t r u k t u r i e r t e

alte

Text,

Wort

i n dem e r s e t z t

wird

Entsprechend der rekursiven Problemlösungs-Methode suchen wir zunächst nach trivialen Fällen, bei denen die Lösung bekannt und damit direkt angebbar ist. Wenn die STRUKTUR nur aus dem zu ersetzenden Wort O B J E K T besteht, dann ist die Lösung bekannt und wir brauchen nur ERSATZ zurückzugeben. Ist die STRUKTUR keine Liste, sondern ein elementares Element z.B. eine Zahl oder ein Symbol und ist dieses Element nicht gleich dem OBJEKT, dann ist die STRUKTUR selbst die Lösung. Anders formuliert: Ist die STRUKTUR ein Atom (vgl. Tab. 2.2-1) und trifft nicht der erste triviale Fall zu, dann ist die STRUKTUR der Rückgabewert. Betrachten wir das erste Element von STRUKTUR, dann kann es ein Atom oder wieder eine Liste sein. Für den Fall eines Atomes haben wir die Lösung mit der Definition der trivialen Fälle abgedeckt. Für eine Liste ist der Ersetzungsvorgang sowohl für ihr erstes Element als auch für ihre Restliste zu vollziehen. Das Ergebnis ist die Verknüpfung der Ersetzung im ersten Element mit der Ersetzung in der Restliste. Diese Verknüpfung ist mit dem Konstruktor CONS abbildbar. Wir können die Ersetzung in einer komplexen STRUKTUR auf zwei einfachere Probleme zurückführen: 1. auf die Ersetzung im ersten Element von STRUKTUR und 2. auf die Ersetzung im Rest von STRUKTUR. Die Gesamtlösung ist die Verknüpfung der beiden Teillösungen. Wir definieren daher:

94

I. Konstrukte (Bausteine zum Programmieren)

eval>

(DEFINE ERSETZUNG (LAMBDA (OBJEKT ERSATZ STRUKTUR) (COND ((EQ? OBJEKT STRUKTUR) ERSATZ) ((ATOM? STRUKTUR) STRUKTUR) (T (CONS (ERSETZUNG OBJEKT ERSATZ (CAR STRUKTUR)) (ERSETZUNG OBJEKT ERSATZ (CDR S T R U K T U R ) ) ) ) ) ) ) ==> ERSETZUNG eval> (ERSETZUNG 'WOHNPLATZ 'WOHNSITZ BGB-§7) = = > (((1) WER SICH AN EINEM ORTE S T Ä N D I G N I E D E R L Ä ß T B E G R Ü N D E T AN DIESEM O R T E SEINEN W O H N S I T Z ) ...

3.3 Rekursive Konstrukte für rekursive Strukturen Die Rekursion ist die „geborene" Lösungsmethode für die Handhabung von Baumstrukturen. Im allgemeinen sind Operationen, die sich auf einen Baum beziehen, wiederum auf dessen Teilbäume anzuwenden und zwar so lange, bis die Baumblätter erreicht werden. Anhand eines simplen Beratungssystems wird deutlich, daß die rekursive Lösungsmethode zweckmäßig für Baumstrukturen ist. Beispiel: Ja-Nein-Navigation

entlang einer

Baumstruktur

Zu definieren sei ein Beratungssystem, das auf seine Fragen als Dialogeingabe „Ja" oder „Nein" erwartet. Die Fragen sind hierarchisch strukturiert, wie die folgende Skizze (Bild 3.2-1) zeigt. Das Beratungssystem basiert auf der rekursiven Struktur: ( () ()) Der und der haben beide wieder diese Struktur. Beim Abarbeiten des Baumes wird eine Systemantwort erkannt, wenn der Baum nur noch aus einem Element besteht; d.h. (LENGTH ) ist gleich 1. [Hinweis: LENGTH ist eine eingebaute LISPFunktion. Sie entspricht dem Konstrukt LAENGE, vgl. Abschnitt 3] Das Durchlaufen dieses Binärbaums steuert die rekursive Funktion NAVIGATION. Nach der rekursiven Problemlösungsmethode konstruieren wir die Lösung auf der Basis trivialer Fälle. Hier ist der triviale Fall gegeben, wenn der Binärbaum so weit abgearbeitet ist, daß ein Baumblatt erreicht ist, d.h. eine Systemantwort auszugeben ist. Wir definieren daher ein Prädikat, das feststellt, ob ein Baumblatt gegeben ist. eval>

(DEFINE BAUMBLATT?

(LAMBDA

(BAUM) (EQ? 1 (LENGTH ==>

BAUM)))) BAUMBLATT?

Abhängig von der Dialogantwort ist entweder im oder im cneinbaum> das „Navigieren" fortzusetzen. Wäre der weiter zu verfolgen, dann ist ein Selektor erforderlich, der aus dem ursprünglichen Baum den ermittelt. Bezogen auf die Liste, die den Baum abbildet, ist das zweite Element zu selektieren. Für den ist ein analoger Selektor erfor-

3. Rekursion als Problemlösungsmethode

Ja

Ja

--

5ild

3.2-1.

... ... Skizze

Nei n

--

Ja

Mei n

~lo Ja

... — < F 3 > — Legende: FO, Fl. F2 SO, Sl, S2

95

Nein --

Nei n --

l2

:= :=

--

Fragen Systemantworten eines

Binärbaumes

derlich. Wir definieren eine Funktion VERZWEIGUNG, die abhängig von der Dialogantwort die entsprechende Selektorfunktion ermittelt. Der Wert der Funktion VERZWEIGUNG ist selbst eine Funktion, nämlich die jeweilige Selektorfunktion. VERZWEIGUNG ist daher eine Funktion höherer Ordnung. eval>

( D E F I N E V E R Z W E I G U N G ( L A M B D A () (COND ( ( Z U S T I M M U N G ? ) S E L E K T O R - J A - B A U M ) (T S E L E K T O R - N E I N - B A U M ) ) ) ) = = > V E R Z W E I G U N G

Die Funktion ZUSTIMMUNG? wurde in Abschnitt 2.2.2 mit dem CASE-Konstrukt definiert. Entsprechend der gewählten Datenstruktur für die Abbildung des Binärbaums sind die Selektorfunktionen, wie folgt, zu definieren: eval> eval>

(DEFINE

SELEKTOR-JA-BAUM (LAMBDA (BAUM) (CAR (CDR B A U M ) ) ) ) = = > S E L E K T O R - J A - B A U M (DEFINE S E L E K T O R - N E I N - B A U M (LAMBDA (BAUM) (CAR(CDR(CDR BAUM)))))==> SELEKTOR-NEIN-BAUM

Mit VERZWEIGUNG als Funktion höherer Ordnung ist das Konstrukt NAVIGATION, wie folgt, formulierbar: eval>

(DEFINE NAVIGATION (LAMBDA (BAUM) (COND ( ( B A U M B L A T T ? B A U M ) ( S Y S T E M - A N T W O R T B A U M ) ) (T ( S Y S T E M - F R A G E B A U M ) (NAVIGATION ((VERZWEIGUNG) BAUM)))))) ==> NAVIGATION

Die Konstrukte SYSTEM-FRAGE und SYSTEM-ANTWORT definieren wir mit den entsprechenden Selektoren, wie folgt: eval>

(DEFINE

SELEKTOR-FRAGE

(LAMBDA

(BAUM)

(CAR B A U M ) ) ) ==> SELEKTOR-FRAGE

96

I. Konstrukte (Bausteine zum Programmieren)

eval>

(DEFINE S Y S T E M - F R A G E (LAMBDA (BAUM) (WRITELN ( S E L E K T O R - F R A G E BAUM)))) ==>

eva1>

(DEFINE S E L E K T O R - B A U M B L A T T

eval>

(DEFINE S Y S T E M - A N T W O R T (LAMBDA (WRITELN " S y s t e m a n t w o r t : " ( S E L E K T O R - B A U M B L A T T BAUM)))) ===>

SYSTEM-FRAGE

(LAMBDA

(BAUM) (CAR BAUM))) ==> S E L E K T O R - B A U M B L A T T (BAUM) SYSTEM-ANTWORT

In der Definition von NAVIGATION führt die Applikation von (VERZWEIGUNG) zur Rückgabe der anzuwendenden Selektorfunktion, so daß ((VERZWEIGUNG) BAUM) den zutreffenden „Restbaum" ermittelt. Wir wenden auf diesen „Restbaum" die Funktion NAVIGATION erneut an. Diese Vorgehensweise entspricht der schon erörterten Technik der Funktionsanwendung auf die Restliste (vgl. Abschnitt 3.1). Der Unterschied besteht in der Ermittlung des „Restes"; d.h. des einfacheren Problems im Sinne der Rekursionsroute. Der skizzierte Lösungsansatz führt zu einer Anzahl von Funktionen, die im Zusammenhang mit dem BERATUNGSSYSTEM-Konstrukt zu definieren sind. Außerhalb dieses Konstruktes sind diese Funktionen nicht erforderlich. Mit Hilfe einer LET-Formulierung (vgl. Abschnitt 2.2.1) können die Funktionen wie SELEKTOR-JA-BAUM, SELEKTOR-NEIN-BAUM, BAUMBLATT?, VERZWEIGUNG, NAVIGATION etc. als lokale Variablen definiert werden. Wir vermeiden damit, daß diese Konstrukte auf der LISP-System-Ebene („top level") bekannt sind und z.B. versehentlich durch namensgleiche Symbole überschrieben werden. Mit der LET-Notation sind ihre Wert-Assoziationen nicht in der USER-INITIAL-ENVIRONMENT eingetragen, sondern in der Umgebung, die durch die LET-Konstruktion aufgebaut wird. Da das Konstrukt NAVIGATION rekursiv ist, wird das LETREC-Konstrukt benötigt. Das Programmfragment 3.3-1 zeigt die Lösung mit dem LETREC-Konstrukt. Sie weist darüber hinaus einige kleine Verbesserungen auf z.B. steht statt der Schachtelung von (CAR (CDR (CDR BAUM))) der Selektor (LIST-REF BAUM 2). Um den Rückgabewert NIL des WRITELN-Konstruktes nicht als letzten Wert auf dem Bildschirm zu zeigen, ist die Variable *THE-NON-PRINTING-OBJECT* (ein vordefiniertes Scheme-Symbol) als letztes Element der Sequenz genannt. /Hinweis: LIST-REF-Konstrukt. In Scheme ermittel das LIST-REF-Konstrukt das n-te Element einer Liste, wobei n < j a - b a u m > < n e i n - b a u m > ) eval> (DEFINE BERATUNGSSYSTEM (LAMBDA (BAUM) (LETREC ( ( S E L E K T O R - J A - B A U M ( L A M B D A (X) ( L I S T - R E F X 1 ) ) ) ( S E L E K T O R - N E I N - B A U M ( L A M B D A (X) ( L I S T - R E F X 2 ) ) ) ( S E L E K T O R - F R A G E ( L A M B D A (X) ( C A R X ) ) ) ( S E L E K T O R - B A U M B L A T T ( L A M B D A (X) ( C A R X ) ) ) ( B A U M B L A T T ? ( L A M B D A (X) (EQ? 1 ( L E N G T H X ) ) ) ) ( Z U S T I M M U N G ? ( L A M B D A () (CASE (READ) ((J JA Y Y E S T) # T ) ( E L S E N I L ) ) ) ) ( V E R Z W E I G U N G ( L A M B D A () (COND ((ZUSTIMMUNG?) S E L E K T O R - J A - B A U M ) (T S E L E K T O R - N E I N - B A U M ) ) ) ) ( S Y S T E M - F R A G E ( L A M B D A (X) (WRITELN (SELEKTOR-FRAGE X)))) ( S Y S T E M - A N T W O R T ( L A M B D A (X) (WRITELN "Systemantwort: " (SELEKTOR-BAUMBLATT X)))) ( N A V I G A T I O N ( L A M B D A (X) ( C O N D ( ( B A U M B L A T T ? X) ( S Y S T E M - A N T W O R T X)) (T ( S Y S T E M - F R A G E X) (NAVIGATION ((VERZWEIGUNG) X))))))) (NAVIGATION BAUM) *THE-NON-PRINTING-OBJECT*))) ==> BERATUNGSSYSTEM eval>

eval>

(DEFINE FAHRRAD-ANALYSE ' ( " H a t es e i n e n D i a m a n t r a h m e n ? " ( " I s t d e r R a h m e n h o e h e r a l s 56 c m ? " ( " U n g e e i g n e t fuer ein Kind!") ("Notfalls fuer ein Kind v e r w e n d b a r ! " ) ) ( " B e d i n g t g e e i g n e t - nur fuer kleine P e r s o n e n ! " ) ) ) ==> FAHRRAD-ANALYSE (BERATUNGSSYSTEM FAHRRAD-ANALYSE) ==> H a t es e i n e n D i a m a n t r a h m e n ? n ¡Benutzereingabe B e d i n g t g e e i g n e t - nur fuer kleine P e r s o n e n !

Programmfragment

3.3-1.

Beratungssystem konstruiert einem Binärbaum

mit

3.4 Geschachtelte Rekursion Beispiel: Umdrehen der Reihenfolge der Elemente in einer Liste Die Funktion REVERSE hat als Wert eine Liste, die die Elemente ihres Argumentes in umgekehrter Reihenfolge aufweist.

I. Konstrukte (Bausteine zum Programmieren)

98 eval > (REVERSE

'(A

(B C)

D))

==>

(D

(B C)

A)

Wir definieren diese Funktion selbst, und zwar rekursiv. Um die eingebaute Funktion nicht zu überschreiben, nennen wir unsere Funktion UMDREHEN. Im Sinne der rekursiven Problemsicht halten wir zunächst fest, daß das Konstrukt UMDREHEN bei einer Liste ohne Elemente den Wert NIL hat. Dann unterstellen wir, daß für eine Liste eine Teillösung vorliegt, nämlich ihre umgedrehte Restliste. Mit dieser Teillösung ist dann die gesamte Problemlösung bekannt. Sie ist gleich das erste Element der Liste, an das Ende der unterstellten Teillösung angehängt. Benötigt wird eine Funktion, die ein solches „Anhängen" an eine Liste ermöglicht. Das entsprechende Konstrukt heißt APPEND (deutsch: hinzufügen). Es verknüpft zwei Listen miteinander. eval>

(APPEND

'(1

2 3)

'(4

5))

==>

(12

3 4

5)

Damit ist die Definition von UMDREHEN, wie folgt, formulierbar: eval>

(DEFINE

UMDREHEN (LAMBDA ( X ) (COND ( ( N U L L ? X) N I L ) (T (APPEND (UMDERHEN (CDR X ) ) ( L I S T (CAR X ) ) ) ) ) ) )

==>

UMDREHEN

Das APPEND-Konstrukt ist ebenfalls rekursiv definierbar. Wir nennen es (um ein Überschreiben zu vermeiden) ANHAENGEN. Ein trivialer Fall ist gegeben, wenn der Wert eines Argumentes von ANHAENGEN die leere Liste ist. Die zugehörige Lösung ist dann die jeweils andere Liste. Da mit dem CONS-Konstrukt ein Mittel vorhanden ist, an den Anfang einer Liste ein Element einzufügen, nehmen wir die Liste des zweiten Argumentes von ANHAENGEN als Ausgangsbasis und betrachten das Problem als Einfügen der Liste des ersten Argumentes. Wir unterstellen wieder die Existenz einer Teillösung, nämlich das Verknüpfen der Restliste des ersten Argumentes mit der Liste des zweiten Argumentes. Die Lösung für das Gesamtproblem ist dann das Einfügen des ersten Elementes der ersten Liste in diese Teillösung. Das Einfügen übernimmt das CONSKonstrukt. Damit ist ANHAENGEN, wie folgt, definierbar: eval>

( D E F I N E ANHAENGEN (LAMBDA ( L I S T E _ 1 L I S T E _ 2 ) (COND ( ( N U L L ? L I S T E . l ) LISTE_2) ( T (CONS (CAR L I S T E _ 1 ) (ANHAENGEN (CDR L I S T E _ 1 ) LISTE_2)))))) ==> A N H A E N G E N

Bisher wurde die Rekursion sowohl als eine iterativ geprägte Formulierung („tail recursion") als auch im Zusammenhang mit Verknüpfungsfunktionen wie CONS oder APPEND erörtert. Jetzt diskutieren wir die Rekursion im Hinblick auf geschachtelte Aufrufe (Komposition). Der rekursive Bezug wird als Argument eines schon rekursiven Funktionsaufrufes formuliert. Dazu dient wieder

3. Rekursion als Problemlösungsmethode

99

die Funktion UMDREHEN; jedoch wird diese nicht mehr mittels APPEND definiert. Der Lösungsansatz geht von folgenden Überlegungen aus: o Enhält die umzudrehende Liste kein Element, dann ist NIL die Lösung. o Enthält die umzudrehende Liste nur ein Element, dann ist sie selbst die Lösung. o Ist die Liste X, z.B. ( A B C D), umzudrehen, dann ist das letzte Element der Liste, hier D, an den Anfang einer Liste zu setzen, deren weitere Elemente die umgedrehte Restliste, hier das Umdrehen von (A B C), ist. o Das letzte Element einer Liste X ist selektierbar durch e v a l > (CAR

(UMDREHEN

(CDR

X))

==>

D ¡letzte

Element

von X

o Gesucht ist die rekursive Definition, welche die Liste X ohne ihr letztes Element, hier D, als Wert hat. Die folgende rekursive Definition in LISP-Notation verdeutlicht den skizzierten Lösungsansatz:

(UMDREHEN

X)

NIL

falls

(NULL?

X)

X

falls

(NULL?

(CDR

::= - I -

X))

- (CONS < 1 e t z t e - e l e m e n t > falls sonst (UMDREHEN ))

Die Formulierung für den Sonst-Fall erarbeiten wir durch folgende Schritte, wobei für die Erläuterung angenommen wird, daß X an die Liste (A B C D) gebunden ist: 1. Restliste umdrehen: eval>

(UMDREHEN

(CDR

X))

==>

(D C B )

2. Von Schritt 1 das erste Element entfernen: eval>

(CDR

(UMDREHEN

(CDR

X)))

==>

(C

B)

3. Ergebnis von Schritt 2 umdrehen eval>

(UMDREHEN

(CDR

(UMDREHEN

(CDR

X))))

==>

(B

C)

4. Erste Element von X, also A, in Ergebnis von Schritt 3 einfügen: eval>

(CONS

(CAR

X)

(UMDREHEN

(CDR

(UMDREHEN

(CDR

X)))))

==>

(ABC)

Die Definition von Schritt 4 ermittelt aus der Liste (A B C D) die Liste (A B C).

100

I. Konstrukte (Bausteine zum Programmieren)

Damit ist entsprechend dem skizzierten Lösungsansatzes das UMDREHENKonstrukt, wie folgt, notierbar: eval>

(DEFINE (COND

UMDREHEN (LAMBDA ( X ) ( ( N U L L ? X) N I L ) ( ( N U L L ? (CDR X ) ) X) (T (CONS (CAR (UMDREHEN (CDR X ) ) ) (UMDREHEN (CONS (CAR X) (UMDREHEN (CDR (UMDREHEN (CDR

X))))))))))) ==> UMDREHEN

Diese Definition des Konstruktes UMDREHEN zeigt geschachtelte rekursive Bezüge. Nur durch Rekursion und Abstützung auf die Basiskonstrukte LAMBDA, COND, CONS, CAR, CDR und NULL? ist das Umdrehen abgebildet. Mit Hilfe der Rekursion lassen sich viele eingebaute Konstrukte auf wenige Basiskonstrukte, d.h auf das sogenannte „Pure LISP", zurückführen (vgl. z.B. das DO-Konstrukt in Abschnitt 2.2.3).

3.5 Zusammenfassung: Rekursion Nimmt eine Definition auf sich selbst Bezug, so bezeichnet man diesen Selbstbezug als Rekursion. Die Lösung eines Problems durch Rekursion ist angebracht, wenn das Problem als eine Familie „verwandter" Probleme betrachtbar ist, von denen eines (oder mehrere) so einfach ist (sind), daß sich die Lösung(en) direkt angeben läßt (lassen). Neben diesen trivialen Lösungen ist eine RekursionsRoute zu definieren, so daß die Lösung für das gesamte Problem nach endlich vielen Schritten auf die trivialen Lösungen zurückgeführt wird. Dabei geht es um die Kernfrage: Wie kommt man von einem typischen Fall genau einen Schritt näher zu einem trivialen Fall und wie konstruiert man für den typischen Fall die Lösung aus der „unterstellten" Lösung für den nächst einfacheren Fall? Besteht das Problem in der Abarbeitung einer Liste, z.B. im Prüfen, ob jedes Element ein bestimmtes Prädikat erfüllt, dann ist die Lösung als eine rekursive Anwendung der Funktion auf die Restliste definierbar. Ein Beispiel ist das MEMBER?-Konstrukt, das feststellt, ob ein vorgegebenes Element in einer Liste enthalten ist (vgl. charakteristische Beispiele). Ist eine Baumstruktur gegeben, d.h. eine Liste, die wieder selbst Listen als Elemente enthält, dann sind die rekursiv definierten Teilproblem-Lösungen mit-

101

3. Rekursion als Problemlösungsmethode

einander zu verknüpfen. Dafür bieten sich die Konstruktoren CONS und APPEND an. Ein Beispiel ist das folgende Konstrukt TURN. Es kehrt die Reihenfolge der Elemente einer Liste um, wobei auch die Elemente in den Sublisten umgedreht werden. eval>

eval>

( O E F I N E TURN (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) N I L ) ((ATOM? (CAR L I S T E ) ) (APPEND (TURN (CDR

(TURN

LISTE)) (CONS (CAR L I S T E ) N I L ) ) ) (T (APPEND (TURN (CDR L I S T E ) ) (CONS (TURN (CAR L I S T E ) ) N I L ) ) ) ) ) ) = = > TURN ' ( A B (C D) E ) ) = = > (E (D C) B A)

Die Definition lokaler rekursiver Funktionen ermöglicht das LETREC-Konstrukt. Im Gegensatz zum LET-Konstrukt, das erst die Werte für alle lokalen Variable ermittelt und dann mit diesen in der Umgebung eine Assoziation aufbaut, vermerkt das LETREC-Konstrukt zunächst die lokale Variable in der Umgebung und ermittelt dann ihren Wert. eval>

(LETREC ((F00

(F00

Charakteristische

(LAMBDA ( L ) (COND ( ( N U L L ? L) N I L ) ( ( = (CAR L) 0) (CONS " U n d e f i n i e r t ! " (T (CONS (/ 1 (CAR L ) ) ( F 0 0 (CDR L ) ) ) ) ) ) ) ) ' ( 2 0 4 0 1 ) ) ) ==> (0.5 " U n d e f i n i e r t ! " 0.25

(F00

(CDR

L))))

"Undefiniert!"

1)

Beispiele für Abschnitt 3:

eval>

( D E F I N E MEMBER? (LAMBDA (ELEMENT L I S T E ) (COND ( ( N U L L ? L I S T E ) N I L ) ( ( E Q U A L ? ELEMENT (CAR L I S T E ) ) L I S T E ) (T (MEMBER? ELEMENT (CDR L I S T E ) ) ) ) ) ) = = > MEMBER? e v a l > (MEMBER? ' B (A B C D ) ) = = > (B C D) ;Wahr, da u n g l e i c h N I L e v a l > (MEMBER? ' ( A B) ' ( A B C D ) ) = = > ( ) e v a l > (MEMBER? ' ( A B) ' ( A (A B) ( C ) ) ) = = > ( ( A B) ( O ) eval>

( D E F I N E UNION-SEQUENZ (LAMBDA ( L I S T E _ 1 L I S T E _ 2 ) (COND ( ( N U L L ? L I S T E . l ) L I S T E _ 2 ) ( ( M E M B E R ? (CAR L I S T E _ 1 ) L I S T E _ 2 ) ( U N I O N - S E Q U E N Z (CDR L I S T E . l ) L I S T E _ 2 ) ) (T (CONS (CAR L I S T E _ 1 ) ( U N I O N - S E Q U E N Z (CDR L I S T E _ 1 ) L I S T E _ 2 ) ) ) ) ) ) = = > UNION-SEQUENZ ; R e k u r s i v e D e f i n i t i o n

eval>

(UNION-SEQUENZ

' ( A A B B C)

'(B

D D E)) ==>

(A A C B D D E)

102

I. Konstrukte (Bausteine zum Programmieren)

e v a l > ( D E F I N E UNION (LAMBDA ( L I S T E _ 1 L I S T E _ 2 ) (COND ( ( N U L L ? L I S T E . l ) L I S T E _ 2 ) ( ( M E M B E R ? (CAR L I S T E _ 1 ) L I S T E _ 2 ) (UNION (CDR L I ST E _ 1 ) L I S T E _ 2 ) ) (T (UNION (CDR L I S T E _ 1 ) (CONS (CAR L I S T E _ 1 ) L I S T E _ 2 ) ) ) ) ) ) = = > UNION ;Endrekursive Definition! e v a l > (UNION " ( A A B B C) ' ( B D D E ) ) = = > (C A B D D E) ¡ R e i h e n f o l g e !

In der endrekursiv-definierten UNION-Lösung arbeitet das Prädikat MEMBER? stets das erzeugte Zwischenresultat ab und nicht nur den Wert des ursprünglichen Argumentes LISTE_2. Die folgende Lösung UNION-MENGE verfügt über eine lokale rekursive Funktion mit einer Lambda-Variable AKKUMULATOR. Sie ist als eine Mengenoperation definiert, d.h. ihr Wert enthält keine doppelten Elemente und die Reihenfolge der Elemente ist unerheblich. eval>

eval> ;;;;

( D E F I N E UNION-MENGE (LAMBDA ( L I S T E _ 1 L I S T E . 2 ) (LETREC ( ( V E R E I N I G U N G (LAMBDA ( L _ l L_2 AKKUMULATOR) (COND ( ( A N D ( N U L L ? L _ l ) ( N U L L ? L _ 2 ) ) AKKUMULATOR) ( ( N U L L ? L_1) ( V E R E I N I G U N G L_2 L_1 AKKUMULATOR)) ( ( M E M B E R ? (CAR L _ l ) AKKUMULATOR) ( V E R E I N I G U N G (CDR L _ l ) L_2 AKKUMULATOR)) (T ( V E R E I N I G U N G (CDR L _ l ) L_2 (CONS (CAR L _ 1 ) A K K U M U L A T O R ) ) ) ) ) ) ) (VEREINIGUNG L I S T E _ 1 LISTE_2 N I L ) ) ) ) = = > UNION-MENGE (UNION-MENGE ' ( A A B B C) ' ( B D D E ) ) ) = = > (E D C B A) Expontential-Funktion r1 Xn

(DEFINE

g a n z a h l i g e Exponenten falls n = 0

(Xn/2)2

:= -

eval>

für

X *

(X

( l v l )

)

(BASIS

(LETREC

n

gradzahlig

falls

n

ungradzahlig

INTEGER.ZAHL)

((EVEN?

(LAMBDA

(SQUARE (EXPT

(N)

(LAMBDA

(LAMBDA

(X

(COND ( ( = O N) ((EVEN? (T (EXPT B A S I S (**

falls

Null

**

(LAMBDA

eval>

größer

3 2) = = > 9

(*

(Y)

( = 0 (REMAINDER N 2 ) ) ) ) (*

Y Y)))

N) 1) N)

(SQUARE

X (EXPT X ( -

INTEGER_ZAHL))))

(EXPT X (/ N 2 ) ) ) ) N

1))))))))

==>

**

103

3. Rekursion als Problemlösungsmethode

Vergleich zweier Listen, wobei das Element mit dem numerischen Maximalwert der ersten Liste ermittelt wird. Rückgabewert ist das Element der zweiten Liste, das an der gleichen Position steht. ; ; ; ; V e r g l e i c h zweier L i s t e n e v a l > ;; I t e r a t i v e Lösung ( D E F I N E MAXIMUM-PARALLEL-WERT (LAMBDA ( Z A H L E N _ L I S T E S U C H _ L I S T E ) (DO ( ( Z L Z A H L E N _ L I S T E (CDR Z L ) ) ( S L S U C H _ L I S T E (CDR S L ) ) (ZL-MAX 0) (SL-MAX N I L ) ) ((NULL? ZL) SL-MAX) (COND ( ( < ZL-MAX (CAR Z L ) ) ( S E T ! ZL-MAX (CAR Z L ) ) ( S E T ! SL-MAX (CAR S L ) ) ) (T N I L ) ) ) ) ) = = > MAXIMUM-PARALLEL-WERT eval>

;; R e k u r s i v e Lösung ( D E F I N E MAXIMUM-PARALLEL-WERT (LAMBDA ( Z A H L E N . L I S T E S U C H _ L I S T E ) (LETREC ((MAX-PARALLEL (LAMBDA ( Z L SL ZL-MAX S L - M A X ) (COND ( ( N U L L ? Z L ) S L - M A X ) ( ( < ZL-MAX (CAR Z L ) ) (MAX-PARALLEL (CDR Z L ) (CDR S L ) (CAR Z L ) (CAR S L ) ) ) (T ( M A X - P A R A L L E L (CDR Z L ) (CDR S L ) ZL-MAX S L - M A X ) ) ) ) ) ) (MAX-PARALLEL ZAHLEN.LISTE SUCH_LISTE 0 N I L ) ) ) ) = = > MAXIMUM-PARALLEL-WERT

e v a l X MAXIMUM-PARALLEL-WERT ' ( 2 4 1 2 6) ' ( L I SP I S T MANCHMAL SEHR N U E T Z L I C H ) ) eval>

==>

NUETZLICH

;; Ergänzung e i n e r bestehenden A s s o z i a t i o n s l i s t e (DEFINE ERGAENZUNG-A-LISTE (LAMBDA ( L I S T E _ K E Y S L I S T E _ W E R T E A _ L I S T E ) (COND ( ( A N D ( N U L L ? L I S T E _ K E Y S ) ( N U L L ? L I S T E _ W E R T E ) ) A_ L I S T E) ((NULL? LISTE_KEYS) (ERGAENZUNG-A-LISTE ( L I S T ' ? ) LISTE_WERTE A _ L I S T E ) ) ((NULL? LISTE_WERTE) (ERGAENZUNG-A-LISTE LISTE_KEYS (LIST ' ? ) A_LISTE)) (T (CONS ( L I S T (CAR L I S T E _ K E Y S ) (CAR L I S T E _ W E R T E ) ) (ERGAENZUNG-A-LISTE (CDR L I S T E . K E Y S ) (CDR L I S T E _ W E R T E ) A _ L I S T E ) ) ) ) ) ) ==> ERGAENZUNG-A-LISTE

I. Konstrukte (Bausteine zum Programmieren)

104

eval> (ERGAENZUNG-A-LISTE '(EMMA HANS KARL) '(VERHEIRATET '((OTTO VERWITWET) (HANNA L E D I G ) ) )

LEDIG)

((EMMA VERHEIRATET) (HANS LEDIG) (KARL ?) (OTTO VERWITWET) (HANNA LEDIG)) eval> ; ; Bestimmung der Schachtelungstiefe (DEFINE S T R U K T U R - T I E F E (LAMBDA ( L ) (COND ((NULL? L) 1) ((ATOM? L) 0) ( T (+ (APPLY MAX (MAP S T R U K T U R - T I E F E L ) ) 1 ) ) ) ) ) ==> S T R U K T U R - T I E F E eval> ( S T R U K T U R - T I E F E

' ( ( A B) C ( ( ( D (E F ) ) ) ) ) ) ==> 5

IL Konstruktionen (Analyse und Synthese)

Im Rahmen des zweiten Kapitels analysieren wir Programmierbeispiele und erweitern die Menge der im ersten Kapitel erläuterten Konstrukte („Bausteine"). Wir skizzieren charakteristische Konstruktionen („Bauarten") und vertiefen diese im Zusammenhang mit (Programmier)-„Paradigmen", d.h. mit Vorstellungen des Konstrukteurs über die Abarbeitung durch einen (möglicherweise auch fiktiven) Rechner. Im Mittelpunkt stehen die konstruktiven („handwerklichen") Aspekte des Programmierens und weniger die ästhetischen, wie sie etwa im Leitmotto „The Art of Programming" zum Ausdruck kommen. /Hinweis: Konstruktion. Der Begriff Konstruktion (lateinisch: Zusammenschichtung) bezeichnet in der Technik die „Bauart" eines Produktes oder Erzeugnisses. In der Mathematik bezeichnet man damit die Herleitung oder Ermittlung eines mathematischen „Objektes" mit Hilfe genau beschriebener Operationen (Konstruktionsvorschriften) aus bestimmten Ausgangsobjekten. Beispielsweise ist in der Geometrie eine gesuchte Figur aus den vorgegebenen Elementen: Geraden, Winkeln und Kreisen herzuleiten. Die konstruktive (formale) Logik ist eine wissenschaftliche Disziplin (Theorie) des Argumentierens, Beweisens und Schließens. In der Linguistik ist konstruieren das Zusammenfügen von Satzgliedern, Wortgruppen und Wörtern nach Regeln der Grammatik. Die Konstruktion ist eine in Konstituenten zerlegbar, komplexe sprachliche Einheit.7 Als Einstieg dient ein klassisches LISP-Beispiel: „Symbolisches Differenzieren". Dabei zeigt sich der Vorteil, ein Programm als einen Verbund von Konstruktor, Selektor, Prädikat und Mutator zu konzipieren (Abschnitt 4). Die Wahl der zweckmäßigen Abbildung von Daten, die sogenannte Datenrepräsentation, ist eine Kompromißentscheidung zwischen diversen, zum Teil konkurrierenden Anforderungen z.B.: Effizienter Datenzugriff, Datenmodifizierbarkeit, Speicherplatzbedarf oder Lebensdauer. LISP verfügt daher, wie andere Programmiersprachen auch, nicht nur über eine Darstellungsmöglichkeit. Neben der Liste gibt es Vektoren, Zeichenketten, Ports, Ströme etc. Wegen der in LISP bestehenden Ambivalenz zwischen „Daten" und „Programm" (vgl. Abschnitt 1.3) sprechen wir hier allgemein von Abbildungsoption. Zunächst vertiefen wir die Abbildungsoption Liste als Grundlage einer Konstruktion (Abschnitt 5). Durch Modifikation der CONS-Zelle konstruieren wir eine zirkuläre Liste (Abschnitt 5.1). Darauf aufbauend werden die Konstrukte für die Assoziationsliste (Abschnitt 5.2) und die Eigenschaftsliste (Abschnitt 5.3) erörtert. Anhand des relativ ,größeren' Beispiels „Aktenverwaltung" diskutieren wir

106

II. Konstruktionen (Analyse und Synthese)

Konstrukte für die Abbildungsoption Vektor (Abschnitt 6). Die Option Zeichenkette erörtern wir im Rahmen der Aufgabe eines Mustervergleichs (Abschnitte 7.1 und 7.2). Wir befassen uns mit dem PRINT-Namen eines Symbols als eine weitere Abbildungsoption (Abschnitt 7.3). Erklärt wird die Notwendigkeit ein neues Symbol als Unikat zu generieren (Abschnitt 7.4). Die Nützlichkeit des Mustervergleichs demonstiert ein kleiner Vorübersetzer für eine PseudocodeNotation. Ausführlich wird die Funktion und ihre Umgebung als Abbildungsoption behandelt (Abschnitt 8). Das Konstruieren von Funktionen höherer Ordnung erörtern wir anhand der drei Beispiele: Abbildung der CONS-Zelle als Funktion (Abschnitt 8.1.1), Rekursion mit dem Fixpunktoperator Y (Abschnitt 8.1.2) und Simulation der wechselseitigen Abarbeitung zweier Prozesse (Abschnitt 8.1.3). Wir können in Scheme nicht nur die Funktion selbst an ihre eigene LAMBDA-Variable binden, sondern mit dem CALL/CC-Konstrukt auch die noch nicht vollzogene Abarbeitung. Mit dieser sogenannten Continuation („Fortsetzung") konstruieren wir Kontrollstrukturen, die als Sprünge in die „Zukunft" und in die „Vergangenheit" einer Berechnung erklärbar sind (Abschnitt 8.2). Die Möglichkeiten der Syntaxverbesserung durch Makros zeigen wir am Beispiel DEFINE-P, einem Konstrukt, das mehrere Symbole binden kann (Abschnitt 8.3). Die in Kapitel I skizzierte Kontenverwaltung realisieren wir als eine Hierarchie von Umgebungen im Sinne von eigenständigen Paketen (Abschnitt 8.4). Zur Effizienzverbesserung wird das Compilieren von Funktionen kurz erwähnt (Abschnitt 8.5). Die Kontenverwaltung verwenden wir auch, um Schritt für Schritt die „Objektprägung" von Konstruktionen zu steigern. Zur Abschirmung von Wertebeschreibungen (Abschnitt 9.1) dient zunächst das DEFINE-STRUCTUREKonstrukt (Abschnitt 9.2). Aufbauend auf die damit realisierbare einfache statische Vererbung (Abschnitt 9.3) wird die Abbildungsoption „Klasse-Instanz" erklärt (Abschnitt 10). Eine „Klasse-Instanz"-Kontenverwaltung zeigen wir mit dem „Scheme Object-Oriented Programming System" (Abschnitt 10.1). Es erlaubt eine multiple dynamische Vererbung (Abschnitt 10.2) und umfaßt das Konzept aktiver Slots (Abschnitt 10.3). Die Möglichkeiten Knoten, Kanten und Knoteninhalte bei einem Vererbungsgraphen zu modifizieren, sind ein wesentlicher Maßstab für die Bewertung von objektgeprägten Entwicklungsumgebungen. Wir befassen uns daher eingehend mit solchen Modifikationen (Abschnitt 10.4).

4. Verstehen einer Konstruktion Wollen wir Konstruktionen (Programme) verstehen, dann sind die einzelnen Konstrukte und ihre Verknüpfungen (Wechselwirkungen) untereinander zu an-

4. Verstehen einer Konstruktion

107

alysieren. Da die verfolgten Analysezwecke vielfältig sind, müssen Konstrukte im Hinblick auf unterschiedliche Aspekte hinreichend verstanden werden. Die Konstrukte können z.B. nach ihrer Aufgabe, ihrem Leistungsumfang oder ihrer Abarbeitung durch das LISP-System untersucht werden (Abschnitt 4.1). Eine derartige Klassifikation zeigt Tabelle 4-1. So gehört z.B. das DOKonstrukt (vgl. Abschnitt 2.2.3) in PC Scheme zur Klasse der „eingebauten Konstrukte". Als Benutzer können wir es der Klasse „spezielle Konstrukte" zuordnen, weil es seine Argumente nach einer speziellen Vorschrift evaluiert. Aus der Aufgabensicht fällt es in die Klasse „Steuerungskonstrukte", weil es einen Wiederholungsprozeß (Iteration) steuert. Aus der Implementationssicht ist es ein Makro, das zu einem LETREC-Konstrukt expandiert. Jede Konstruktion ist geprägt von den Vorstellungen, die der (Chef-)Konstrukteur hat, wenn er über die Ausführung durch einen Rechner nachdenkt. Für sein Ausführungsmodell sind spezifische Konstrukte, Verknüpfung, Lösungsansätze und Vorgehensweisen charakteristisch. Ein Modell orientiert sich an beispielhaften, besonders gelungenen Konstruktionen. Sie bilden den Rahmen in dem der Konstrukteur seine Lösungen sucht, notiert und bewertet. Ein derartiger „Denkrahmen" wird als Programmierparadigma oder kurz als Paradigma bezeichnet. Man könnte weniger anspruchsvoll auch von einem (Programmier-)Stil reden, wenn wir dabei die jeweils bestimmenden Stilelemente in den Mittelpunkt rücken (vgl. Stoyan, 1986). Allgemein ist ein Paradigma ein von der wissenschaftlichen Fachwelt als Grundlage der weiteren Arbeiten anerkanntes Erklärungsmodell, eine forschungsleitende Theorie. Unerheblich ist, ob das Modell direkt von einem „real existierenden" Rechner abgearbeitet werden kann, oder ob es auf einem fiktiven Rechner (einer angenommenen Maschine) basiert. Im letzteren Fall läßt sich der Konstrukteur nicht davon leiten, wie die Umsetzung seines Modells auf einen verfügbaren Rechner vollzogen wird. Er unterstellt nur, daß seine paradigma-spezifische Kombination von Eigenschaften, Merkmalen und Besonderheiten für seine Konstruktions-Aufgabe zweckmäßig und prinzipiell auch effizient ist. Praxisrelevant ist für ihn die Frage: Existiert eine paradigma-konforme Entwicklungsumgebung, welche die spezifischen Konstrukte direkt bereitstellt und ihre Verknüpfung unterstützt? Angenommen, es sei eine Konstruktion zu analysieren, bei der sich der Konstrukteur einen Rechner vorgestellt hat, der Objekte generieren und modifizieren kann, die miteinander Nachrichten austauschen. In diesem objektgeprägten Modell werden Abstraktionen durch Klassen- und Instanz-Definitionen kombiniert mit Vererbungsmechanismen abgebildet. Es dominieren daher Konstrukte zur Definition und Modifikation von anwendungsspezifischen Klassen und Instanzen (Näheres vgl. Abschnitt 10). Wäre das gleiche Problem mit einem funktionsgeprägten Modell gelöst, dann dominieren anwendungsspezifische Funktionen, wobei Abstraktionen durch Funktionen höherer Ordnung abgebildet wären (Nä-

II. Konstruktionen ( A n a l y s e und S y n t h e s e )

108

Analyseaspekt

Konstruktmerkmal

Beispiele/Erläuterung

E r s t e l 1 er des Konstruktes

ei ngebaut

CONS, CAR,CDR, NULL, E Q ? , ATOM? KONTO.ABBUCHEN! , EINZAHLEN!

Aba r b e i t u n g d u r c h das LISP-System

normal s p e z i el 1

EQUAL?, + , I F , QUOTE, DEFINE

Lei s t u n g s u m f a n g

primitiv komplex

NULL? DO, C A S E ,

Verwendungs fehlerri siko

problemlos destrukti v

CONS, L I S T APPEND!,SET-CAR!, S ET -CDR !

Rei chwei t e

global

lokal

D e f i n i e r t in USER-GLOBALENVIRONMENT bzw. USER-INITIALENVIRONMENT DO, LAMBDA, LET

Aufgabe

Konstruktor Seiektor P r ä d i kat Mutator

CONS, MAKE-KONTO CAR, SUMME-EINZAHLUNGEN N U L L ? , ZERO?, KONTO? S E T ! , EINZAHLEN!

Ablauf

Steuerung (transfer control)

Sequenz, S e l e k t i o n , I t e r a t i o n , " g o t o " , EXIT

seibstdefiniert

of

Umgebungso p e r a t i o n im System

- , ASSQ LAMBDA,

LETREC

B i n d u n g von l o k a l e n Vari ablen, THE-ENVIRONMENT

Kommuni k a t i on READ, WRITE, mit der Umwelt Tab. 4 - 1 .

Klassifikation

für

LOAD

Konstrukte

heres v g l . Abschnitt 8). O f f e n s i c h t l i c h erleichtern Kenntnisse über den a n g e w e n deten D e n k r a h m e n das Verstehen einer v o r g e g e b e n e n Konstruktion. D e r Denkrahmen, in w e l c h e m w i r ein P r o b l e m analysieren und unsere Ideen f ü r seine L ö s u n g g e w i n n e n und notieren, u m f a ß t zumindest: o elementare Konstrukte D i e s e Basiskonstrukte (engl.: primitives) stellen die elementaren „ E i n h e i t e n " dar, mit denen der Konstrukteur umgehen kann.

4. Verstehen einer Konstruktion

109

m >

S o Verknüpfungsmittel Sie ermöglichen das Konstruieren von komplexen Konstrukten aus weniger komplexen. o Abstraktionsmittel Sie benennen komplexe Konstrukte, um diese dann als „Einheiten" handhaben (manipulieren) zu können. Moderne LISP-Systeme unterstützen verschiedene Denkrahmen („Paradigmen"). Sie stellen zumindest derartige Mittel bereit für: o imperativgeprägte Programme (z.B. SET!), Kennzeichnend sind die Erklärungen von Variablen und die schrittweise Änderung ihrer Werte durch Zuweisungsbefehle (engl.: assign statements). Diese Prägung unterstützen z.B. die Programmiersprachen COBOL, FORTRAN oder Pascal. o funktionsgeprägte Programme (z.B. LAMBDA, APPLY) und Kennzeichnend sind Kompositionen von Funktionen höherer Ordnung. Diese Prägung unterstützt z.B. die Programmiersprache ML (vgl. Gordon u.a., 1984). o objektgeprägte Programme (z.B. DEFINE-CLASS, MAKE-INSTANCE). Kennzeichnend sind Objekte (lokale Speicher), deren Eigenschaften durch einen Vererbungsmechanismus bestimmt werden und die Nachrichten untereinander austauschen. Diese Prägung unterstützen z.B. die Programmiersprachen SmallTalk, C++ und Flavor (vgl. Abschnitt 10). /Hinweis: "Orientiert", "basiert" oder "geprägt". In der Literatur werden in diesem Zusammenhang Beiworte wie „orientiert" oder "basiert" verwendet. Stets verdeutlichen solche Beiworte, daß der Denkrahmen nicht auf die reine Lehre beschränkt ist. Wir bezeichnen eine Konstruktion als funktionsgeprägt, selbst wenn sie z.B. das WRITELN-Konstrukt enthält, das wegen des Nebeneffektes eingesetzt wird und nicht zur Erzielung eines Funktionswertes. Entschei-

110

II. Konstruktionen (Analyse und Synthese)

dend ist das „Sich-leiten-lassen" vom funktionalen Modell. Kompositionen anwendungsspezifischer Funktionen, die (weitgehend) frei von Nebeneffekten definiert sind, geben der Konstruktion ihr Beiwort „funktionsprägt"./ Die Folge dieser LISP-„Potenz" ist eine Konstruktevielfalt. Um sich in dieser großen Menge zurechtzufinden, bedarf es eines pragmatischen Leitfadens (Abschnitt 4.2). Wir wählen dazu eine aufgabenbezogene Klassifikation in die vier Typen: o Konstruktor, o Selektor (incl. Präsentator), o Prädikat und o Mutator, auch Modifikator genannt (incl. Destruktor). Ein Konstruktor erzeugt und kombiniert „Einheiten" zu neuen „Einheiten". Dabei ist eine „Einheit" z.B. ein Objekt, eine Funktion, Menge, Liste oder ein Vektor. Ein Selektor ermöglicht den Zugriff auf eine solche „Einheit". Prädikate vergleichen „Einheiten". Sie prüfen z.B. auf Gleichheit oder Identität und stellen damit den „Typ" fest. Mutatoren ändern existierende „Einheiten". In dieses pragmatische, enge Klassifikationsschema integrieren wir auch Vorgänge, wie das Zerstören von „Einheiten" (Destruktor hier als Mutator klassifiziert) oder das Präsentieren von „Einheiten" (Präsentator hier als Selektor klassifiziert). Der zeitliche Aspekt z.B. das Aktivieren, Terminieren und Suspendieren von „Einheiten" ist damit kaum erfaßbar. Wir decken diesen Aspekt daher durch Graphiken (z.B. Vererbungsgraphen) und zusätzliche Erläuterungen ab. Gezeigt wurde, daß eine strikte begriffliche Trennung in Programm (= Algorithmus) und Daten(struktur) die Ambivalenz zwischen beiden verdeckt (vgl. Abschnitt 1.3). Zum Verstehen von LISP-„Programmen" erscheint es sinnvoll, diese Trennlinie nicht permanent zu betonen. Wir sprechen daher im folgenden weniger von Programm- und Datenstrukturen sondern von Optionen zur Abbildung von „Einheiten".

4.1 Analyseaspekte Der Zweck einer Analyse bestimmt die Suche nach den bedeutsamen (relevanten) Konstrukten und ihrem Zerlegungsgrad. Dabei ist ein Konstrukt entweder ein „bekanntes" Konstrukt, oder es setzt sich wiederum aus zu analysierenden Konstrukten zusammen. Wir unterstellen, daß der Leser des Quellcodetextes zumindest eingebaute LISP-Konstrukte erkennen und ihre Wirkung (gegebenenfalls durch Nachschlagen im jeweiligen Referenzmanual) ihm hinreichend bekannt ist.

111

4. Verstehen einer Konstruktion

Von den mannigfaltigen Analysezwecken sind z.B. zu nennen: o Das Nachvollziehen von Ergebnissen (Output) bei vorgegebener Ausgangssituation (Input), o das Prüfen der Übereinstimmung mit der Spezifikation, o die Erweiterung des Leistungsumfangs, o die Korrektur von Fehlern, o die Veränderung des Ressourcen-Bedarfs („Tunen") und o die Portierung auf ein anderes LISP-System. Das Herausfinden und Verstehen der Konstrukte, die für den jeweiligen Analysezweck relevant sind, ist keine triviale Aufgabe. Zu beantworten sind die beiden Fragen: o Aus welchen bekannten Konstrukten besteht die Konstruktion? o Welche Wechselwirkungen (Interaktionen) bestehen zwischen diesen Konstrukten? ;;;;

T L C - L I SP

T-eval>

(LET

Lösung

zum F e s t s t e l l e n ,

ob e i n e

Zahl

N gerade

((GERADE? (LAMBDA (COND

(N) ( ( Z E R O P N) T ) (T ( U N G E R A D E ? ( S U B 1 N ) ) ) ) ) ) (UNGERADE? (LAMBDA (N) (COND ( ( Z E R O P N) N I L ) (T (GERADE? ( S U B 1 N ) ) ) ) ) ) ) (GERADE? 4 4 ) ) ==> T

;;;;

Lösung

portiert ZEROP

nach

PC

ZERO?)

Scheme

S-eval>

(DEFINE

¡einfache

Namensangleichung

S-eval>

;; Protierungsproblem: ;; - T L C - L I S P : dynamische Bindung ;; - SCHEME: s t a t i s c h e B i n d u n g (LETREC ¡ H i e r L E T R E C n i c h t LET ((GERADE? (LAMBDA (N) (COND ( ( Z E R O P N) T ) (T ( U N G E R A D E ? ( S U B 1 N ) ) ) ) ) )

!!!

ist.

112

II. Konstruktionen (Analyse und Synthese) (UNGERADE? (LAMBDA (N) (COND ((ZEROP N) NIL) (T (GERADE? (SUB1 (GERADE? 44)) ==> T P r o g r a m m f r a g m e n t 4.1-1.

Portierung

von

N))))))) TLC-LISP

nach

SCHEME

Als Beispiele analysieren wir zwei Programmfragmente (4.1-1 und 4.1-2) im Hinblick auf eine Portierung nach Scheme. Beim ersten Programmfragment ist die Beseitigung der Namensunterschiede von eingebauten Konstrukten (hier: ZEROP bzw. ZERO?) nicht ausreichend. Zu berücksichtigen sind die unterschiedliche Bindungsstrategien des namensgleichen Konstruktes LET. Etwas salopp formuliert: „Kein Verlaß auf Namen!". Im zweiten Programmfragment ist die Iteration auf direkte Weise durch einen expliziten Rücksprung mit dem GOKonstrukt und der Sprungmarke LOOP realisiert. Das Verlassen der Iteration geschieht über das RETURN-Konstrukt. Diese Aufgabe kann in Scheme entweder ein DO-Konstrukt oder eine rekursive lokale Funktion übernehmen. ;;;; Iteration mittels "go to"- Konstrukt ::;; (vgl. T a n i m o t o , 1987 , S. 32.) Tanimoto-eval>

(PROG (X Y) (SETQ X 10) (SETQ y 0) LOOP (PRINT (TIMES Y Y)) (SETQ X (SUB1 X)) (SETQ Y (ADD1 Y)) (COND ((ZEROP X) (RETURN Y))

0 1 4 9 16 25 36 49 64

81 10

;PR0G d e f i n i e r t X und Y ; als lokale Variablen

¡Verlassen der ; Iteration (T (GO LOOP)))) ==>

; jede Zahl

auf neuer

Zeile

;;;; Lösung portiert nach PC Scheme ;;; Portierungsproblem: ;;; Das Erkennen und Verstehen der "go t o " - K o n s t r u k t i o n die Auswahl der z w e c k m ä ß i g e n Lösung in Scheme. S-eva1>

und

;; Lösung mit D O - K o n s t r u k t (DO ((X 10 ( - 1 + X)) (Y 0 (1+ Y))) ((= X 0) Y) (PRINT (* Y Y))) ==> 0 1 ... S - e v a l > ;; A l t e r n a t i v e Lösung mit lokaler ;; e n d - r e k u r s i v e r Funktion (LETREC ((F00 (LAMBDA (X Y) (COND ((= X 0) Y) (T (PRINT (* Y Y)) (F00 ( - 1 + X) (1+ Y)))))))

4. Verstehen einer Konstruktion

113

(F00 10 0)) ==> 0 1 . . . Programmfragment 4 . 1 - 2 .

Portierung von einem tem nach PC Scheme

kleinem

LISP-Sys-

Für das Verstehen zum Zweck der Portierung mag das Durcharbeiten der Ouellcodetexte, beginnend von der ersten Zeile bis zum Schluß, erfolgreich sein. Handelt es sich jedoch um ein komplexes (Programm)System, dann ist für das Verstehen die sequentielle Betrachtung „Von-Anfang-bis-Ende-des-Textes" selten hilfreich. Zumindest sind in der alltäglichen Programmierpraxis schon aus Zeitgründen nicht alle Konstrukte in Notationsreihenfolge analysierbar. Das Verfolgen aller möglichen „Pfade", die sich aufgrund der Steuerungskonstrukte ergeben, scheidet wegen ihrer großen Zahl aus. Die Bewältigung der Komplexität bedingt, daß wir uns an Abstraktionsebenen orientieren.

4.2 Verbund von Konstruktor, Selektor, Prädikat und Mutator Ein komplexes Konstrukt setzt sich aus einfacheren Konstrukten zusammen. In Bild 4.2-1 besteht das Konstrukt K-l° aus den Konstrukten K-21 und K-31, wobei der Exponent hier zur Kennzeichnung des Zerlegungsgrades dient. Die analytische Betrachtung ist durch eine an der Synthese orientierten Betrachtung zu ergänzen. Das Konstrukt K-l° ist dann ein Konstruktor, der die Konstrukte K-21 und K-31 zusammenfügt. Damit ein Konstruktor die richtigen „Einheiten" zusammenfügen kann, müssen die einzelnen „Einheiten" erkannt werden. Benötigt werden daher passende Erkennungskonstrukte (engl.: recognizer). Erforderlich ist in unserem Beispiel ein Konstrukt, das feststellt, ob es sich bei einer gegebenen „Einheit" um K-2 handelt oder nicht. Ein solches Erkennungskonstrukt verhält sich wie ein Prädikat wie z.B. das NUMBER?-Konstrukt, das prüft, ob ein numerischer Wert vorliegt. Zu einem Konstruktor, der n-verschiedene „Einheiten" zusammenfügt, benötigen wir n-verschiedene Prädikate zum Erkennen der einzelnen „Einheit". Wir definieren daher für unseren Konstruktor K-l die korrespondierenden Prädikate K-2? und K-3?. Auf der nächst tieferen Abstraktionsebene, hier Ebene 1, wären z.B. für den Konstruktor K-3 die Prädikate K-6?, K-7? und K-8? zu definieren. Zusätzlich benötigen wir korrespondierende Selektoren, um bei einem zusammengesetzten Konstrukt auf seine einzelnen „Einheiten" zugreifen zu können. Die Selektoren haben die Aufgabe der Umkehrung der Konstruktor-Wirkung. Für den Konstruktor K-l im Bild 4.2-1 wären folgende Selektoren zu definieren: eval> (SELEKTOR-K-2 K-l) ==> K-2 eval> (SELEKTOR-K-3 K-l) ==> K-3

114

II. Konstruktionen (Analyse und Synthese)

Abstrakti ons ebene bzw. Zerlegungsgrad

Legende:

K-i J —>

::= Konstrukt mit der Identifizierungsnummer i auf der Abstrakionsebene j o ::= "Setzt-sich-zusammen", z.B. s e t z t sich K-5^ aus 5 J K-9- und K- 10 zusammen

Bild 4 . 2 - 1 .

Skizze

einer

Konstrukte-Hierarchie

(Modul-Hierarchie)

Die Analyse und damit das Verstehen werden erleichtert, wenn wir von folgenden Eigenschaften bei unserem Verbund aus Konstruktoren, Selektoren und Prädikaten ausgehen können: eva1> (SELETOR-K-2 (KONSTRUKTOR-K-1 K-2 K-3)) eval> (SELETOR-K-3 (KONSTRUKTOR-K-1 K-2 K-3)) eval> (K-2? (SELETOR-K-2 (KONSTRUKTOR-K-1 K-2 K-3))) ==> eval> (K-3? (SELETOR-K-3 (KONSTRUKTOR-K-1 K-2 K-3))) ==>

==> K-2 ==> K-3 #T #T

Wir streben daher für alle Abstraktionsebenen diese Verbundeigenschaften an. Sie sind allgemein, wie folgt, beschreibbar:

4. Verstehen einer Konstruktion

115

Der Verbund von Konstruktoren, Selektoren und Prädikaten ermöglicht Neukonstruktionen. Soll eine bestehende Konstruktion geändert, also nicht von Anfang an neu zusammengesetzt werden, dann sind Modifikations-Konstrukte, sogenannte Mutatoren, erforderlich. Betrachten wir z.B. ein Konto, von dem ein Geldbetrag abzubuchen ist, dann wollen wir (wahrscheinlich) mit einem Konstrukt ABBUCHEN! das bestehende Konto im Saldo ändern, d.h. modifizieren und nicht ein ganz neues Konto anlegen und alle alten Buchungen rekonstruieren und dann die neue Buchung zusätzlich vollziehen (vgl. Beispiel in Abschnitt 1.4.2). In den folgenden Ausführungen dient dieser Verbund als pragmatischer Leitfaden. Er ist kein Patentrezept („Allheilmittel"). Er ersetzt keinesfalls intensives Durchdenken einer Konstruktion. Er hilft jedoch eine komplexe Aufgabe zweckmäßig in weniger komplexe Teile zu gliedern.

4.3 LISP-Klassiker: „Symbolisches Differenzieren" Als Analysebeispiel betrachten wir das Differenzieren einer mathematischen Funktion anhand des Programms 4.3-1. Diese Aufgabenstellung ist ein LISPKlassiker. Sie war das „Markenzeichen" beim Start der LISP-Entwicklung. Sie ist noch heute für Erläuterungszwecke aktuell (vgl. z.B. Abelson/Sussman/Sussman, 1985, p. 104; Müller, 1985, S.13ff, Wagner, 1987, S. B-lff, Weissman, 1967, p. 16 , oder Winston/Horn, 1987, dt.Ausgabe S.276 ff). Wir nutzen den Vorzug, daß sie einerseits in LISP einfach (rekursiv) lösbar ist und andererseits keiner langen Erläuterung bedarf (Schulmathematik, vgl. z.B. Scheid, 1985).

116

II. Konstruktionen (Analyse und Synthese)

Das Programm ABLEITUNGSFUNKTION (4.3-1) faßt die lokal definierten Konstruktoren, Selektoren und Prädikate zusammen. Es hat folgende Struktur: eval> (DEFINE ABLEITUNGSFUNKTION ( L A M B D A ( M A T H E M A T I S C H E R _ A U S D R U C K DX) (LETREC ;;;; P r ä d i k a t e f ü r K o n s t a n t e u n d V a r i a b l e ( ( K O N S T A N T E ? ... ) (VARIABLE? ... ) ( G L E I C H E - V A R I A B L E ? ... ) ;;;; P r ä d i k a t e , S e l e k t o r e n und K o n s t r u k t o r e n ( S U M M E ? ... ) ( E R S T E R - S U M M A N D ... ) ( Z W E I T E R - S U M M A N D ...) ( B I L D E - S U M M E ...) ( V E R E I N F A C H E - S U M M E ... ) ;;;; P r ä d i k a t e , S e l e k t o r e n u n d K o n s t r u k t o r e n ( P R O D U K T ? ... ) ( M U L T I P L I K A N D ... ) ( M U L T I P L I K A T O R ... ) ( B I L D E - P R O D U K T ... ) ( V E R E I N F A C H E - P R O D U K T ... ) ;;;; Abi ei t u n g s r e g e l n (ABLEITEN (LAMBDA (COND ...)))

(FORMEL

für

die

Summenregel

für

die

Produktregel

VARIABLE) ; F a l 1 u n t e r s c h e i d u n g für die ; anzuwendende Ableitungsregel

;; A n w e n d u n g d e r l o k a l e n F u n k t i o n (ABLEITEN MATHEMATISCHER.AUSDRUCK DX)))) ==>

ABLEITUNGSFUNKTION

Beim Aufruf des Konstruktes ABLEITEN ist die LAMBDA-Variable FORMEL an den abzuleitenden mathematische Ausdruck gebunden. Mit Hilfe der Prädikate SUMME? und PRODUKT? wird erkannt, ob es sich um eine Summe oder ein Produkt handelt. In diesen Fällen ist der Wert von FORMEL ein zusammengesetzes Konstrukt, dessen einzelne Teile zur Anwendung der entsprechenden Ableitungsregel benötigt werden. Im Fall der Summe sind deshalb die Selektoren ERSTER-SUMMAND und ZWEITER-SUMMAND definiert. Für das Produkt sind es die Selektoren MULTIPLIKAND und MULTIPLIKATOR. Die Benennung der lokalen Funktionen und Kommentierung entsprechen den Empfehlungen von Abschnitt 11. In dem kurzen Programmvorspann verweist daher die Abkürzung A auf eine Anforderung und die Abkürzung T auf Testfälle. Dokument 4.3-1 Titel: Programm ABLEITUNGSFUNKTION Erstellt: 05.09.89 ¡letzte Ä n d e r u n g :

18.10.90

AI

A B L E I T U N G S F U N K T I O N e r m i t t e l t die A b l e i t u n g einer m a t h e m a t i s c h e n Formel n a c h e i n e r v o r g e g e b e n V a r i a b l e n .

A2

Für d i e s e s s y m b o l i s c h e D i f f e r e n z i e r e n folgenden Ableitungsregeln:

gelten

die

4. Verstehen einer Konstruktion A2.1 A2.2

Ableiten einer Ableiten einer du/dx

A2.3 A2.4

Summenregel: Produktregel:

A3

Die a b z u l e i t e n d e anzugeben.

T1

Beispiel:

117

K o n s t a n t e n : dc/dx ==> 0 Variablen: = = > 1 f a l l s u=x ;x n a c h x a b g e l e i t e t 0 sonst d ( u + v ) / d x = = > du/dx + d v / d x d ( u * v ) / d x ==> (du/dx)*v + u * ( d v / d x ) Formel

ist

in

d(ax2

+ bx + c ) / d x = = > 2ax + b

eval>

(ableitungsfunktion ' ( + (+ ( * a (* x x ) )

Wert d e r Bei s p i e l s f u n k t i o n m i t a : = 3 und b := 5 eval>

(ableitungswert ' ( + (+ (* a ( *

Präfixnotation

( * b x ) ) c) ' x ) = = > ( + ( * a (* 2 x))

an d e r S t e l l e

x x))

(*

b x))

c)

x

:= 1

'x

1) = = >

( D E F I N E A B L E I T U N G S F U N K T I O N (LAMBDA (MATHEMATISCHER_AUSDRUCK (LETREC ( ( K O N S T A N T E ? (LAMBDA ( X ) (NUMBER? X ) ) ) ( V A R I A B L E ? (LAMBDA ( X ) (SYMBOL? X ) ) ) (GLEICHE-VARIABLE? (LAMBDA ( V A R _ 1 VAR_2) (AND ( V A R I A B L E ? VAR_1) ( V A R I A B L E ? VAR_2) ( E Q ? VAR_1 V A R _ 2 ) ) ) ) ; ; ; ; K o n s t r u k t o r , S e l e k t o r e n und P r ä d i k a t f ü r d i e Summe ;;; ;;;

E r k e n n e n e i n e r a b z u l e i t e n d e n Summe: und S e l e k t i e r e n i h r e r Summanden. (SUMME? (LAMBDA ( X ) (AND ( P A I R ? X) (OR ( E Q ? (CAR X) ( E Q ? (CAR X)

(+ u v)

bzw.

Konstruktoren

für

die

'+) 'PLUS)))))

"rechte"

(BILDE-SUMME (LAMBDA ( S _ l S _ 2 ) ( L I S T ' + S_1 S _ 2 ) ) )

Seite

der

11 DX)

(PLUS u v)

(ERSTER-SUMMAND (LAMBDA (SUMME) ( L I S T - R E F SUMME 1 ) ) ) (ZWEITER-SUMMAND (LAMBDA (SUMME) ( L I S T - R E F SUMME 2 ) ) ) ;;;

b)

Summenregel

118

II. Konstruktionen (Analyse und Synthese) ( V E R E I N FACHE-SUMME (LAMBDA (SUMME) ( L E T ( ( S - l ( E R S T E R - S U M M A N D SUMME) ( S - 2 (ZWEITER-SUMMAND SUMME))) (COND ( ( A N D ( N U M B E R ? S - l ) ( N U M B E R ? S - 2 ) ) ( + S - l ((AND (NUMBER? S - l ) (ZERO? S - l ) ) S - 2 ) ((AND (NUMBER? S - 2 ) (ZERO? S - 2 ) ) S - l ) ((GLEICHE-VARIABLE? S - l S-2) (BILDE-PRODUKT 2 S - l ) ) (T S U M M E ) ) ) ) ) ; Konstruktor,

Selektoren

und

Prädikat

für

Erkennen eines abzuleitenden Produktes: (* (MUL u v ) u n d S e l e k t i e r e n v o n M u l t i p l i k a n d (PRODUKT? (LAMBDA ( X ) (AND ( P A I R ? X ) (OR ( E Q ? ( C A R (EQ? (CAR

X) X)

das

S-2))

Produkt

u v ) bzw. und M u l t i p l i k a t o r .

'*) 'MUL)))))

( M U L T I P L I K A N D ( L A M B D A ( P R O D U K T ) ( L I S T - R E F PRODUKT 1 ) ) ) ( M U L T I P L I K A T O R ( L A M B D A ( P R O D U K T ) ( L I S T - R E F PRODUKT 2 ) ) ) K o n s t r u k t o r e n für die " r e c h t e " S e i t e der Produktregel (BILDE-PRODUKT (LAMBDA ( M _ l M_2) ( L I S T ' * M _ 1 M_ 2 ) ) ) ( V E R E I N F A C H E - PRODUKT (LAMBDA (PRODUKT) (LET ( ( M - 1 (MULTIPLIKAND PRODUKT)) (M-2 (MULTIPLIKATOR PRODUKT))) (COND ( ( O R ( A N D ( N U M B E R ? M - l ) ( Z E R O ? M - l ) ) (AND ( N U M B E R ? M - 2 ) ( Z E R O ? M - 2 ) ) ) ( ( E Q ? M - l 1) M - 2 ) ( ( E Q ? M-2 1) M - l ) ((AND (NUMBER? M - l ) (NUMBER? M - 2 ) ) ( * (T P R O D U K T ) ) ) ) ) ;

0) M-l

M-2))

Ableitungsregeln ( A B L E I T E N (LAMBDA (FORMEL V A R I A B L E ) (COND ( ( K O N S T A N T E ? F O R M E L ) 0 ) ;dc/dx ==> 0 ( ( V A R I A B L E ? FORMEL) (COND ( ( G L E I C H E - V A R I A B L E ? ;dx/dx ==> 1 FORMEL V A R I A B L E ) 1 ) (T 0 ) ) ) ( ( S U M M E ? FORMEL) ;Summenregel ( V E R E I N FACHE-SUMME (BILDE-SUMME ( A B L E I T E N (ERSTER-SUMMAND FORMEL) V A R I A B L E ) ( A B L E I T E N (ZWEITER-SUMMAND FORMEL) VARIABLE))))

4. Verstehen einer Konstruktion

119

((PRODUKT? FORMEL) ;Produktregel (VEREINFACHE-SUMME (BILDE-SUMME (VEREIN FACHE-PRODUKT (BILDE-PRODUKT (ABLEITEN (MULTIPLIKAND FORMEL) VARIABLE) (MULTIPLIKATOR FORMEL))) (VEREIN FACHE-PRODUKT (BILDE-PRODUKT (MULTIPLIKAND FORMEL) (ABLEITEN (MULTIPLIKATOR FORMEL) VARIABLE)))))) (T ( W R I T E L N ¡sonst "Keine Ableitung fuer: " FORMEL " definiert")))))) ; ; Startfunktion (ABLEITEN MATHEMATISCHER_AUSDRUCK DX)))) ;;;;

Berechnung

eines

Wertes

der

Ableitungsfunktion

(DEFINE ABLEITUNGSWERT ( L A M B D A ( M A T H E M A T I S C H E R _ A U S D R U C K DX A N _ D E R _ S T E L L E ) ((EVAL (LIST 'LAMBDA ( L I S T DX) (ABLEITUNGSFUNKTION MATHEMATISCHER_AUSDRUCK AN_DER_STELLE))) Programm 4.3-1.

Symbolisches

DX)))

Differenzieren

Die Ableitungsregel für eine Summe (kurz: Summenregel) lautet: d(u+v)/dx

==>

du/dx +

dv/dx

Das Ergebnis, die rechte Seite der Summenregel, ist ein zusammengesetzter Ausdruck, der wieder eine Summe darstellt. Das Zusammenfügen von du/dx mit dv/dx übernimmt der Konstruktor BILDE-SUMME. Entsprechendes gilt für die Produktregel: d(u*v) ==>

(du/dx)*v

+

u*(dv/dx)

Hier setzt der Konstruktor BILDE-PRODUKT in Kombination mit BILDESUMME das Ergebnis zusammen. Nehmen wir als Beispiel an, daß S_ 1 an den Wert 5 und S_ 2 an den Wert 7 gebunden sind, so daß gilt: eval>

(BILDE-SUMME

S_1

S_2) ==>

(+ 5 7)

Um dieses Ergebnis zu vereinfachen, können wir statt (+ 5 7) den Wert 12 zurückgeben. Die Vereinfachung der gebildeten Summe übernimmt das Konstrukt VEREINFACHE-SUMME, das dazu auf Teile der gebildeten Summe zugreifen muß. Für den Konstruktor BILDE-SUMME benötigen wir korrespondierende Selektoren, damit VEREINFACHE-SUMME auf seine Teile zugreifen kann. Das Konstrukt BILDE-SUMME gibt eine Liste mit dem Additionsoperator

120

II. Konstruktionen (Analyse und Synthese)

als erstes Element (Präfixnotation) zurück. Damit erzeugt BILDE-SUMME die gleiche Struktur wie bei einer eingegebenen Summe, die es abzuleiten gilt, d.h. der Wert an den FORMEL gebunden ist. Wir können daher für VEREINFACHE-SUMME auf dieselben Selektoren zurückgreifen. ERSTER-SUMMAND und ZWEITER-SUMMAND sind damit (auch) die korrespondierenden Selektoren zu BILDE-SUMME. Entsprechendes gilt für den Konstruktor BILDE-PRODUKT mit seinen Selektoren MULTIPLIKAND und MULTIPLIKATOR in Verbindung mit VEREINFACHE-PRODUKT (vgl. Bild 4.3-1.). /Hinweis: Selektoraufgabe. Die Doppelaufgabe der Selektoren ERSTER-SUMMAND und ZWEITER-SUMMAND, d.h. einerseits den Wert von FORMEL und andererseits den von BILDE-SUMME erzeugten zu „zerlegen", macht sich bei einer Programmänderung erschwerend bemerkbar. Dies gilt entsprechend auch für die Selektoren MULTIPLIKAND und MULTIPLIKATOR. Wollen wir z.B. auf Infix-Notation bei der Eingabe (Wert für FORMEL) übergehen und die Präfix-Notation für BILDE-SUMME beibehalten, dann muß die Doppelfunktion erkannt werden. Jeweils passende Selektoren sind dann neu zu programmieren.7 Im Sinne unserer angestrebten Verbundeigenschaft (vgl. Abschnitt 4.2) gilt in dem Konstrukt ABLEITEN z.B.: eval> eval> eval> eval>

(ERSTER-SUMMAND (BILDE-SUMME 5 7 ) ) ==> 5 (ZWEITER-SUMMAND (BILDE-SUMME 5 7 ) ) ==> 7 (MULTIPLIKAND (BILDE-PRODUKT 2 3)) ==> 2 (MULTIPLIKATOR (BILDE-PRODUKT 2 3 ) ) ==> 3

In der Programmier(alltags)praxis wird diese Eigenschaft einer Konstruktion kaum beachtet und selten realisiert (vgl. z.B. die Lösungen zur Ableitungsaufgabe in der oben genannten Literatur). Bezogen auf das Programm ABLEITEN wäre z.B. die folgende Lösung weniger zweckmäßig, wenn als korrespondierende Selektoren ERSTER-SUMMAND und ZWEITER-SUMMAND beibehalten werden. Sie faßt die Konstruktoren VEREINFACHE-SUMME mit BILDESUMME zu einem Konstrukt MAKE-SUMME zusammen: eval>

( D E F I N E MAKE-SUMME (LAMBDA ( S _ l (COND ( ( A N D ( N U M B E R ? S _ l ) ((AND (NUMBER? S _ l ) ((AND (NUMBER? S _ 2 ) ((GLEICHE-VARIABLE? (LIST ' * 2 S_1)) (T ( L I S T ' + S _ 1 S _ 2 )

S_2) (NUMBER? S _ 2 ) ) ( + S _ 1 (ZERO? S _ l ) ) S_2) (ZERO? S _ 2 ) ) S _ l ) S_1 S_2) ) ) ))

==>

S_2))

MAKE-SUMME

Die Selektion eines Summanden wäre bei diesem Konstruktor nicht in jedem Fall mehr möglich. eval>

(ERSTER-SUMMAND = = > ERROR . . .

(MAKE-SUMME 5 7 ) ) ¡Grund: ( L I S T - R E F

12

1)

!

4. Verstehen einer Konstruktion

121

ABLEITEN

Ableiten einer Konstanten dc/dx = >

Ableiten einer Variablen du/dx ==> 1; u=x 0; sonst

Linke Seite: Linke Seite: P: KONSTANTE? P: VARIABLE? —>SYMB0L? -> NUMBE GLEICHEVARIABLE? -> EQ?

Ableiten einer Summe diu + i ) l dx = > du/dx + dv/dx

Ableiten eines Produktes d(u*v)/dx = > (du/dx)*v + u*(dv/dx)

Linke Seite: Linke Seite: P: SUMME? - > PAIR?, EQ? - > PAIR?.EQ?, CAR CAR S: ERSTER-SÜMMAND S: MULTIPLKAND ZUEITER-SUMMAND MULTIPLIKATOR -> CAR, CDR -> CAR, CDR Rechte Seite: K: BILDE-SUMME -> LIST, + VEREINFACHESUMME -> +, BILDE-f S: siehe Linke Seite

Rechte Seite: K: BILDE-PRODUKT, BILDE-SUMME -> LIST, *, + VEREIN FACHE S: siehe Linke Seite

Legende

= = = = = =

Linke Seite der entsprechenden Ableitungsregel Rechte Seite der entsprechenden Ableitungsregel Prädikat (Erkennungskonstrukt) Selektor Konstruktor basiert auf; z.B. basiert KONSTANTE? auf dem eingebauten NUMBER?-Konstrukt = Kennzeichnung einer Selektion (vgl. Abschnitt I I I ) B i l d 4 . 3 - 1 . Strukturskine

d e s Programs

4.3-1.

4.4 Zusammenfassung: Abstraktionsebene Der „Denkrahmen" des Konstrukteurs, allgemein als Paradigma bezeichnet, wird bestimmt von charakteristischen Konstruktionen, die z.B. imperativ-, funktions- oder objektgeprägt sind. Ein Paradigma umfaßt elementare Konstrukte, Verknüpfungs- und Abstraktionsmittel.

122

II. Konstruktionen (Analyse und Synthese)

Wir betrachten eine Konstruktion auf den verschiedenen Abstraktionsebenen als einen Verbund von: o Konstruktor, o Selektor (incl. Präsentator), o Prädikat und o Mutator (incl. Destruktor). Von diesem Verbund fordern wir: Die Bausteine, die ein Konstruktor zusammenfügt, müssen von Prädikaten erkannt und durch Selektoren ohne Verlust wiedergewonnen werden können. Charakteristisches

Beispiel für Abschnitt 4\

;;;; Abbildung eines Formulars ;;; Untere A b s t r a k t i o n s e b e n e : e v a l > ;; K o n s t r u k t o r (DEFINE BILDE-LEERES-FORMULAR (LAMBDA ( ) ( L I S T ' * K 0 P F * ' * T E X T * ;;; Prädikate e v a l > ( D E F I N E KOPF? (LAMBDA ( X ) e v a l > ( D E F I N E T E X T ? (LAMBDA ( X ) e v a l > ( D E F I N E F U S S ? (LAMBDA ( X ) e v a l > (DEFINE LEERES-FORMULAR? (LAMBDA ( X ) (AND ( P A I R ? X ) (EQ? ( L I S T - R E F X (EQ? ( L I S T - R E F X (EQ? ( L I S T - R E F X (EQ? ( L I S T - R E F X

(STRING? (STRING? (STRING?

0) 1) 2) 3)

'*FUSS* '*F0RM*))) ==> BILDE-LEERES-FORMULAR X))) X))) X)))

==> ==> ==>

KOPF? TEXT? FUSS?

'*K0PF*) '*TEXT*) '*FUSS*) '*F0RM)))) ==> LEERES-FORMULAR?

; ; ; Nächst höhere A b s t r a k t i o n s e b e n e : e v a l > ;; K o n s t r u k t o r (DEFINE BILDE-FORMULAR (LAMBDA (KOPF TEXT FUSS) ( I F (AND ( K O P F ? K O P F ) ( T E X T ? T E X T ) ( F U S S ? F U S S ) ) ( C O N S KOPF (CONS TEXT (CONS FUSS (CDDDR (BILDE-LEERES-FORMULAR)))))))) ==> BILDE-FORMULAR e v a l > ;; P r ä d i k a t ( D E F I N E FORMULAR? (LAMBDA ( X ) (OR ( L E E R E S - F O R M U L A R ? X ) (AND ( P A I R ? X ) ( E Q ? ( L I S T - R E F X 3 ) ' * F 0 R M * ) ) ) ) ) = = > FORMULAR? ;;; Seiektoren e v a l > ( D E F I N E GET-KOPF (LAMBDA ( X ) (COND ( ( F O R M U L A R ? X ) ( L I S T - R E F X 0 ) ) (T (ERROR " K e i n F o r m u l a r : " X ) ) ) ) ) e v a l > (DEFINE GET-TEXT (LAMBDA ( X ) (COND ( ( F O R M U L A R ? X ) ( L I S T - R E F X 1 ) ) (T (ERROR " K e i n F o r m u l a r : " X ) ) ) ) )

==>

GET-KOPF

==>

GET-TEXT

5. Abbildungsoption: Liste

123

eval>

(DEFINE GET-FUSS (LAMBDA ( X ) (COND ( ( F O R M U L A R ? X ) ( L I S T - R E F X 2 ) ) (T ( E R R O R " K e i n F o r m u l a r : " X ) ) ) ) . ) :;; Mutatoren ; ; ; H i n w e i s : D a s S E T - C A R ! - K o n s t r u k t w i r d e r s t im A b s c h n i t t 5 ; ; ; e r l ä u t e r t . E s i s t h i e r e r f o r d e r l i c h , um e i n e x i s t i e r e n d e s : ; ; F o r m u l a r , das durch e i n e L i s t e a b g e b i l d e t ist, ; ; ; m o d i f i z i e r e n zu können, eval> (DEFINE SET-KOPF! (LAMBDA (IF

(KOPF

(AND

FORM)

(KOPF?

KOPF)

(FORMULAR?

FORM))

(BEGIN (SET-CAR! FORM)))) eval>

(DEFINE

KOPF)

SET-KOPF!

SET-TEXT!

(LAMBDA (IF

FORM ==>

(TEXT

(AND

FORM)

(TEXT?

TEXT)

(FORMULAR?

FORM))

(BEGIN (SET-CAR! FORM)))) eval>

(DEFINE

FORM)

TEXT)

SET-TEXT!

SET-FUSS!

(LAMBDA (IF

(CDR ==>

(FUSS

(AND

FORM)

(FUSS?

FUSS)

(FORMULAR?

FORM))

(BEGIN (SET-CAR! FORM)))) ;;;; eval>

(CDDR ==>

FORM)

FUSS)

SET-FUSS!

Bei s p i e l (DEFINE

F-l

(BILDE-FORMULAR

"Baw"

"Bescheid"

"Stand:

Alt")) ==>

eval>

(GET-TEXT

F-l)

==>

eval>

(GET-FUSS

(SET-FUSS!

F-l

"Bescheid" "Stand:

Neu"

F-l))

==>

"Stand:

Neu"

5. Abbildungsoption: Liste Für die Abbildungsoption „Liste" haben wir bisher den Konstruktor CONS, die Selektoren CAR und CDR sowie die Prädikate NULL? und ATOM? in unterschiedlichen Zusammenhängen genutzt. Es wurden z.B. neue Listen erzeugt, Elemente am Anfang einer Liste eingefügt, Listen aneinander gekoppelt, die Reihenfolge der Elemente umgedreht oder die Länge einer Liste bestimmt. Wir können die Liste im Sinne einer Signatur (vgl. Abschnitt 1.4) mit diesen Opera-

124

II. Konstruktionen (Analyse und Synthese)

tionen definieren (vgl. Bild 5-1). Dabei unterstellen wir eine endliche, nicht zirkuläre Liste.

Legende: CD -•-

::= Wertebereich ::= Operation

Bild 5-1. Signatur einer endlichen, nicht zirkulären Liste

Vergleicht man das Konzept der Liste mit dem der Menge, dann sind zwei Aspekte bedeutsam: o die Berücksichtigung der Reihenfolge und o das mehrfache Vorkommen gleicher Elemente (Multiplizität). Die Liste ist ein Konzept mit Reihenfolge und Multiplizität. Die Menge (engl.: set) ist ein Konzept ohne Reihenfolge und ohne Multiplizität. Konzepte ohne Reihenfolge, aber mit Multiplizität werden „bags" oder „multisets" genannt (z.B. in Smalltalk, vgl. Goldberg/Robson, 1983). Für den Selektor läßt sich Multiplizität auch mit Hilfe einer zirkulären Liste simulieren. Bei ihr ist der Nachfolger des (quasi) letzten Elementes das erste Element der Liste („Die Katze beißt sich in den Schwanz!"). Wir nutzen die zirkulären Liste im Beispiel „Verwalten von Arbeitsvorgängen" (Abschnitt 5.1). Dabei unterstellen wir einen Manager, der für größere Arbeiten eine Bearbeitsungsreihenfolge festlegt. Zur Wiederholung der bisher verwendeten ListenKonstrukte zeigen wir die Lösung zunächst ohne Listen-Mutatoren. Mit destruktiven Listenveränderungen definieren wir anschließend die Lösung abgebildet als zirkuläre Liste. Zur Konstruktion einer zirkulären Listen wird eine schon existierende Liste modifiziert. Zum leichteren Verstehen dieser Listen-Mutatoren befassen wir uns

5. Abbildungsoption: Liste

125

mit CONS-Zellen-Skizzen (vgl. Abschnitt 2.1, Bild 2.1-2). Mit diesen CONSZellen-Strukturen sind keinesfalls alle realisierten Implementationen einer Liste beschrieben (vgl. z.B. LISP-Maschinen; Anhang B). Eine übliche Abweichung ist die Reihenfolge von CAR- und CDR-Teil. Weil die Applikation des CDRKonstruktes häufiger vorkommt als die des CAR-Konstruktes, stellen z.B. in einer CONS-Zelle von 8 Byte Länge die ersten vier Bytes den CDR-Teil dar. Die Assoziationsliste (kurz A-Liste) behandeln wir anhand der Definition eines Kontos (Abschnitt 5.2). Die Eigenschaftliste (engl.: property list; kurz PListe) wird mit dem Beispiel „Laufmappen-Transportsteuerung" erläutert. In einem Bürogebäude ist für die Steuerung eines mechanischen Fließbandsystems ein Weg zwischen Start- und Ziel-Schreibtisch zu ermitteln. Dazu programmieren wir die beiden Suchalgorithmen „Depth First" und „A*" (Abschnitt 5.3). Beispiel:

Verwalten von

Arbeitsvorgängen

Ein Manager bearbeitet seine größeren Aufgaben nach einem Zyklusverfahren. Abhängig vom aktuellen Tagesgeschehen widmet er sich eine Zeit lang einem Vorgang, dann geht er zum nächsten Vorgang über. Hat er alle Vorgänge bearbeitet, fängt er wieder von vorn an. Fertige Vorgänge werden aus diesem Bearbeitungszyklus entfernt. Neue Vorgänge werden fallabhängig in den Zyklus eingeordnet. Zu einem Zeitpunkt t hat er z.B.folgende Vorgänge zu bearbeiten: 1. „Über die Investition Lagerhalle West entscheiden", 2. „Zeugnis für den Abteilungsleiter Meier diktieren" und 3. „Budgetplanung für die Zentralabteilung aufstellen". Die globale Variable *ARBEITSPLAN* stellt diese Vorgänge und ihre Reihenfolge durch eine Liste dar: eval >

(DEFINE_.^ARBEITSPLAN* ( L I S T " Ü b e r d i e I n v e s t i t i o n L a g e r h a l l e West e n t s c h e i d e n " " Z e u g n i s f ü r den A b t e i l u n g s l e i t e r M e i e r d i k t i e r e n " "Budgetplanung für die Zentralabtei1ung aufstellen")) ==> *ARBEITSPLAN*

Der Bearbeitungszyklus könnte auf der Grundlage dieser einfach strukturierten Liste, wie folgt, realisiert werden: eval>

( D E F I N E VORGANGSVERWALTUNG ( L A M B D A ( ) (LETREC ((ANFORDERUNG (LAMBDA ( ) ( B E G I N (WRITELN " Z e i g e n a e c h s t e n (ZYKLUS (LET

(NEWLINE) Vorgang? J/N:")) (READ)))

(LAMBDA ( L I S T E ) ((ANTWORT ( A N F O R D E R U N G ) ) ) (COND ( ( E Q ? ANTWORT * J )

126

II. Konstruktionen (Analyse und Synthese) (COND ;; U m s c h a l t e n ; ; der ((NULL?

auf den

Anfang

Li s t e LISTE)

(PRINT (CAR * A R B E I T S P L A N * ) ) (ZYKLUS (CDR * A R B E I T S P L A N * ) ) ) ;; Ü b e r g a n g a u f d a s n ä c h s t e ;; E l e m e n t (T

(PRINT

(CAR

(ZYKLUS (T (COND

((NULL? (T

LISTE)))))

"Ende"))))))

*ARBEITSPLAN*)

(ZYKLUS

LISTE)) (CDR

"Keine

Aufgaben")

^ARBEITSPLAN*))))) ==>

VORGANGSVERWALTUNG

Das Hinzufügen eines neuen Vorganges oder das Herausnehmen eines fertigen Vorganges ermöglicht der Mutator SET! in Verbindung mit dem Konstruktor CONS und dem Selektor CDR. Wir können als entsprechende Beispiele definieren: eval>

;; V o r g a n g h i n z u f ü g e n (SET! * A R B E I T S P L A N * (CONS ==>

eval>

("Urlaubstermin

"Urlaub festlegen" *ARBEITSPLAN*)) f e s t l e g e n " "Über die

..."

... )

;; E r s t e n V o r g a n g l ö s c h e n (SET! * A R B E I T S P L A N * (CDR * A R B E I T S P L A N * ) ) = = > ( " Ü b e r d i e ... " ... )

Die Veränderung des Symbolwertes leistet das SET!-Konstrukt, weil es in der jeweiligen Umgebung, hier USER-INITIAL-ENVIRONMENT, das Symbol an den neuen Wert bindet (vgl. Abschnitt 1.4.2). Wir können das SET!-Konstrukt auch als Mutator des Zeigers *ARBEITSPLAN* auffassen, der auf die erste CONS-Zelle der Liste zeigt (vgl. Bild 5.-1). Mit dem SET!-Konstrukt kann ein Zeiger, der auf eine CONS-Zelle zeigt, auf eine andere vorhandene CONS-Zelle gesetzt werden. Die Zeiger des CAR-Parts bzw. des CDR-Parts einer CONS-Zelle bleiben davon unberührt. Für ihre Änderung gibt es eigene Mutatoren, die Konstrukte SET-CAR! und und SET-CDR!. Mutatoren für die

CONS-Zelle:

eval> eval>



(SET-CAR! (SET-CDR!

) — > ) — >



mit:

::= S y m b o l i s c h e r A u s d r u c k , d e r e i n e C O N S Zelle als Wert hat. C m o d i f i z i e r t e - z e l 1 e > : : = C O N S - Z e l l e mit d e m W e r t von < s e x p r > im C A R - o d e r C D R - T e i l

5. Abbildungsoption: Liste

127

Die beiden Mutatoren SET-CAR! und SET-CDR! haben die Aufgabe, dem CAR- bzw. CDR-Teil einer existierenden CONS-Zelle einen neuen Wert zu geben. Dazu werten sie ihre Argumente aus. Da wir sie nur für diesen Nebeneffekt verwenden (sollten!), ist ihr eigener Wert prinzipiell nicht spezifiziert. /Hinweis: RPLACA und RPLACD. In vielen LISP-Implementationen, so z.B. in Common LISP, haben diese Mutatoren die klassischen Namen: RPLACA (Akronym für RePLAce the CAr of ...) und RPLACD (Akronym für RePLAce the CDt of...).] Beispiele:

evaf> eval> eval> eval> eval>

(DEFINE F00 '(A B C D)) ==> (A B C D) (SET-CAR! F00 (* 2 3)) ==> (6 B C D) (SET-CDR! F00 '(X Y)) ==> (6 X Y) (SET-CDR! F00 (* 3 3)) ==> (6 . 9) F00 ==> (6 . 9)

Die Mutatoren SET-CAR! und SET-CDR! können Tansparenz-Probleme bewirken. Nicht leicht durchschaubare Nebeneffekte sind formulierbar, wie das folgende Beispiel zeigt: eval> (DEFINE F00 (CONS (CONS T T) (CONS T T ) ) ) ==> ((#T . #T) #T . #1) eval> (DEFINE BAR (LET ((X (CONS T T))) (CONS X X))) ==> ((#T . #T) #T . #T) eval> (EQUAL? F00 BAR) ==> #T eval> (SET-CDR! (CDR F00) 'NEU) ==> (#T . NEU) eval> (SET-CDR! (CDR BAR) 'NEU) ==> (#T . NEU) Nachdem sowohl auf FOO wie auf BAR die gleiche SET-CDR .'-Formulierung angewendet wurde, erwartet man spontan, daß „Gleiches" weiterhin gleich ist, d.h. (EQUAL? FOO BAR) weiterhin den Wert #T hat. Dies ist jedoch nicht der Fall! eval> (EQUAL? FOO BAR) ==> () eval> FOO ==> ((#T . #T) #T . NEU) eval> BAR ==> ((#T . NEU) #T . NEU) Dieser Nebeneffekt leuchtet unmittelbar ein, wenn man das CONS-Zellen-Modell für FOO und BAR betrachtet. Das Bild 5-3 zeigt diese vor der Applikation des Mutators SET-CDR!. Problematisch sind Mutatoren immer dann, wenn ihre „Zerstörung" sich auf mehrfach genutzte „Objekte" bezieht. Als Merksatz formuliert: Sind Mutatoren dabei, dann ist „Gleiches durch Gleiches" nicht einfach ersetzbar. (Näheres zur Semantik destruktiver Konstrukte, vgl. z.B. Mason, 1986).

128

II. Konstruktionen (Analyse und Synthese)

Ausgangssituation: (DEFINE *ABEITSPLAN* (LIST " E l e m e n t 1" " E l e m e n t *ARBEITSPLAN*-

2" " E l e m e n t

3")) ==> *ARBEITSPLAN* NIL

->n v "Element

(SET! * A R B E I T S P L A N * ^ARBEITSPLAN*—i

(CDR

1"

v "Element

2"

*ARBEITSPLAN*)) ==> ("Element o-

• "Element

1"

"Element

3"

2" " E l e m e n t

o-

->ül

v " E l e m e n t 2"

3")

NIL

v "Element

3"

(SET! * A R B E I T S P L A N * (CONS " N e u e E l e m e n t " * A R B E I T S P L A N * ) ) = = > ( " N e u e E l e m e n t " " E l e m e n t 2" " E l e m e n t 3") *ARBEITSPLAN*—i

G

" Element

0-

1"

o-

"Element

• > u

2"

"Element

- D v "Neue

Element"

Legende: CAR- CDRPart Part

::=

o

::=

>

CONS-Zelle Zeiger

(Adresse)

::= N I L - Z e i g e r , Bild 5.-2.

entspricht:

o—>

NIL

NIL

SET!-Konstrukt - Ä n d e r u n g des Z e i g e r s auf e i n e e x i s t i e r e n d e C O N S - Z e l l e -

3"

5. Abbildungsoption: Liste

129

F00

BAR

crm

HCT] v #T Legende: Bild

v #T Vgl.

5-3.

V #T Bild

(EQUAL?

— >

V #T

ran V

V

V #T

V #T

tun

5-2. FOO

BAR)

==>

#T

5.1 Zirkuläre Liste Im obigen Beispiel „Verwalten von Arbeitsvorgängen" können wir mit dem SET-CDR!-Konstrukt den Bearbeitungszyklus einfach dadurch abbilden, daß der Zeiger des CDR-Parts der letzten CONS-Zelle von * ARBEITSPLAN* auf die erste CONS-ZELLE zeigt. Nehmen wir an, daß die Liste * ARBEITSPLAN* drei Elemente enthält, dann wäre formulierbar: eval>

;; A c h t u n g e n d l o s e B i 1 d s c h i r m a u s g a b e ! (SET-CDR! (CDDR *ARBEITSPLAN*) *ARBEITSPLAN*)

==>

...

Aufgrund des READ-EVAL-PRINT-Zyklus wird versucht die Liste * ARBEITSPLAN* auf dem Bildschirm auszugeben. * ARBEITSPLAN* stellt jetzt jedoch eine zirkuläre Liste (infinite Sequenz) dar, weil der letzte CDR-Part-Zeiger wieder auf die Anfangs-CONS-Zelle zeigt. Wir definieren daher zur Vermeidung der endlosen Ausgabe, wie folgt: eval>

(BEGIN

(SET-CDR! (CDDR *ARBEITSPLAN*) ^ARBEITSPLAN*) # T ) = = > # T ; R ü c k g a b e w e r t ist l e t z t e r B E G I N - A u s d r u c k

Daß *ARBEITSPLAN* an eine zirkuläre Liste gebunden ist, zeigt die Anwendung des EQ?-Konstruktes. eval>

(EQ? * A R B E I T S P L A N *

(CDDDR *ARBEITSPLAN*))

==>

#T

Das EQ?-Konstrukt stellt fest, ob die ihm übergebenen Zeiger auf dieselbe CONS-Zelle zeigen, d.h. denselben Wert haben. Die Identitätsentscheidung des EQ?-Konstruktes (vgl. Abschnitt 2.1) ist das Prüfen von Zeigern. Um die Elemente einer zirkulären Liste ausgeben zu können, ist ein eigenes

130

II. Konstruktionen (Analyse und Synthese)

PRINT-Konstrukt zu definieren, da das eingebaute PRINT-Konstrukt für zirkuläre Listen keine Terminierung enthält. Wir definieren z.B.: eval>

(DEFINE PRINT-ELEMENT (LAMBDA ( Z I R K U L A E R E _ L I S T E ) ( P R I N T (CAR Z I R K U L A E R E _ L I S T E ) ) ( L E T ((ANFORDERUNG (BEGIN (NEWLINE) (WRITELN " N a e c h s t e s E l e m e n t ? J/N : " ) (READ)))) (COND ( ( E Q ? ANFORDERUNG ' J ) ( P R I N T - E L E M E N T (CDR Z I R K U L A E R E _ L I S T E ) ) ) (T " E n d e ! " ) ) ) ) ==> PRINT-ELEMENT

Mit Hilfe der Mutatoren SET!, SET-CAR! und SET-CDR! ist die Verwaltung der Arbeitsvorgänge auf der Grundlage des Konzepts der zirkulären Liste im Programm 5.1-1. abgebildet. Dieses Programm hat folgende Struktur: e v a l > ( D E F I N E VERWALTEN - VON -ARBEITSVORGAENGEN (LAMBDA (Z_ L I S T E ) (LETREC ((ANFORDERUNG . . . ( R E A D ) ) (VORGANGSVERWALTUNG ; H a u p t f u n k t i on (LAMBDA ( Z I R K U L A E R E _ L I S T E ) ( L E T ((ANTWORT (ANFORDERUNG))) ;Steuerungscode; abfrage (COND ( ( E Q ? ANTWORT ' J A ) Nächster Vorgang ( ( E Q ? ANTWORT ' N E U ) Neuer V o r g a n g vgl. Bild 5.1-1/2 Lösche Vorgang ( ( E Q ? ANTWORT ' O K ) vgl. Bild 5.1-3/4 Programm beenden ( ( E Q ? ANTWORT ' S T O P ) Vorgang anzeigen (T ...)))))) A u f r u f der l o k a l e n (VORGANGSVERWALTUNG Z . L I S T E ) ) ) ) e n d - r e k u r s i ven H a u p t f u n k t i on = = > VERWALTEN-VON-ARBEITSVORGAENGEN!

Dokument 5 . 1 - 1 T i t e l : Programm VERWALTEN-VON-ARBEITSVORGAENGEN! E r s t e l l t : 09.09.89 ¡ l e t z t e Aenderung: 3.11.90 AI

VERWALTEN-VON-ARBEITSVORGAENGEN! s p e i c h e r t V o r g ä n g e in einer z i r k u l a e r e n L i s t e . A I . 1 D i e V o r g ä n g e können i n d e r g e s p e i c h e r t e n R e i h e n f o l g e nacheinander a n g e z e i g t werden. A I . 2 Der a n g e z e i g t e V o r g a n g i s t a u s d e r L i s t e e n t f e r n b a r . A2 E i n neuer Vorgang i s t i n d i e L i s t e A 2 . 1 Vor dem g e z e i g t e n V o r g a n g o d e r A 2 . 2 h i n t e r dem g e z e i g t e n V o r g a n g .

einfügbar:

5. Abbildungsoption: Liste

131

T1

Beispiel

Tl.l

Arbeitsplan: eval > (DEFINE * A R B E I T S P L A N * (LIST "Lieber die Investition Lagerhalle West e n t s c h e i d e n " "Zeugnis fuer den A b t e i l u n g s l e i t e r Meier d i k t i e r e n " "Budgetplanung fuer die Z e n t r a l a b t e i 1 u n g a u f s t e l l e n " ) ) ==> * A R B E I T S P L A N *

;;; T1.2 Applikati on: ;;; eval> ( V E R W A L T E N - V O N - A R B EI T S V O R G A E N G E N ! * A R B E I T S P L A N * ) ;;; ==> ... ¡Geführte B e n u t z e r e i n g a b e n (DEFINE VERWALTEN - VON-ARB EI T S V O R G A E N G E N ! (LAMBDA (Z_LISTE) (LETREC ((ANFORDERUNG (LAMBDA () (BEGIN (WRITELN " Naechster Vorgang? JA") (WRITELN " Neuen Vorgang e i n f u e g e n ? NEU") (WRITELN " Vorgang fertig b e a r b e i t e t ? OK") (WRITELN " Ende? STOP") (WRITELN " Zeige Vorgang? sonst") (NEWLINE) (READ)))) ;; H a u p t f u n k t i o n (VORGANGSVERWALTUNG (LAMBDA ( Z I R K U L A E R E _ L I S T E ) (LET ((ANTWORT (ANFORDERUNG))) ; S t e u e r u n g s c o d e a b f r a g e (COND ;;; Zum nächsten Vorgang ü b e r g e h e n ((EQ? ANTWORT 'JA) (VORGANGSVERWALTUNG ;;; Neuen Vorgang in die z i r k u l a e r e

(CDR Z I R K U L A E R E . L I S T E ) ) ) Liste einbauen (vgl. A2)

((EQ? ANTWORT 'NEU) (LET ( ( N E U E R - V O R G A N G (BEGIN (WRITELN "Bitte geben Sie Ihren " "Vorgang ein! (Zeichenkette) :") (NEWLINE) (READ))) ( N E U E - C O N S - Z E L L E (LIST " P L A T Z H A L T E R " ) ) ) (COND ;; Wenn die z i r k u l a e r e Liste nur aus ;; einer C O N S - Z e l l e besteht, dann kann :; d i e s e mit ihrem C A R - Z e i g e r auf den ;; neuen Vorgang zeigen. ((EQ? (CAR Z I R K U L A E R E _ L I S T E ) NIL) (SET-CAR! Z I R K U L A E R E _ L I S T E NEUER-VORGANG) (VORGANGSVERWALTUNG ZIRKULAERE_LISTE))

132

;;;

II. Konstruktionen (Analyse und Synthese)

Vorgang

; ; Vor dem a k t u e l l e n V o r g a n g den neuen ;; Vorgang e i n b a u e n ? ; v g l . A 2 . 1 ((EQ? 'JA (BEGIN (WRITELN " S o l l I h r neuer Vorgang " " v o r h e r e i n g f u e g t werden? J A " ) (NEWLINE) (READ))) ;; E r l ä u t e r u n g s i e h e B i l d 5 . 1 - 1 . (SET-CDR! NEUE-CONS-ZELLE (CDR Z I R K U L A E R E _ L I S T E ) ) (SET-CAR! NEUE-CONS-ZELLE (CAR Z I R K U L A E R E _ L I S T E ) ) (SET-CAR! ZIRKULAERE_LISTE NEUER-VORGANG) (SET-CDR! ZIRKULAERE_LISTE NEUE-CONS-ZELLE) (VORGANGSVERWALTUNG Z I R K U L A E R E _ L I S T E ) ) ;; E r l ä u t e r u n g s i e h e B i l d 5 . 1 - 2 . ;; v g l . A2.2 (T ( S E T - C D R ! NEUE-CONS-ZELLE (CDR Z I R K U L A E R E _ L I S T E ) ) (SET-CAR! NEUE-CONS-ZELLE NEUER-VORGANG) (SET-CDR! ZIRKULAERE_LISTE NEUE-CONS-ZELLE) (VORGANGSVERWALTUNG ZIRKULAERE_LISTE))))) a u s der z i r k u l ä r e n L i s t e e n t f e r n e n ( v g l . A I . 2 ) ( ( E Q ? ANTWORT ' O K ) ;; E r l ä u t e r u n g s i e h e B i l d 5 . 1 - 3 . (LETREC ((QUASI -LETZTE-ELEMENT (LAMBDA ( L I S T E N _ Z E I G E R A K T U E L L E _ Z E I G E R VORHERGEHENDE_POSITION) (COND ( ( E Q ? L I S T E N _ Z E I G E R AKTUELLE_ZEIGER) VORHERGEHENDE_POSITION) (T ( Q U A S I - LETZTE - ELEMENT LISTEN_ ZEIGER (CDR A K T U E L L E _ Z E I G E R ) (CDR V O R H E R G E H E N D E _ P O S I T I O N ) ) ) )))) ;; E r l ä u t e r u n g s i e h e B i l d 5 . 1 - 4 . (SET-CDR! (QUASI-LETZTE-ELEMENT ZIRKULAERE_LISTE (CDDR Z I R K U L A E R E _ L I S T E ) (CDR Z I R K U L A E R E _ L I S T E ) ) (CDR Z I R K U L A E R E _ L I S T E ) ) (SET-CAR! ZIRKULAERE_LISTE NIL) (SET! ZIRKULAERE_LISTE (CDR Z I R K U L A E R E . L I S T E ) ) (VORGANGSVERWALTUNG Z I R K U L A E R E _ L I S T E ) ) )

5. Abbildungsoption: Liste ;;; P r o g r a m m

133

beenden (C EQ? A N T W O R T ' S T O P ) CWRITELN "Vorgangsverwaltung *THE-NON-PRINTING-OBJECT*)

;;; V o r g a n g

anzeigen (T

(vgl.

AI.1)

(WRITELN "Bitte bearbeiten Sie: " (CAR Z I R K U L A E R E _ L I S T E ) ) (VORGANGSVERWALTUNG ZIRKULAERE.LISTE)))))))

;; S t a r t d e r l o k a l e n e n d r e k u r s i v e n (VORGANGSVERWALTUNG Z.LISTE)))) Program

5.1-1

beendet")

Verwalten

von

Hauptfunktion

Arbeitsvorgängen

Zur Erläuterung wird angenommen, daß beim Aufruf des Programms 5.1-1 das Argument *ARBEITSPLAN* an eine zirkuläre Liste mit drei Elementen gebunden ist. eval > ( V E R W A L T E N - V O N - A R B E I T S V O R G A E N G E N !

*ARBEITSPLAN*)

==>

...

Damit ist zunächst die LAMBDA-Variable Z_ LISTE an die zirkuläre Liste von * ARBEITSPLAN* gebunden. Über die Applikation von (VORGANGANGSVERWALTUNG Z_ LISTE) wird die LAMBDA-Variable von VORGANGSVERWALTUNG, also ZIRKULAERE. LISTE, ebenfalls an diese zirkuläre Liste gebunden. Anders formuliert: Der Zeiger Z I R K U L Ä R E . LISTE weist auf dieselbe CONS-Zelle wie der Zeiger * ARBEITSPLAN*. Unterstellen wir nun, daß der Benutzer mit der Eingabe NEU antwortet und seinen neuen Vorgang am „Anfang" der zirkulären Liste einfügen will, dann zeigt das Bild 5.1-1 die Änderung der Zeiger auf die CONS-Zellen. Nochmals sei darauf hingewiesen, daß es sich um ein graphisches Modell für das Verstehen der Mutatoren SET!, SET-CAR! und SET-CDR! handelt. Im Bild 5.1-1 werden mit der Anwendung der Mutatoren SET-CAR! und SET-CDR! auf ZIRKULAERE. LISTE die Inhalte derjenigen CONS-Zellen geändert, auf die sowohl die LAMBDA-Variable Z I R K U L A R E . LISTE selbst als auch die globale Variable *ARBEITSPLAN* zeigen. Nach Beendigung des Programms wird die Bindung der LAMBDA-Variable wieder aufgehoben. Die Bindung der globalen Variable * ARBEITSPLAN* bleibt bestehen, d.h. es gibt weiterhin in der „top level" Umgebung (hier USER-INITIAL-ENVIRONMENT) die Assoziation zwischen dem Symbol *ARBEITSPLAN* und den modifizierten CONS-Zellen. Das Bild 5.1-2 zeigt das Einfügen eines neuen Vorgangs nach dem aktuellen Vorgang. Hier ist wieder das Aufbrechen einer CONS-Zellen-Kette und das Einfügen einer zusätzlichen CONS-Zelle durch den Mutator SET-CDR! abgebildet.

134

II. Konstruktionen (Analyse und Synthese)

Bei der zirkulären Liste können wir mit dem CDR-Konstrukt auf die nachfolgende CONS-Zelle positionieren. In Pfeilrichtung läßt sich der Zirkel durchlaufen, in der entgegengesetzten Richtung nicht. Soll der CDR-Part einer vorgehenden CONS-Zelle modifiziert werden, z.B. um die aktuelle CONS-Zelle zu überspringen, dann müssen wir eine Hilfslösung wählen. Wir durchlaufen den Zyklus in der CDR-Richtung so weit, bis wir an die vorhergehende CONS-Zelle kommen. Diese Hilfslösung bildet das Konstrukt QUASI-LETZTE-ELEMENT ab (vgl. Bild 5.1-3). Ausgangssituation:

V

V

"Element_l" NEUE-CONS-ZELLE

>

o

NIL

v "PLATZHALTER"

"Element_2"

V

"Element_3"

5. Abbildungsoption: Liste

135

(SET-CDR! NEUE-CONS-ZELLE (CDR ZIRKULAERE_LISTE)) = - >

...

v

"PLATZHALTER"

(SET-CAR! NEUE-CONS-ZELLE (CAR ZIRKULAERE_LISTE)) ==> (SET-CAR! ZIRKULAERE_LISTE NEUER-VORGANG)) —> . . .

v "Element_l"

...

136

II. Konstruktionen (Analyse und Synthese)

(SET-CDR!

ZIRKULAERE_LISTE

NEUE-CONS-ZELLE))

— >

...

v "Element_l" Legende:

Vgl.

Bild 5.1-1.

Bild

5-2.

Neuen Vorgang am "Anfang" der zirkulären einfügen (vgl. P r o g r a m m 5 . 1 - 1 )

Liste

Ausgangssituation:

v "Element_l" NEUE-CONS-ZELLE—>

o

NIL

v "PLATZHALTER"

v "Element_2"

v "Element_3"

5. Abbildungsoption: Liste

(SET-CDR! (SET-CAR! (SET-CDR!

137

N E U E - C O N S - Z E L L E (CDR Z I R K U L A E R E _ L I S T E ) ) = = > N E U E - C O N S - Z E L L E N E U E R - V O R G A N G ) - = > ... ZIRKULAERE_LISTE NEUE-CONS-ZELLE)) — > ...

...

v

NEUER-VORGANG Legende:

Vgl. B i l d

Bild 5.1-2.

Neuen (vgl.

5-2. Vorgang in die zirkuläre Programm 5.1-1)

Liste

einfügen

Das QUASI-LETZTE-ELEMENT-Konstrukt gibt als Wert einen Zeiger auf die CONS-Zelle wieder, die der aktuellen CONS-Zelle in der zirkulären Liste vorhergeht. Zum Herausnehmen einer CONS-Zelle aus der zirkulären Liste werden mit den Mutatoren SET-CAR! und SET-CDR! die Inhalte der gemeinsamen CONS-Zellen von * ARBEITSPLAN* und ZIRKULAERE_ LISTE modifiziert (vgl. Bild 5.1-4). Mit dem Mutator SET!, angewendet auf die LAMBDA-Variable ZIRKULAERE_ LISTE, wird jedoch nur dieser Zeiger geändert, d.h. auf die um das erste Element verkürzte zirkuläre Liste gesetzt. Der Zeiger * ARBEITSPLAN* bleibt davon unberührt. Wird nach dem Löschen eines Vorganges das Programm VERWALTENVON-ARBEITS VORGAENGEN! mit dem Argument * ARBEITSPLAN* ein zweites Mal gestartet, dann wäre das erste Element von *ARBEITSPLAN* NIL (vgl. Bild 5.1-4). Um diesen Effekt zu vermeiden, muß der Zeiger •ARBEITSP L A N * modifiziert werden. Dies kann mit dem Mutator SET! in der (Definitions-)Umgebung von * ARBEITSPLAN* erfolgen. Dazu könnte z.B. das Programm VERWALTEN-VON-ARBEITSVORGAENGEN an der Stelle der Programm-ENDE-Behandlung, wie folgt, geändert werden: bisher: ((EQ? A N T W O R T 'STOP) (WRITELN "Vorgangsverwaltung *THE-NON-PRINTING-OBJECT*)

beendet")

138

II. Konstruktionen (Analyse und Synthese)

Funktion QUASI - LETZTE-ELEMENT: (LETREC ((QUASI - L E T Z T E - E L E M E N T (LAMBDA (LISTEN_ZEIGER AKTUELLE_ZEIGER VORHERGEHENDE_POSITION) ;; Das quasi l e t z t e E l e m e n t der z i r k u ;; l ä r e n L i s t e w i r d e r k a n n t d u r c h Ver;; g l e i c h m i t t e l s EQ? wobei ;; A K T U E L L E _ Z E I G E R und ;; V O R H E R G E H E N D E . P O S I T I O N " s y n c h r o n " ;; die Liste e n t l a n g w a n d e r n . ( C O N D ((EQ? L I S T E N _ Z E I G E R A K T U E L L E _ Z E I G E R ) VORHERGEHENDE,POSITION) (T (QUASI - L E T Z T E - E L E M E N T LISTEN_ZEIGER (CDR A K T U E L L E _ Z E I G E R ) (CDR V O R H E R G E H E N D E _ P 0 S I T I O N ) ) ) ) ) ) ) ... )

Legende:

Vgl.

Bild 5.1-3.

Änderung:

Bild

5-2.

Hi1fslösung um die quasi letzte zirkulären Liste zu ermitteln (vgl. P r o g r a m m 5 . 1 - 1 )

((EQ? A N T W O R T 'STOP) (WRITELN "Vorgangsverwaltung ZIRKULAERE_LISTE)

CONS-Zelle

einer

beendet")

Der Rückgabewert des Programms VERWALTEN-VON-ARBEITSVORGAENGEN! ist dann stets der Zeiger ZIRKULAERE LISTE, so daß dann formuliert werden könnte: eval>

( B E G I N (SET! * A R B E I T S P L A N * ( V E R W A L T E N - VON - A R B E I T S V O R G A E N G E N ! * A R B E I T S P L A N * ) )

#T)

==>#T

/Hinweis: Einerseits ist diese umständliche Formulierung mit der zweimaligen Nennung von * ARBEITSPLAN* durch die Definition eines Makros syntaktisch vereinfachbar. Für eine solche Makro-Nutzung vgl. z.B. Abschnitt 13.3. Anderer-

5. Abbildungsoption: Liste

139

seits könnte * ARBEITSPLAN* direkt und nicht über die LAMBDA-Schnittstelle des Konstruktes VERWALTEN-VON-ARBEITSVORGAENGEN! modifiziert werden, vgl. z.B. die Konstruktion des Programms 6.3-1.7 (SET-CDR!

(SET-CAR!

(QUASI - L E T Z T E - E L E M E N T ZIRKULAERE_LISTE ¡Listenzeiger (CDDR ZIRKULAERE_LISTE) ¡aktueller Zeiger (CDR Z I R K U L A E R E _ L I S T E ) ) ¡vorhergehende Position (CDR Z I R K U L A E R E _ L I S T E ) ) — > ...

ZIRKULAERE_LISTE

(SET! Z I R K U L A E R E _ L I S T E

NIL)

¡notwendig, falls zirkuläre L i s t e n u r aus e i n e r CONS-Zel1e besteht. (CDR Z I R K U L A E R E _ L I S T E ) ) -=>...

"Element_2" Legende: Bild

Vgl.

5.1-4.

Bild

'Element_3"

5-2.

Vorgang aus der zirkulären (vgl. Programm 5.1-1)

Liste

entfernen

Wie die Bilder 5.1-1 bis 4 dokumentieren, verändert das Konstrukt VERWALTEN-VON-ARBEITS VORGAENGEN! die CONS-Zellen seines Argumentes. Hier ist gerade diese Modifikation der gewünschte Effekt. In anderen Fällen allerdings ist sicherzustellen, daß die CONS-Zellen eines Argumentes nicht verändert werden. Haben wir keinen Einfluß auf ein Konstrukt, d.h. können wir seine Implementation nicht beeinflußen und wollen trotzdem sichergehen, daß die CONS-Zellen des Arguments nicht modifiziert werden, dann können wir diese nicht direkt dem fremden Konstrukt übergeben. Keine LAMBDA-Variable darf direkt an diese CONS-Zellen gebunden werden. Es ist zunächst eine Kopie des Argumentes durch neue CONS-Zellen zu konstruieren und diese Kopie dient

140

II. Konstruktionen (Analyse und Synthese)

dann als Bindungswert. Wird die Kopie verändert, bleibt das Original erhalten. Die Sicherstellung des Argumentes ist damit erreicht. Eine solche Kopie ist der Rückgabewert des COPY-Konstruktes.

Fall 1 : Modifikation der CONS-Zellen des Argumentes eval> eval>

( D E F I NE F 0 0 ( D E F I NE BAZ

eval>

(EQ?

F00

' ( A (B C) D ) ) F 0 0 ) = = > BAZ

BAZ)

eval>

(SET-CDR!

eval>

FOO = = >

= = > #T

BAZ

(A

==>

"(E))

F00 ;F00 i s t Argument f ü r ; DEFINE-Konstrukt

das

; F 0 0 und BAZ z e i g e n a u f ; dieselben CONS-Zellen ==>

(A

E)

E)

¡Modifikation

von

FOO !

Fall 2: Modifikationsschutz durch COPY-Konstrukt eval> eval> eval> eval> eval> eval>

( D E F I N E FOO ' ( A ( B C ) D ) ) = = > FOO ( D E F I N E BAZ (COPY F O O ) ) = = > BAZ ( E Q ? FOO B A Z ) = = > ( ) ;FOO und BAZ z e i g e n a u f v e r ; schiedene CONS-Zellen ( E Q U A L ? FOO B A Z ) = = > # T ; P r ü f t Gl e i c h h e i t d e r E l e ; mente, n i c h t d i e I d e n t i t ä t ; der CONS-Zellen ( S E T - C D R ! BAZ ' ( E ) ) = = > ( A E) FOO = = > ( A ( B C) D) ¡ K e i n e M o d i f i k a t i o n v o n FOO

Fall 3: Zirkuläre eval> eval>

Liste

( D E F I N E FOO ' ( A ( B C ) D ) ) = = > FOO ;; Z i r l u l ä r e L i s t e - Vermeidung e i n e r (BEGIN

(SET-CDR!

(CDDR

eval>

(BEGIN

(DEFINE

eval>

(BEGIN

(SET-CAR!

BAZ

FOO)

eval>

(CAR

(CDDDDR

BAZ))

==>

eval>

(CAR

(CDDDDR

FOO))

==>

eval>

( B E G I N ( S E T ! BAZ = = > ERROR . . .

(CADR

(COPY

FOO)

FOO) # T )

#T)

==>

#T

BAZ)

'E)

#T)

(E

C)

(E

C)

FOO))

endlosen ==>

==>

Ausgabe

#T #T

¡Nebeneffekt

!

#T) ¡ R e k u r s i o n zu t i e f , ; Stack-Überlauf

daher

Bei einer zirkulären Liste ist das eingebaute COPY-Konstrukt nicht anwendbar, da beim Kopieren der CONS-Zellen kein Ende erkannt wird (analog zum PRINT-Konstrukt). Hier ist die Kopie selbst mit Hilfe des CONS-Konstruktes zu erstellen. Wir halten fest: Wenn ein Listenelement verändert wird, können dadurch gleichzeitig andere Listen davon betroffen sein. Zu empfehlen ist daher, eine Liste, die als Konstante dient, prinzipiell nicht zu verändern (vgl. Charniak u.a., 1987, p. 23). Man stelle sich vor, eine solche Konstante befindet sich in einem Lesespeicher (engl.: read only memory). Jeder Änderungsversuch führt zu ei-

141

5. Abbildungsoption: Liste

nem Hardwarefehler. Das folgende Beispiel macht nochmals die Gefahr deutlich, den Nebeneffekt der Modifikation zu übersehen. eval > (DEFINE PARLAMENTS-FRAKTION? (LAMBDA ( X ) (MEMBER X ' ( S P D CDU FDP G R U E N E ) ) ) ) ==> PARLAMENTS-FRAKTION? e v a l > (DEFINE SO-NICHT! (LAMBDA ( X ) (LET ( ( T E I L ( P A R L A M E N T S - F R A K T I O N ? X ) ) ) (COND ( T E I L ( S E T - C A R ! T E I L N I L ) ) (T N I L ) ) ) ) ) = = > S O - N I C H T ! e v a l > ( P A R L A M E N T S - F R A K T I O N ? ' F D P ) = = > (FDP GRUENE) e v a l > ( S O - N I C H T ! ' F D P ) = = > ( ( ) GRUENE) eval > (PARLAMENTS-FRAKTION? 'FDP) ==> () e v a l > ;; " P r e t t y P r i n t " (PP P A R L A M E N T S - F R A K T I O N ? ) = = > # = (LAMBDA ( X ) (MEMBER X ' ( S P D CDU ( ) G R U E N E ) ) )

5.2 Assoziationsliste Wenn wir auf der Basis der einfachen Liste Konstruktoren, Selektoren, Prädikate und Mutatoren definieren, dann ist vorab die Listenstruktur festzulegen, d.h. die Bedeutung der einzelnen Listenelemente ist zu fixieren. Wenn wir annehmen, daß es sich bei einer Liste um ein Konto handelt, dann ist beispielsweise festzulegen, daß das 1. Element die Kontonummer, das 2. Element den Kontentyp, das 3. Element den Namen des Kontoinhabers etc. abbilden. Wir betrachten Kontonummer, Kontentyp, Name des Kontoinhabers etc. als Attribute A zum Objekt Konto. Den einzelnen Listenelementen sind dann entsprechend ihrer Reihenfolge die einzelnen Attributbeschreibungen zuzuordnen. Objekt

Konto:

(



...

)

Beschreibung der Attribute: Wert i n der L i s t e

Bedeutung



: : = Kontonummer ::= Kontentyp : : = Name des K o n t o i n h a b e r s

: : = Datum der

letzten

Buchung

/Hinweis: Datenlexikon. Zur Beschreibung eines Attributes gehört in der Regel auch die Definition des Wertebereichs, z.B: Kontonummer, 4-stellig numerisch, positiv ganzzahlig, (AND ( eval>

eval>

(DEFINE

KONTONUMMER (LAMBDA

(LISTE)

(LIST-REF LISTE 0 ) ) ) = = > KONTONUMMER ( D E F I N E KONTENTYP (LAMBDA ( L I S T E ) ( L I S T - R E F L I S T E 1 ) ) ) = = > KONTENTYP ( D E F I N E NAME-DES-KONTOINHABERS (LAMBDA ( L I S T E ) ( L I S T - R E F L I S T E 2 ) ) ) = = > NAME-DES-KONTOINHABERS

( D E F I N E DATUM-LETZTE - BUCHUNG (LAMBDA (LIST-REF

(LISTE)

L I S T E N) = = > DATUM-LETZTE - BUCHUNG

Damit der jeweilige Selektor das richtige Attribut ermittelt, ist sicherzustellen, daß die Position eines Attributs sich in der Liste nicht verschiebt. Die L A M B DA-Variable LISTE der obigen Selektoren ist an ein Konto zu binden, bei dem die definierte Struktur statisch ist. Löschen eines Attributwertes setzt voraus, daß die Listenelemente ihre Position nicht ändern. Daher ist der aktuelle Attributwert beim Löschen durch einen vereinbarten Ersatzwert (engl.: default value) bzw. Löschwert, z.B. NIL, zu ersetzen. Die Maxime „Position ist Informationsträger" ist statisch und daher nur bedingt geeignet für das prinzipiell dynamische Listenkonzept. Dynamisch ist die Liste in bezug auf das Verschieben von Elementen, beim Einfügen und Löschen von Elementen. Sind viele Attribute abzubilden, dann ist die einfache Liste kaum geeignet. Ihr dynamisches Konzept erweist sich als nachteilig, wenn von den einzelnen Objekten (hier: Konten) nur wenige Attribute einen Wert ungleich dem Ersatzwert haben. In solchem Fall hat die Liste viele Platzhalter, die nur deshalb erforderlich sind, um die fest vorgegebene Position der Elemente zu sichern. Dadurch wird erstens die Selektionszeit verlängert und zweitens das Verstehen des Programms unnötig erschwert. Zumindest bei wenig „besetzten" Attributen ist es angebracht, die Position in einer Liste nicht als Informationsträger zu nutzen. Die Information, um welches Attribut es sich handelt, kann dem Element zugeordnet werden, wenn die Ele-

5. Abbildungsoption: Liste

143

mente jetzt selbst Listen sind. Eine solche Verknüpfung des Attributs mit seinem Wert wird als Assoziations-Ziste, kurz A-Liste, bezeichnet. Eine A-Liste ist eine Liste mit eingebetteten Sublisten, in denen das erste Element jeweils die Aufgabe eines Schlüsselbegriffs (key) übernimmt. /Hinweis: Allgemein können A-Listen als Listen aufgefaßt werden, deren Elemente Punkt-Paare (engl.: dotted pairs) s i n d j In unserem Beispiel könnte das Konto dann folgende Listenstruktur haben: Objekt Konto:

((KONTONUMMER < a _ l > ) (KONTOTYP ) (NAME - DES -KONTOINHABERS )

(DATUM-LETZTElBUCHUNG

))

Zur Konstruktion der Selektoren, die auf der Basis A-Liste arbeiten, können wir eingebaute Zugriffskonstrukte nutzen z.B. in Scheme ASSOC, ASSQ oder ASSV. Solche eingebauten Konstrukte sorgen für eine effiziente Selektion, da sie die jeweiligen Implementationsdetails nutzen können. Ihre prinzipielle Funktion verdeutlicht das selbstdefinierte Konstrukt MEIN-ASSQ: e v a l > (DEFINE MEIN-ASSQ (LAMBDA (KEY A _ L I S T E ) (COND ((NULL? A_ L I S T E ) N I L ) ( ( E Q ? KEY (CAAR A _ L I S T E ) ) ¡Vergleichsoperation (CAR A . L I S T E ) ) (T (MEIN-ASSQ KEY (CDR A _ L I S T E ) ) ) ) ) ) ==>MEIN-ASSQ

In dem Konstrukt MEIN-ASSQ fußt die Vergleichsoperation zwischen dem Wert, an den KEY gebunden ist, und dem ersten Element einer Subliste auf dem EQ?-Konstrukt. Unterstellen wir, daß der Wert von KEY nicht nur ein Symbol sein kann, sondern z.B. eine Liste, dann ist für die Vergleichsoperation das EQUAL?-Konstrukt zu verwenden. In solchem Fall wollen wir die Subliste selektieren, deren erstes Element „gleich" der Liste von KEY ist. Da hier Gleichheit nicht Identität bedeutet (d.h. nicht die identischen CONS-Zellen für beide Listen voraussetzen soll), ist EQUAL? statt EQ? zu wählen. Die Benutzung der eingebauten Selektoren für A-Listen setzt daher die Kenntnis der Vergleichsoperation voraus. [PC Scheme: ASSOC testet mit EQUAL?, ASSV mit EQV? und ASSQ mit EQ?./ Passende Selektoren für das Kontobeispiel können mit dem MEIN-ASSQ-Konstrukt, wie folgt, definiert werden:

144

II. Konstruktionen (Analyse und Synthese)

eval> (DEFINE KONTONUMMER (LAMBDA (A_LISTE) (MEIN-ASSQ 'KONTONUMMER A.LISTE))) ==> KONTONUMMER eval> (DEFINE KONTOTYP (LAMBDA (A_LISTE) (MEIN-ASSQ 'KONTOTYP A_LISTE))) ==> KONTOTYP eval> (DEFINE NAME-DES-KONTOINHABERS (LAMBDA (A_LISTE) (MEIN-ASSQ 'NAME-DES-KONTOINHABERS A.LISTE))) ==> . . . /Hinweis: Subliste als Selektionswert. Die A-Listen-Selektoren haben im Trefferfall die ganze Subliste als Wert und nicht nur den zum Schlüssel assozierten Wert. Vermieden wird damit die Doppeldeutigkeit im Fall NIL. Ist ein Schlüssel mit dem Wert NIL verknüpft, dann wäre sonst der Rückgabewert gleich dem Wert "kein Treffer".; Eine A-Liste, die z.B. keine Eintragung für KONTOTYP enthält, kann im Gegensatz zur einfachen Listenstruktur nun problemlos als Argument für den Selektor NAME-DES-KONTOINHABERS dienen. eval> (DEFINE *K007* '((KONTOTYP 'SACHMITTEL) (KONTONUMMER 4711))) ==> *K007* eval> (NAME-DES-KONTOINHABERS *K007*) ==> NIL eval> (KONTONUMMER *K007*) ==> (KONTONUMMER 4711) Zum Konstruieren und Ändern einer A-Liste stehen dieselben Konstrukte wie für die einfache Liste zur Verfügung: CONS, LIST, SET-CAR!, SET-CDR! etc. Auf die A-Liste ausgerichtete Konstruktoren und Mutatoren sind selbst zu definieren. Ein Prädikat zum Erkennen einer A-Liste kann, wie folgt, definiert werden: eval> (DEFINE A-LISTE? (LAMBDA (OBJEKT) (LETREC ((LEERE-A-LISTE? (LAMBDA (X) (EQUAL? X (LIST NIL)))) (NUR-EINE-SUBLISTE? (LAMBDA (X) (AND (PAIR? X) (NULL? (CDR X)) (PAIR? (CAR X))))) (A-L? (LAMBDA (X) (COND ((OR (NOT (PAIR? X)) (NULL? X)) NIL) ((OR (LEERE-A-LISTE? X) (NUR-EINE-SUBLISTE? X)) T) ((PAIR? (CAR X)) (A-L? (CDR X))) (T NIL))))) (A-L? OBJEKT))) ==> A-LISTE? eval> eval> eval> eval>

(A-LISTE? (A-LISTE? (A-LISTE? (A-LISTE?

(LAMBDA () (+ 1 2 ) ) ) ==> () ' ( ( ) ) ) ==> #T '(A (B) (C 3 ) ) ) ==> () '((A 10) (B 9) (C 3 ) ) ) ==> #T

Bei der Definition eines A-Listen-Konstruktors ist zu entscheiden, ob die A-Liste dasselbe Attribut (denselben Schlüsselbegriff) mehrfach enthalten kann

5. Abbildungsoption: Liste

145

oder nicht (Multiplizität). Der oben skizzierte Selektor ASSQ wäre nicht in der Lage, eine zweite Ausprägung desselben Attributs zu selektieren, wie das folgende Beispiel zeigt. eval>

(DEFINE

*K001*

eval>

' ( ( S T R A S S E "Fuhrenkamp 2a") (ORT " S c h i f f d o r f " ) ( P L Z 2 8 5 8 ) (TELEFON " 0 4 7 0 3 / 5 2 7 0 " ) ) ) ==> * K 0 0 1 * ( D E F I N E C 0 N S - A - L I S T E ( L A M B D A ( N E U E _ E L E M E N T A_ L I S T E ) (COND ( ( P A I R ? N E U E _ E L E M E N T ) ( C O N S N E U E _ E L E M E N T A . L I S T E ) ) (T ( C O N S ( L I S T N E U E . E L E M E N T ) A_ L I S T E ) ) ) ) ) ==> CONS-A-LISTE

eval>

(SET!

*K001*

eval>

(ASSQ

'TELEFON

(CONS-A-LISTE *K001*)

==>

'(TELEFON (TELEFON

"0421/63171"

*K001*))

"0421/63171")

Die Möglichkeit, A-Listen mit Attributen zu definieren, die mehrfach vorkommen können, setzt eine problemgerechte Festlegung für den Verbund von Konstruktoren, Selektoren, Prädikaten und Mutatoren voraus. Beispielsweise eignen sich A-Listen mit Multiplizität zum Abbilden von aktuellen Werten gemeinsam mit ihren vorherigen Werten (d.h. ihrer Historie). Im Fall der globalen Variable * K 0 0 1 * bleibt die alte Telefonnummer bestehen. Existierende Konstrukte greifen mit ihrem Selektor TELEFON ohne Änderung auf die aktuelle Telefonnummer zu. Die Historie, d.h. die zunächst unter dem Attribut TELEFON gespeicherte Telefonnummer, kann z.B. für Revisionszwecke über einen entsprechend definierten Selektor wiedergewonnen werden. Sind die Schlüssel einer A-Liste Symbole und kommt jeder Schlüssel nur einmal in der A-Liste vor, dann bietet sich als Alternative die Eigenschaftsliste an (vgl. Abschnitt 5.3). Sind die Schlüssel nicht nur Symbole, dann ist in modernen LISP-Systemen die „Hash"-Tabelle eine Alternative. Für große A-Listen bieten sie eine Effizienzsteigerung, da die Zugriffszeit auf eine Assoziation (quasi) konstant ist. Wir skizzieren die Hash-Option für PC Scheme im folgenden Exkurs. /Exkurs: Hash-Tabelle in PC Scheme. Für die Elemente der A-Liste FOO berechnen wir mit dem OBJECT-HASH-Konstrukt eine signifikante Integerzahl. Unter Angabe dieser Integerzahl selektiert das OBJECT-UNHASH-Konstrukt das Element in der Hash-Tabelle. eval>

eval>

( D E F I N E FOO '((A ("More" (#\a (4711 ( ( A B C ) (#(BONN B E R L I N )

"LAMBDA Das u l t i m a t i v e Denkmodell") ( M e a n s Of R e q u i r e m e n t s Engineering)) (LAMBDA ( X ) ( P R I N T X) X ) ) "Manchmal s e h r n o t w e n d i g " ) ( 1 2 3 4 5 6 7 8 9 1 2 3 4)) "Hauptstadt von?")))

(DEFINE HASH-A-LISTE (LAMBDA (HASH_RICHTUNG A _ L I S T E ) (MAP (LAMBDA ( X ) ( L I S T (HASH_RICHTUNG (HASH.RICHTUNG A _ L I S T E ) ) ) ==> HASH-A-LISTE

(CAR X ) ) (LIST-REF

X

1))))

146

II. Konstruktionen (Analyse und Synthese)

eval>

( H A S H - A - L I S T E OBJECT-HASH F00) ==> ( ( 720896 1507328) (1572864 1638400) (1114112 1769472) ( 1 8 3 5 0 0 8 1900544)

eval>

(EQUAL? F00 ( H A S H - A - L I S T E 0BJECT-UNHASH (HASH - A - L I S T E OBJECT-HASH F 0 0 ) ) ) ==> #T

( 983040 (1966080

1703936) 2031616))

]

Aus geschachtelten A-Listen sind Rahmen (engl.: frames) konstruierbar, die eine mehrstufige Struktur darstellen (vgl. Bild 5.2-1). Mit solchen Rahmen hat Marvin Minsky ein Paradigma zur Abbildung von „Wissen" vorgeschlagen (Minsky, 1975). Ein Rahmen besteht aus Einschüben (engl.: slots). Die Einschübe haben Facetten (engl.: facets), die wiederum Werte haben, die voreingestellt sein können. Die Werte selbst können weiter strukturiert sein, aber auch Funktionen umfaßen. Wir können z.B. ein Konto als einen Rahmen definieren. Alle Rahmen fassen wir hier in einer A-Liste * R A H M E N - D A T E N B A N K * zusammen. Es sei angemerkt, daß es zweckmäßiger ist, den einzelnen Rahmen als Eigenschaft einer Kontonummer zu verwalten. Die Konstrukte für eine solche Bindung behandeln wir im folgenden Abschnitt Eigenschaftsliste.

Detail i erungsgrad

( 1( {( (I ) 1 ))

Legende: BNF-Notation ( v g l . Abschnitt 2.2)

Rahmen - Name o b e r s t e Ebene d e r E i n h e i t Ei n s c h u b - Name n ä c h s t e Ebene (Komponenten)

Facetten-Name A t t r i b u t e e i n e r Komponente

Wert Ausprägungen der A t t r i b u t e

Bild 5.2-1.

Rahmen konstruiert

aus

geschachtelten

e v a l > ( D E F I N E * RAHMEN - DATENBANK* '(RAHMEN (K001 (KONTO-TYP (GRUPPE SACHKONTO)) (KONTO-INHABER (NAME KRAUSE) (TELEFON 4 7 1 1 ) ) (LETZTE-BUCHUNG (DATUM 9 0 0 6 0 1 ) (BUCHUNGS-ART AUSZAHLUNG) (DM 5 . ) ) (EINZAHLUNGEN (DM-DATUM ( 1 0 . 900110) ( 5 5 . 9 0 0 2 1 2 ) ) ) (AUSZAHLUNGEN (DM-DATUM ( 5 . 9 0 0 6 0 1 ) ) ) (SALDO (DM 6 0 . ) ) )

A-Listen

147

5. Abbildungsoption: Liste (K002

(KONTO-TYP (GRUPPE HAUSHALT)) (KONTO-INHABER (NAME E I S E R ) ) (LETZTE-BUCHUNG (DATUM 9 0 0 7 0 1 ) (BUCHUNGS-ART EINZAHLUNG) (DM 1 0 5 . ) ) (EINZAHLUNGEN (DM-DATUM ( 1 0 5 . 9 0 0 7 0 1 ) ) ) (SALDO (DM 1 0 5 . ) ) ) ) ) = = > *RAHMEN-DATENBANK*

5.3 Eigenschaftsliste Die Eigenschaftsliste, kurz P-Liste („Property LIST') genannt, ist das klassische Konzept zur Abbildung eines Lexikons. Mit der Definition eines Symbols (Literalatoms) wird eine an das Symbol gebundene Eigenschaftsliste verfügbar. Jedes Symbol hat eine P-Liste, die zunächst leer ist. Ziehen wir eine Analogie zur Kernspaltung in der Physik, dann entsprechen den Neutronen, Elektronen, Protonen, die Zellen des (Literal-)Atoms. Abhängig vom jeweiligen LISP-System und seiner Implementation faßt das Symbol bestimmte Zellen (slots) zusammen, d.h. einem Symbol sind Speichermöglichkeiten für mehrere Zwecke zugeordnet (vgl. Bild 7.3-1). Bisher haben wir den Wert eines Symbols betrachtet. Dieser wird mit der „Wertzelle" (engl.: value cell) abgebildet. Beim Evaluieren eines Symbols (vgl. EVAL-Regel 4, Abschnitt 1.2) wird auf diese Zelle zugegriffen. Im allgemeinen verfügt ein Symbol zusätzlich über eine Zelle zum Abbilden der P-Liste. Eine Eintragung in die P-Liste eines Symbols erfolgt mittels PUTPROP. Der zugehörende Selektor ist das GETPROP-Konstrukt. Mit dem REMPROP-Konstrukt wird eine Eigenschaft von der P-Liste entfernt. Die gesamte Eigenschaftsliste ist durch das PROPLIST-Konstrukt verfügbar. Ein besonderes Prädikat für die P-Liste gibt es nicht, da sich die P-Liste von einer „normalen" Liste nicht unterscheidet. Konstruktor/Mutator. eval> eval>

(PUTPROP < e i g e n s c h a f t > ) ==> (REMPR0P < e i g e n s c h a f t > ) — > < s y m b o l - p - 1 i s t e >

Sd^ktofstx' eval> eval>

( P R O P L I S T < s y m b o l > ) ==> < p - l i s t e > (GETPROP < e i g e n s c h a f t > ) = >

¡ganze

P-Liste

Prädikat: eval>

(PAIR?

)

==> #T

mit:

: : = Das S y m b o l , d e s s e n troffen ist.

Eigenschaftsliste

be-

II. Konstruktionen (Analyse und Synthese)

148



B e l i e b i g e r symbolischer Ausdruck als Ausprägung der E i g e n s c h a f t . E i n S y m b o l , das d i e E i g e n s c h a f t von benennt. Eine L i s t e mit der E l e m e n t f o l g e aus < e i g e n s c h a f t > und < w e r t > , s o r t i e r t i n d e r u m g e k e h r t e n R e i h e n f o l g e d e r PUTPROPA p p l i kati onen. M o d i f i z i e r t e < p - l i s t e > mit a l s z u s ä t z l i c h e s e r s t e s Element.

Die Konstrukte PUTPROP, GETPROP, REMPROP und PROPLIST evaluieren ihre Argumente, so daß an der Stelle von , und symbolische Ausdrücke stehen, die einen entsprechenden Rückgabewert haben. Das folgende Beispiel verdeutlicht die Konstrukte zur Eigenschaftsliste: eval eval eval eval eval

> > > > >

(PUTPROP (PUTPROP (PUTPROP (PUTPROP (PROPLIST

eval>

(REMPROP

eva1>

(GETPROP

eva1> eval> eval>

(PUTPROP (GETPROP (GETPROP ==> ()

' ' ' '

*K001* "Schiffdorf" 'ORT) ==> " S c h i f f d o r f " * K 0 0 1 * 2858 ' P L Z ) ==> 2858 *K001* "04703/5270" 'TELEFON) ==> "04703/5270" *K001* "0421/63171" 'TELEFON) ==> "0421/63171" ' * K 0 0 1 * ) ==> ( T E L E F O N " 0 4 2 1 / 6 3 1 7 1 " PLZ 2 8 5 8 ORT " S c h i f f d o r f " * K 0 0 1 * 'PLZ) ==> ( * K 0 0 1 * T E L E F O N " 0 4 2 1 / 6 3 1 7 1 " ORT " S c h i f f d o r f " ' * K 0 0 1 * 'ORT) ==> " S c h i f f d o r f "

' * K 0 0 1 * () 'ORT) ==> () ' * K 0 0 1 * 'ORT) ==> () '*K001* 'LAUFZEIT) Achtung! Keine Unterscheidung zwischen einer U n d e f i n i e r t e n E i g e n s c h a f t und e i n e r d e f i n i e r t e n E i g e n s c h a f t m i t dem W e r t ( ) .

In klassischen LISP-Systemen (z.B. LISP 1.5, vgl. McCarthy u.a., 1966) wird die P-Liste mitverwendet, um eine Funktion, die das Symbol benennt, zu Speichen. Die Selektion des Funktionswertes eines Symbols entspricht einer Anwendung des PUTPROP-Konstruktes mit der Eigenschaft VALUE (vgl. Abschnitt 7.3). In modernen LISP-Systemen ist die Wertzelle von der Zelle für die P-Liste zu unterscheiden. So kann ein Symbol in einer Umgebung keinen Wert haben, also keinen Eintrag in seiner Wertzelle aufweisen, jedoch Eintragungen in seiner PListe haben. Das folgende Beispiel zeigt einen solchen Fall. eval>

eval> eval>

(DEFINE (LET

M0DUL-P (LAMBDA ( ) ((SCHULZE ' P 2 3 4 5 ) (KRAUSE ' P 0 0 1 2 ) ) (PUTPROP SCHULZE 'MANAGER ' B E R U F ) (PUTPROP KRAUSE ' S C H R E I B K R A F T 'BERUF) (THE-ENVIRONMENT) ¡Hat d i e LET-Umgebung )) ==> M0DUL-P (MODUL-P) ==> # ( P R O P L I S T ' P 2 3 4 5 ) = = > ( B E R U F MANAGER)

als

Wert.

5. Abbildungsoption: Liste

149

eval>

P2345 ==>

ERROR

eval>

SCHULZE = = >

eval> eva1>

(PROPLIST 'SCHULZE) ==> () (ACCESS SCHULZE ( M O D U L - P ) ) ==> P2345 Wert von SCHULZE i n der M O D U L - P - U m g e b u n g . Zum A C C E S S Konstrukt vgl. Abschnitt 1.4.2.

ERROR

Symbol i s t i n d e r U S E R - I N I T I A L E N V I R O N M E N T an k e i n e n W e r t g e bunden . Symbol i s t i n d e r U S E R - I N I T I A L E N V I R O N M E N T an k e i n e n W e r t g e bunden .

/Hinweis: Eigenschaft PCS*PRIMOP-HANDLER. PC Scheme stellt zunächst fest, ob ein Symbol eine PCS*PRIMOP-HANDLER Eigenschaft hat; wenn ja, dann wird dessen CDR-Teil an die Stelle des Namens gesetzt. Das folgende Beispiel verdeutlicht die Auswertung der Eigenschaft PCS*PRIMOP-HANDLER. eval> eval> eval>

eval>

( D E F I N E F00 (LAMBDA ( X ) ( + X 7 ) ) ) = = > F00 ( F 0 0 4 ) = = > 11 ( P U T P R O P ' F 0 0 ' ( " H i e r o h n e B e d e u t u n g " LAMBDA ( Y ) ( + Y 3 ) ) 'PCS*PRIMOP-HANDLER) = = > ( " H i e r o h n e B e d e u t u n g " LAMBDA ( Y ) ( + Y 3 ) ) (F00 4) ==> 7

eval>

F00 = = >

(LAMBDA

(Y)

(+ Y 3))

]

Mit Hilfe des Iterationskontruktes FOR-EACH (vgl. Abschnitt 2.2.3) ist von einer vorgegebenen A-Liste eine P-Liste einfach erstellbar. Dazu definieren wir das Konstrukt MAKE-LEXIKON: eval>

eval>

eval>

( D E F I N E M A K E - L E X I K O N (LAMBDA (SYMBOL A _ L I S T E ) ( F O R - E A C H (LAMBDA ( X ) (PUTPROP SYMBOL ( L I S T - R E F X 1) (CAR X ) ) ) A_ L I S T E ) ) ) = = > M A K E - L E X I K O N (MAKE-LEXIKON '*K002* • ( ( T E L E F O N " 0 7 2 1 / 1 2 3 4 " ) (ORT " K a r l s r u h e " ) (PLZ 7500) (TELEFON " 0 7 2 1 / 4 5 6 7 " ) ) ) ==> ¡Rückgabewert des FOR-EACH-Konstruktes (PROPLIST '*K002*)) = = > ( P L Z 7 5 0 0 ORT " K a r l s r u h e " T E L E F O N " 0 7 2 1 / 4 5 6 7 " ) ;Zu b e a c h t e n i s t d i e R e i h e n f o l g e d e r E l e m e n t e . ; S i e w e i s t den l e t z t e n W e r t f ü r d i e E i g e n s c h a f t : T E L E F O N a u s , j e d o c h a u f g r u n d d e s e r s t e n PUTPROP : f ü r TELEFON a l s l e t z t e s P-Listen-Element. ; Neue E i g e n s c h a f t e n w e r d e n v o r n e i n g e f ü g t .

Die Konstruktion mit P-Listen birgt die Gefahr ungewollter Nebeneffekte. Das folgende Beispiel zeigt die Unterschiede der Effekte (Reichweite) der P-Liste zu einer lokalen A-Listendefinition. In beiden Fällen konstruieren wir ein Lexikon FOO, das zunächst die Attribute KEY-1, KEY-2 und KEY-3 enthält. Der Wert von KEY-1 wird modifiziert, und KEY-2 wird gelöscht. Danach wird auf der

150

II. Konstruktionen (Analyse und Synthese)

„top level"-Ebene (d.h. in der Umgebung USER-INITIAL-ENVIRONMENT) mit dem gleichen lokalen Namen eine KEY-4-Definition hinzugefügt. eval> (DEFINE LOKALE-A-LISTE (LAMBDA () (LET ((A-LISTE (COPY :um Konstante zu schützen * C C KEY -1 VALUE-1) (KEY-2 VALUE-2) (KEY-3 VALUE-3)))) (SET-CDR! (ASSQ 'KEY -1 A-LISTE) (LIST 'VALUE-NEU)) (DELETE! (ASSQ 'KEY-2 A-LISTE) A-LISTE) A-LISTE))) ==> LOKALE-A-LISTE eval> (DEFINE FOO (LOKALE-A-LISTE)) ==> FOO eval> FOO ==> ((KEY -1 VALUE-NEU) (KEY - 3 VALUE-3)) eval> (DEFINE A-LISTE '((KEY-4 'VALUE-4))) ==> A-LISTE eval> (DEFINE FOO (LOKALE-A-LISTE)) ==> FOO eval> FOO ==> ((KEY -1 VALUE-NEU) (KEY - 3 VALUE-3)) eval> (DEFINE VERMEINTLICH-LOKALE-P-LISTE (LAMBDA () (LET ((MEINE -P- LISTE 'BAZ)) (PUTPROP MEINE-P-LISTE 'VALUE-1 'KEY-D (PUTPROP MEINE-P-LISTE 'VALUE-2 'KEY - 2) (PUTPROP MEINE-P-LISTE 'VALUE-3 'KEY-3) (PUTPROP MEINE-P-LISTE 'VALUE-NEU 'KEY-D (REMPROP MEINE-P-LISTE 'KEY - 2) (PROPLIST MEINE-P-LISTE)))) ==> VERMEINTLICH-LOKALE-P-LISTE eval> (DEFINE FOO (VERMEINTLICH-LOKALE-P-LISTE)) ==> FOO eval> FOO ==> (KEY-3 VALUE-3 KEY -1 VALUE-NEU) Die P-Liste ist für das Symbol BAZ, das in der USER-INITIAL-ENVIRONMENT keinen Wert hat, angelegt. Mit dem Definieren des FOO-Konstruktes zeigt das Symbol FOO auf die P-Liste von BAZ. Wird die P-Liste von BAZ verändert, dann ist FOO gegebenenfalls davon mitbetroffen. Bezieht sich die Änderung auf eine Listenposition vor dem FOO-Zeiger, dann ist diese für FOO unerheblich. eval> (PUTPROP 'BAZ 'VALUE-4 'KEY-4) ==> VALUE-4 eval> BAZ ==> ERROR . . . ¡Variable BAZ nicht ; in a k t u e l l e r ; Umgebung d e f i n i e r t eval> FOO ==> (KEY-3 VALUE-3 KEY -1 VALUE-NEU);Keine Änderung Wird jedoch der FOO-Zeiger durch die erneute Applikation von VERMEINTLICH-LOKALE-P-LISTE auf den Anfang der P-Liste von BAZ gesetzt, dann ist FOO von der BAZ-P-Listen-Modifikation betroffen. eval> (DEFINE FOO (VERMEINTLICH-LOKALE-P-LISTE)) ==> FOO eval> FOO ==> (KEY-4 VALUE-4 KEY - 3 VALUE-3 KEY -1 VALUE-1) ¡Achtung Nebeneffekt! KEY-4 e x i s t i e r t .

5. Abbildungsoption: Liste

151

Diese Globalität der P-Liste führt leicht zu ungewollten Nebeneffekten (Fehlerquelle!). Man kann ihre Globalität jedoch vorteilhaft zur Kommunikation zwischen Konstrukten nutzen. Eine Kommunikation über die P-Liste zeigt das folgende Beispiel SCHNITTMENGE-SYMBOLE. Wir berechnen die Schnittmenge von Symbolen in zwei Listen. Zunächst werden alle Symbole, die in der ersten Liste vorkommen, markiert. Sie erhalten eine Eintragung in ihrer P-Liste. Damit eine bereits vorhandene Eigenschaft nicht überschrieben wird, ist sicherzustellen, daß wir eine Eigenschaft wählen, die nicht nocheinmal vorkommt. Dazu nutzen wir das GENSYM-Konstrukt (Näheres dazu vgl. Abschnitt 7.4). Die zweite Liste wird Symbol für Symbol abgearbeitet. Für jedes Symbol prüfen wir, ob es markiert ist. Wenn es markiert ist, dann gehört es zur Schnittmenge. Da es in der zweiten Liste nochmals vorkommen kann, heben wir im Trefferfall die Markierung wieder auf. Alle nicht markierten Symbole gehören nicht zur Schnittmenge. Zum Schluß heben wir die noch bestehenden Markierungen von Symbolen der ersten Liste auf. Das Durchlaufen der beiden Listen übernehmen „mapping functions" (vgl. Abschnitt 2.2.3). Durch das MAP-Konstrukt enthält die Schnittmenge NIL-Werte. Diese löschen wir durch das DELQ!-Konstrukt. Es prüft mit dem EQ?-Konstrukt und löscht ein Listenelement, wenn dieses Prädikat zutrifft. eval>

(DEFINE

KONTEN

'(A

B C D E F G (H

eval>

(DEFINE

O F F E N E - POSTEN

'(F

I

J

K)

Z Y G G A C))

L M N 0 ==>

P)) = = > KONTEN OFFENE-POSTEN

e v a l > (DEFINE SCHNITTMENGE-SYMBOLE (LAMBDA ( L _ l L _ 2 ) (LET ((MARKE (GENSYM))) ; Marke g e n e r i e r e n ;; M a r k i e r e n der Elemente von L_1 (FOR-EACH (LAMBDA ( X ) ( I F ( S Y M B O L ? X) (PUTPROP X T M A R K E ) ) ) L_1) ;; S e l e k t i e r e n der m a r k i e r t e n Werte (BEGINO (DELQ! ¡Loescht a l l e NIL-Werte () (MAP ( L A M B D A ( X ) (COND ( ( A N D ( S Y M B O L ? X ) (GETPROP X M A R K E ) ) ;; L ö s c h t m a r k i e r t e Werte (REMPROP X MARKE) X) ; R ü c k g a b e w e r t von ; ; vgl. Abschnitt 2.2.3 (T L_2))

NIL)))

152

II. Konstruktionen (Analyse und Synthese)

;;

Löschen

noch

bestehender

Marken

(FOR-EACH (LAMBDA (IF

(X)

(AND

(SYMBOL?

(REMPROP L_1))))) eval>

==>

X

X)

(GETPROP

X

MARKE))

MARKE)))

SCHNITTMENGE-SYMBOLE

(SCHNITTMENGE-SYMBOLE

KONTEN

OFFENE-POSTEN)

==>

(F

G A

C)

Als weiteres Beispiel für die Abbildungsoption P-Liste betrachten wir im folgenden ein Bürogebäude einer Kommunalverwaltung (vgl. Bild 5.3-1). Die Akten (Geschäftsvorfälle) werden in Laufmappen transportiert. Den Transport übernimmt ein mechanisches Fließbandsystem. Zu seiner elektronischen Steuerung ist ein Weg zwischen der jeweiligen Startstation und der angegebenen Zielstation zu bestimmen. Aufgabe des LISP-Programm ist es, einen solchen Weg zu ermitteln. Beispiel: Laufinappen-Transportsteuerung Entsprechend der Büroskizze (Bild 5.3-1) ist für jede Hauspoststation eine PListe definiert (vgl. Programmfragment 5.3-1). Sie enthält: o als Eigenschaft ORT, die Lage der Poststation, angegeben in X-Y-Koordinaten, und o als Eigenschaft NACHFOLGER die Hauspoststationen, die direkt erreicht werden können sowie ihre Entfernung (als A-Liste). Wir unterstellen, daß stets nur eine Laufmappe transportiert wird und die einzelnen Stationen diese Laufmappe durchschleusen bzw. aufnehmen können. Wir reduzieren damit das Problem auf eine einfache Wegesuche. Zunächst sei darüber hinaus nur gefordert, daß ein möglicher Weg zwischen Start und Ziel gefunden wird. Dieser Weg muß nicht der kürzeste Weg sein. e v a l > ( D E F I N E BUERO ( B E G I N ; ; ; ; K o o r d i n a t e n der Knoten (PUTPROP 'A ' ( 8 . 5 1 1 . 5 ) 'ORT) (PUTPROP 'B ' ( 1 5 . 5 10) 'ORT) (PUTPROP 'C ' ( 8 . 5 6 . 5 ) 'ORT) (PUTPROP 'D ' ( 8 . 5 5 . 5 ) 'ORT) (PUTPROP ' E ' ( 1 4 6 . 5 ) 'ORT) (PUTPROP ' F ' ( 1 5 . 5 4 . 5 ) 'ORT) (PUTPROP 'G ' ( 1 5 . 5 3) 'ORT) (PUTPROP 'H ' ( 0 . 5 0 . 5 ) 'ORT) (PUTPROP 'U ' ( 1 7 6 . 5 ) 'ORT)

5. Abbildungsoption: Liste ;;;;

153

E r r e i c h b a r e K n o t e n und i h r e E n t f e r n u n g e n (PUTPROP ' A ' ( ( C 5) (B 8 . 5 ) ) 'NACHFOLGER) (PUTPROP ' B ' ( ( A 8 . 5 ) (E 4 . 5 ) ) 'NACHFOLGER) ( P U T P R O P ' C ' ( ( D 1 ) (A 5 ) ( E 5 . 5 ) (H 1 4 ) ) 'NACHFOLGER) (PUTPROP ' D ' ( ( C 1 ) ) 'NACHFOLGER) ( P U T P R O P ' E ' ( ( C 5 . 5 ) ( F 3 ) ( B 4 . 5 ) (U 3 ) ) 'NACHFOLGER) ( P U T P R O P ' F ' ( ( E 3 ) (G 1 . 5 ) ) 'NACHFOLGER) ( P U T P R O P ' G ' ( ( F 1 . 5 ) (H 1 7 . 5 ) ) 'NACHFOLGER) ( P U T P R O P ' H ' ( ( C 1 4 ) (G 1 7 . 5 ) ) 'NACHFOLGER) (PUTPROP 'U ' ( ( E 3 ) ) 'NACHFOLGER) " E n d e " ) ) = = > BUERO

Programmfragment

5.3-1.

Hauspoststationen abgebildet

(Knoten)

als

P-Listen

Als Problemlösung bilden wir das Verfahren „Tiefensuche" (engl.: depth-firstsearch) ab. Die Bezeichnung „Tiefe" geht von einem Suchbaum (azyklischen Graphen) aus, der auf dem Kopf steht. Sie verdeutlicht, daß dieser Baum zunächst in Richtung auf die "Blätter" untersucht wird, ehe der nächste „Zweig" in Betracht gezogen wird. Gilt es z.B., einen Weg vom Startknoten H zum Zielknoten F zu finden, dann stellt Bild 5.3-2 die Knoten dar, die bei der Tiefensuche abzuarbeiten („zu expandieren") sind, ehe der Zielknoten F erreicht wird. Zunächst ist zu entscheiden, welcher Nachfolger vom Startknoten H als erster zu untersuchen ist. Ist C oder G zu wählen? Wir nehmen C, da C als erster Knoten in der Liste der Nachfolger von H genannt ist. eval>

(GETPROP

'H

'NACHFOLGER)

==>

((C

14)

(G

17.5))

Da C nicht der Zielknoten ist, ist zu entscheiden, ob zunächst der erste Nachfolger von C, hier D, oder der bisher nicht geprüfte Knoten G zu analysieren ist? Im Sinne der „Tiefengewinnung" verfolgen wir erst D weiter, also den ersten Nachfolger-Knoten (,,Kind"-Knoten) von C. Wir steigen im Suchbaum zunächst auf dem "linken Ast" eine Ebene tiefer, vorausgesetzt, es gibt dort noch eine solche Ebene, bevor wir die anderen Knoten auf gleicher Ebene untersuchen. Knoten, die schon überprüft darauf wurden, daß sie nicht der Zielknoten sind, müssen dabei nicht noch einmal analysiert, sondern sind nicht mehr zu berücksichtigen. Im Programm DEPTH-FIRST-SEARCH (vgl. Programm 5.3-1) merken wir uns die noch zu expandierenden Knoten als eine einfache Liste mit dem Namen LISTE-EROEFFNETE-KNOTEN. Knoten, die schon überprüft wurden, ob sie der Zielknoten sind, speichern wir als einfache Liste mit dem Namen LISTEGESCHLOSSENE-KNOTEN. Um von den Nachfolgern eines expandierten Knotens diejenigen Knoten streichen zu können, die schon bearbeitet wurden, definieren wir eine Liste mit dem Namen A R B E I T S L I S T E . Den aktuell expandierten Knoten speichern wir als Wert des Symbols ANALYSE-KNOTEN. Die Tabelle 5.3-1 zeigt die Veränderung dieser Variablen bezogen auf das Durchlau-

154

II. Konstruktionen (Analyse und Synthese)

Y

Amtsleiter Krause

1. Sachbearbeiter Huhn B

Mitarbeiter Wolf

Vorzimmer Schmidt

Stellvertretender Amtsleiter Schulze

Legende:

-•-

H

Mitarbeiter Gans

2. Sachbearbeiter Elster G

->

X

::= Hauspoststation x für den Laufmappentransport ::= Transportweg

Bild 5.3-1. Skizze der Büroräume und der Hauspoststation

fen der lokalen Funktion TIEFENSUCHE. Darüber hinaus sind die PUTPROPund GETPROP-Anwendungen angegeben. Die ARBEITSLISTE bereinigen wir um die Knoten, die schon in der LISTEGESCHLOSSENE-KNOTEN vorkommen. Dazu verwenden wir die Funktion DIFFERENZ-KNOTEN (vgl. Tab. 5.3-1, Spalte ARBEITSLISTE, jeweils zweite Eintragung). Enthält die bereinigte ARBEITSLISTE Knoten, die auch in der LISTE-EROEFFNETE-KNOTEN vorkommen, so streichen wir zunächst diese Knoten aus der LISTE-EROEFFNETE-KNOTEN. Dann setzen wir mit APPEND die Knoten der bereinigte ARBEITSLISTE an den Anfang von LI-

5. Abbildungsoption: Liste

155

Sta r t k n o t e n : H Ziel k n o t e n : F eval>

(DEPTH FIRST-SEARCH

'H

'F) = = >

(H C A B E F)

H i t i

(¿)

(E)

b

;Weil D k e i n e n o c h zu u n t e r ; s u c h e n d e n K n o t e n h a t , ist bei : A w e i t e r zu a n a l y s i e r e n .

[A]

[i]J è

[f]

1 [À]

1 1

Legende: (x) [x] Bild

: : = N o c h zu a n a l y s i e r e n d e r K n o t e n ::= S c h o n a n a l y s i e r t e r K n o t e n 5 . 3 - 2.

Tiefensuche für das F Start H und Ziel

obige

Beispiel:

STE-EROEFFNETE-KNOTEN. Die zunächst aus LISTE-EROEFFNETE-KNOTEN gestrichenen Knoten kommen dadurch wieder in die LISTE-EROEFFNETE-KNOTEN, jedoch in der Reihenfolge von ARBEITSLISTE. Dokument 5.3-1 Titel: Programm DEPTH-FIRST-SEARCH Erstellt: 20.09.89 ¡letzte Änderung: AI Al.l AI.2 El El.l El.2

3.11.90

D E P T H - F I R S T - S E A R C H d u r c h s u c h t ein angegebenes Knotennetz n a c h e i n e m W e g v o n e i n e m S t a r t - zu e i n e m Z i e l - K n o t e n . Der erste s e l e k t i e r t e N a c h f o l g e r w i r d z u n ä c h s t w e i t e r analysiert (expandiert), bevor die weiteren Nachfolger expandiert werden (Zweck: "Tiefengewinnung"). Es s i n d e i n S t a r t - u n d e i n Z i e l k n o t e n a n z u g e b e n . Ein K n o t e n i s t e i n S y m b o l . Die K o o r d i n a t e n eines Knoten sind als E i g e n s c h a f t ORT gespei chert. Die N a c h f o l g e r e i n e s K n o t e n s u n d i h r e E n t f e r n u n g e n s i n d a l s E i g e n s c h a f t N A C H F O L G E R g e s p e i c h e r t u n d z w a r als f o l g e n d e A - L i s t e : ( { ( < n a c h f o l g e r > < e n t f e r n u n g > ) 1)

156

II. Konstruktionen (Analyse und Synthese) T1 Tl.l

Beispiel Daten ( v g l .

Tl.2

A p p l i k a t i o n ( v g l . Tab. 5 . 3 - 1 ) e v a l > (DEPTH-FIRST-SEARCH 'H

Programmfragment

5.3-1) 'F)

==>

(H C A B E

F)

( D E F I N E D E P T H - F I R S T - S E A R C H (LAMBDA (START.KNOTEN Z I E L _ K N O T E N ) (LETREC ((LISTE-EROEFFNETE-KNOTEN ( L I S T START_KNOTEN)) (LISTE-GESCHLOSSENE-KNOTEN NIL) (ANALYSE-KNOTEN N I L ) (ARBEITSLISTE NIL) (GET-NACHFOLGER ; vgl . E l . 2 (ohne E n t f e r n u n g s a n g a b e n e n ) (LAMBDA (KNOTEN) (MAP ( L A M B D A ( X ) (CAR X ) ) ( G E T P R O P KNOTEN ' N A C H F O L G E R ) ) ) ) ;; G i b t L I S T E _ 1 z u r ü c k ohne d i e E l e m e n t e , ;; d i e i n L I S T E _ 2 vorkommen. (DIFFERENZ-KNOTEN ( L A M B D A ( L I ST E _ 1 L I S T E _ 2 ) (COND ( ( N U L L ? L I S T E _ 1 ) N I L ) ((MEMBER (CAR L I S T E _ 1 ) L I S T E _ 2 ) ( D I F F E R E N Z - K N O T E N (CDR L I S T E _ 1 ) LISTE_2)) (T (CONS ( C A R L I S T E _ 1 ) (DIFFERENZ-KNOTEN (CDR L I S T E _ 1 ) LISTE_2)))))] D i e E i g e n s c h a f t WEG v e r k n ü p f t d e n A N A L Y S E - KNOTEN m i t s e i n e n N a c h f o l g e r n . AUSGABE-WEG s e t z t d i e s e K e t t e r ü c k w ä r t s vom Z I E L _ K N O T E N zum S T A R T _ K N O T E N a l s Liste zusammen. (AUSGABE-WEG (LAMBDA (KNOTEN) (COND ( ( N U L L ? K N O T E N ) N I L ) (T ( A P P E N D ( A U S G A B E - W E G ( G E T P R O P KNOTEN ' W E G ) ) (LIST KNOTEN))))))

5. Abbildungsoption: Liste

157

;; E r l ä u t e r u n g siehe Tab. 5 . 3 - 1 (TIEFENSUCHE (LAMBDA ( ) (COND ( ( N U L L ? L I S T E - E R O E F F N E T E - K N O T E N ) N I L ) (T ( S E T ! ANALYSE-KNOTEN ; vgl. Al.l (CAR L I S T E - E R O E F F N E T E - K N O T E N ) ) (SET! LISTE-EROEFFNETE-KNOTEN (CDR L I S T E - E R O E F F N E T E - K N O T E N ) ) (SET! LISTE-GESCHLOSSENE-KNOTEN (CONS A N A L Y S E - K N O T E N LISTE-GESCHLOSSENE-KNOTEN)) (COND ( ( E Q ? A N A L Y S E - K N O T E N Z I E L _ K N O T E N ) (AUSGABE-WEG ANALYSE - KNOTEN)) (T (SET! ARBEITSLISTE (GET-NACHFOLGER ANALYSE - KNOTEN)) (SET! ARBEITSLISTE (DIFFERENZ-KNOTEN ARBEITSLISTE LISTE-GESCHLOSSENE-KNOTEN)) (SET! LISTE-EROEFFNETE-KNOTEN (APPEND ARBEITSLISTE (DIFFERENZ-KNOTEN LISTE-EROEFFNETE-KNOTEN ARBEITSLISTE))) (FOR-EACH (LAMBDA (KNOTEN) (PUTPROP KNOTEN ANALYSE-KNOTEN 'WEG)) ARBEITSLISTE) (TIEFENSUCHE)))))))) ; ; Um e i n A b b r u c h k r i t e r i u m f ü r AUSGABE-WEG z u h a b e n . (PUTPROP START_KNOTEN N I L 'WEG) (TIEFENSUCHE)))) Programm

5.3-1.

DEPTH-FIRST-SEARCH

Das Programm DEPTH-FIRST-SEARCH expandiert stets den ersten Knoten von LISTE-EROEFFNETE-KNOTEN. Anders formuliert: Die Nachfolger von (CAR LISTE-EROEFFNETE-KNOTEN) bilden die ARBEITSLISTE. Ändern wir z.B. für den Knoten H die Reihenfolge der Nennungen bei der Eigenschaft NACHFOLGER (vgl. Programmfragment 5.3-1), dann erhalten wir für unser Beispiel mit Start bei H und Ziel bei F einen kürzeren Weg; statt 35 nur 19 Entfernungseinheiten. eval>

(PUTPROP

'H

'((G

17.5)

eval>

(DEPTH-FIRST-SEARCH

'H

(C 'F)

14)) ==>

'NACHFOLGER) ==> ( ( G (H G F)

17.5)

(C

14))

Ob die Tiefensuche für eine vorgegebene Start-Ziel-Angabe einen kurzen Weg ermittelt, hängt von der Reihenfolge der Knoten in der LISTE-EROEFFNETE-KNOTEN ab, die sich aufgrund der Reihenfolge der Nachfolgeknoten be-

158

II. Konstruktionen (Analyse und Synthese)

eval> TIEFEN SUCHEAufruf

(DEPTH-FIRST-SEARCH

ANALYSEKNOTEN ( )

1.

'H

LISTE-EROEFFNETEKNOTEN (H) (PUTPROP ( )

C

(C G) (PUTPROP (PUTPROP (G)

'C 'G

3.

D

(D A E (PUTPROP (PUTPROP (PUTPROP ( A E G

G) 'D 'C ' A •C ' E 'C )

4.

A

5.

6.

7.

'H 'H

'WEG) 'WEG) 'WEG)

'A

'WEG)

'E

'B

E

( E G) (PUTPROP (G)

'WEG) (E

F

( F U G) (PUTPROP 'F (PUTPROP 'U (U G)

'E •E

Reihenfolge im Programm Zielknoten 5.3-1.

'WEG) 'WEG) (F 'F

ARBEITSLISTE

()

()

(H)

(C (C

'WEG) 'WEG)

B

5.3-1.

F)

'WEG)

(A E G) (EG)

(AUSGABE-WEG (GETPROP ==> (H C A B E F )

(H C A B E

LISTE-GESCHLOSSENEKNOTEN

(B E G) (PUTPROP 'B (EG)

(APPEND Tab.

==>

'H N I L

H

2.

'F)

(C

H)

(D

C

(A

D C

(B

A D C

H)

B A D C

H)

(D A (D A

H)

E H) E)

(C) ( \ l ) H)

(C B) (l D R ;1 (A E) (E) (C (F

E B A D C H)

'WEG) )

G) G)

(LIST

;

'F))

F B U)

U)

Ziel ...

der

PUTPROPund GETPROP-Anwendungen DEPTH-FIRST-SEARCH bei Startknoten H, F und dem Büro von Programm- fragment

stimmt. Die Tiefensuche ermittelt stets einen Weg vom Startknoten zum Zielknoten, falls ein solcher exsistiert, aber nur „zufällig" ist dieser Weg der kürzeste Weg. Wir können es nicht der zufällig passenden Reihenfolge in der LISTE-EROEFFNETE-KNOTEN überlassen, welchen Knoten wir expandieren, wenn das Suchverfahren den kürzesten Weg ermitteln soll; natürlich ohne zunächst alle Knoten zu expandieren. Wir benötigen für die Auswahl des zu expandierenden Knotens eine zusätzliche Information. Diese Angabe muß es gestatten, eine Priorität für das Expandie-

5. Abbildungsoption: Liste

159

ren eines Knotens zu ermitteln, so daß das bisherige „blinde" Suchen ersetzt werden kann durch ein prioritäts-gesteuertes Suchen; z.B. nach dem A*-Verfahren (vgl. Programm 5.3-2). Benötigt wird eine sogenannte „Evaluations"-Funktion (engl.: evaluation function) für das Expandieren der Knoten. (Achtung! Nicht verwechseln mit dem EVAL-Konstrukt.). Die Priorität ergibt sich aufgrund einer Entfernungsschätzung vom Start zum Ziel bei dem Weg über den jeweiligen Knoten. Den zurückgelegten Weg vom Start bis zum Knoten merken wir uns in der P-Liste des Knotens unter der Eigenschaft ENTFERNUNG-VOM-START. Addieren wir zu diesem Wert eine Schätzung vom Knoten zum Ziel, dann haben wir einen Schätzwert für die Gesamtentfernung, die wir unter der Eigenschaft SCHAETZUNG-GESAMTENTFERNUNG speichern. Wird die LISTE-EROEFFNETE-KNOTEN nach den Werten von SCHAETZUNG-GESAMTENTFERNUNG sortiert, dann ist ihr erster Knoten die beste Wahl, weil er den kürzesten Gesamtweg verspricht. Das Problem liegt im Berechnen eines geeigneten Schätzwertes. Auf keinen Fall darf der Schätzwert größer sein als die tatsächliche Entfernung zwischen dem Knoten und dem Ziel (Zulässigkeitsbedingung für das A*-Verfahren). Andernfalls wäre nicht sichergestellt, daß der erste Knoten von LISTE-EROEFFNETE-KNOTEN die höchste Priorität hat. Es könnte dann ein Knoten mit höherem Schätzwert letztlich eine kürzere Entfernung repräsentieren als der vermeintlich beste Knoten.

Legende: . . .

>> ::= zurückgelegter Weg ::= mögliche Wege

Bild 5.3-3. Differenz der X-Koordinatenwerte als Schätzung für die kürzeste Entfernung vom Knoten zum Zielknoten

Nehmen wir zur Erfüllung dieser Bedingung als extremen Schätzwert stets 0 an, dann entspricht das A*-Verfahren einem uninformierten Verfahren, mit der Konsequenz, daß wir viele Knoten expandieren müssen. Ein geeigneter Schätzwert ist die Differenz der X-Koordinatenwerte (vgl. Bild 5.3-3). Wie immer der Weg vom Knoten zum Zielknoten auch verläuft, kürzer als die Differenz der X-Koor-

II. Konstruktionen (Analyse und Synthese)

160

dinatenwerte kann er nicht sein, höchsten gleich groß. Damit wird auch die Konsistenzbedingung erfüllt. Sie fordert, daß für zwei beliebige Knoten K. und K die Differenz der Schätzwerte nicht gößer ist als die Differenz der tatsächlichen Entfernungen zum Zielknoten. ;;;; ;;;; ;;;; ;;; ;;: ;;; ;;; ;;; ;;; ;;;

Dokument 5 . 3 - 3 T i t e l : Programm A * - S E A R C H E r s t e l l t : 20.09.89 ¡letzte AI Al.l

AI.2

Änderung:

3.11.90

A*-SEARCH e r m i t t e l t in einem angegebenen Knotennetz d e n k ü r z e s t e n Weg v o n e i n e m S t a r t - z u e i n e m Z i e l - K n o t e n . D e r N a c h f o l g e r m i t dem k l e i n s t e n W e r t d e r F u n k t i o n E N T F E R N U N G S S C H A E T Z U N G - S T A R T - > Z I EL ( v g l . E 2 ) w i r d a l s nächster analysiert (expandiert). ( Z w e c k : Der " b e s t e " K n o t e n z u e r s t ) . Es s i n d e i n S t a r t - und e i n Z i e l k n o t e n a n z u g e b e n .

;;; ;;; ;;; ;;; ;;; ;;; ;:; ;;; ;;; ;;; ;;; ;;;

El El.l

;;; ;;;

T1 Tl.l

B e i s p i el Daten ( v g l .

;;; ;;;

Tl.2

A p p l i k a t i o n ( v g l . Tab. 5 . 3 - 2 ) e v a l > ( A * - S E A R C H ' H ' U ) = = > (H C E U)

El.2 E2

E3

Ein Knoten i s t ein Symbol. D i e K o o r d i n a t e n e i n e s K n o t e n s i n d a l s E i g e n s c h a f t ORT gespei chert. D i e N a c h f o l g e r e i n e s K n o t e n s und i h r e E n t f e r n u n g e n s i n d a l s E i g e n s c h a f t NACHFOLGER g e s p e i c h e r t und z w a r a l s folgende A-Liste: ((( )}) E N T F E R N U N G S S C H A E T Z U N G - S T A R T - > Z I E L a d d i e r t zum z u r ü c k g e l e g t e n Weg vom S t a r t zum K n o t e n d e n A b s o l u t b e t r a g d e r D i f f e r e n z d e r X - K o o r d i n a t e n w e r t e z w i s c h e n K n o t e n und Ziel knoten (vgl . B i l d 5 . 3 - 3 ) . Z w i s c h e n zwei n i c h t m i t e i n a n d e r v e r b u n d e n e n Knoten i s t d i e E n t f e r n u n g u n e n d l i c h ; h i e r i s t es der Wert 9 . 9 E 9 9 . Programmfragment

5.3-1)

( D E F I N E A * - S E A R C H (LAMBDA (START_KN0TEN Z I E L _ K N 0 T E N ) (LETREC ((LISTE-EROEFFNETE-KNOTEN ( L I S T START_KN0TEN)) (LISTE-GESCHLOSSENE-KNOTEN NIL) (ANALYSE-KNOTEN N I L ) (ARBEITSLISTE NIL) (GET-NACHFOLGER (LAMBDA (KNOTEN) (MAP ( L A M B D A ( X ) ( C A R ( G E T P R O P KNOTEN

X)) 'NACHFOLGER))))

(ENTFERNUNGSSCHAETZUNG-START->ZIEL (LAMBDA (KNOTEN) ( + ( G E T P R O P KNOTEN ' E N T F E R N U N G - V O M - S T A R T ) (ENTFERNUNGSSCHAETZUNG-KNOTEN->ZIEL KNOTEN)))) ( E N T F E R N U N G S S C H A E T Z U N G - K N O T E N - > Z I EL (LAMBDA (KNOTEN) ( A B S ( - (CAR (GETPROP Z I E L _ K N 0 T E N 'ORT)) ( C A R ( G E T P R O P KNOTEN ' O R T ) ) ) ) ) )

5. Abbildungsoption: Liste

161

(ENTFERNUNG-START-KNOTEN->ANDERER-KNOTEN (LAMBDA (KNOTEN ANDERER_KNOTEN) ( + ( G E T P R O P KNOTEN ' E N T F E R N U N G - V O M - S T A R T ) ; ; F a l l s KNOTEN n i c h t m i t A N D E R E R _ K N O T E N v e r b ü n d e n ;; i s t , w i r d a l s P s e u d o e n t f e r n u n g e i n g r o ß e r Wert ; ; angenommen ( v g l . E 3 ) . (COND ( ( C A D R ( A S S O C ANDERER.KNOTEN ( G E T P R O P KNOTEN ' N A C H F O L G E R ) ) ) ) (T 9 . 9 e 9 9 ) ) ) ) ) ;; Knoten i n LISTE-EROEFFNETE-KNOTEN s i n d i n der R e i h e n ;; f o l g e i h r e r P r i o r i t a e t s o r t i e r t . Zugaenge s i n d daher ;; entsprechend i h r e s Schaetzwertes einzufliegen. ( G E T - B E S T E - K N O T E N (LAMBDA ( L I S T E ) (CAR L I S T E ) ) ) (EINFUEGEN-NACH-ENTFERNUNGSSCHAETZUNG (LAMBDA (KNOTEN L I S T E ) (LETREC ((NEUE-WERT (ENTFERNUNGSSCHAETZUNG-START->ZIEL KNOTEN)) (EINFUEGEN (LAMBDA (K L) (COND ( ( N U L L ? L ) ( L I S T K ) ) ( ( < NEUE-WERT (GETPROP (CAR L) 'SCHAETZUNG-GESAMTENTFERNUNG)) (CONS K L ) ) (T ( C O N S ( C A R L ) ( E I N F U E G E N K (CDR L ) ) ) ) ) ) ) ) ( E I N F U E G E N KNOTEN L I S T E ) ) ) ) ;; OFFENE-KNOTEN s c h r e i b t d i e E i g e n s c h a f t s l i s t e f ü r d i e ; ; n e u e n K n o t e n d e r b e r e i n i g t e n A R B E I T S L I S E f o r t und ;; s o r t i e r t d i e s e in LISTE-EROEFFNETE-KNOTEN e i n . (OFFENE-KNOTEN (LAMBDA (KNOTEN) ( P U T P R O P KNOTEN (ENTFERNUNG-START-KNOTEN->ANDERER-KNOTEN A N A L Y S E - K N O T E N KNOTEN) 'ENTFERNUNG-VOM-START) ( P U T P R O P KNOTEN (ENTFERNUNGSSCHAETZUNG- S T A R T - > Z I E L KNOTEN) 'SCHAETZUNG-GESAMTENTFERNUNG) ( P U T P R O P KNOTEN A N A L Y S E - K N O T E N ' W E G ) (SET! LISTE-EROEFFNETE-KNOTEN (EINFUEGEN-NACH-ENTFERNUNGSSCHAETZUNG KNOTEN L I S T E - E R O E F F N E T E - K N O T E N ) ) ) )

II. Konstruktionen (Analyse und Synthese) NEUBERECHNEN-OFFENE-KNOTEN i s t e r f o r d e r l i c h f ü r d i e K n o t e n , d i e a u f e i n e m a n d e r e n Weg s c h o n e r r e i c h t w u r d e n , d . h . s i c h i n L I S T E - E R O E F F N E T - K N O T E N und ARBEITSLISTE befinden. (NEUBERECHNEN-OFFENE-KNOTEN (LAMBDA (KNOTEN) (LETREC ((NEUE-WERT (ENTFERNUNG-START-KNOTEN->ANDERER-KNOTEN ANALYSE-KNOTEN KNOTEN))) (COND ( ( < N E U E - W E R T ( G E T P R O P KNOTEN ' E N T F E R N U N G - V O M - S T A R T ) ) ( P U T P R O P KNOTEN NEUE-WERT 'ENTFERNUNG-VOM-START) ( P U T P R O P KNOTEN ( E N T F E R N U N G S S C H A E T Z U N G - S T A R T - > Z I E L KNOTEN) •SCHAETZUNG-GESAMTENTFERNUNG) ( P U T P R O P KNOTEN A N A L Y S E - K N O T E N ' W E G ) (SET! LISTE-EROEFFNETE-KNOTEN (EINFUEGEN-NACH-ENTFERNUNGSSCHAETZUNG KNOTEN ( D E L E T E ! KNOTEN LISTE-EROEFFNETE-KNOTEN)))) (T N I L ) ) ) ) ) ;; GEMEINSAME-KNOTEN g i b t e i n e L i s t e z u r ü c k , d i e n u r ; ; K n o t e n e n t h ä l t , d i e i n L I S T E _ 1 und L I S T E _ 2 v o r k o m m e n . (GEMEINSAME-KNOTEN (LAMBDA ( L I S T E _ 1 L I S T E _ 2 ) (COND ( ( N U L L ? L I S T E _ 1 ) N I L ) ((MEMBER (CAR L I S T E _ 1 ) LISTE_2) (CONS (CAR L I S T E _ 1 ) (GEMEINSAME-KNOTEN (CDR L I S T E _ 1 ) L I S T E _ 2 ) ) ) (T ( G E M E I N S A M E - K N O T E N ( C D R L I S T E _ 1 ) LISTE_2))))) ;; D I F F E R E N Z - K N O T E N g i b t L I S T E _ l z u r u e c k o h n e d i e E l e m e n t e , ;; d i e i n L I S T E _ 2 vorkommen. (DIFFERENZ-KNOTEN (LAMBDA ( L I S T E 1 L I S T E 2) (COND ( ( N U L L ? L I S T E _ 1 ) N I L ) ((MEMBER (CAR L I S T E _ 1 ) LISTE_2) ( D I F F E R E N Z - K N O T E N (CDR L I S T E _ 1 ) LISTE_2)) (T ( C O N S ( C A R L I S T E _ 1 ) (DIFFERENZ-KNOTEN (CDR L I S T E _ 1 ) LISTE_2)))))) D i e E i g e n s c h a f t WEG v e r k n u e p f t d e n A N A L Y S E - K N O T E N m i t seinen Nachfolgern. AUSGABE-WEGsetztdieseKette rueckw a e r t s v o n Z I E L _ K N O T E N zum S T A R T _ K N O T E N a l s L i s t e zusammen. (AUSGABE-WEG (LAMBDA (KNOTEN) (COND ( ( N U L L ? KNOTEN) N I L ) (T (APPEND ( A U S G A B E - W E G ( G E T P R O P KNOTEN ' W E G ) ) (LIST KNOTEN))))))

5. Abbildungsoption: Liste

163

;; Erlaeuterung siehe Tab. 5.3-2. (A*-SUCHE (LAMBDA () (COND ((NULL? L I S T E - E R O E F F N E T E - K N O T E N ) NIL) (T (SET! A N A L Y S E - K N O T E N (GET-BESTE-KNOTEN LISTE-EROEFFNETE-KNOTEN)) (SET! L I S T E - E R O E F F N E T E - K N O T E N (DELETE! A N A L Y S E - K N O T E N LISTE-EROEFFNETE-KNOTEN)) (SET! L I S T E - G E S C H L O S S E N E - K N O T E N (CONS A N A L Y S E - K N O T E N LISTE-GESCHLOSSENE-KNOTEN)) (COND ((EQ? A N A L Y S E - K N O T E N Z I E L _ K N O T E N ) (AUSGABE-WEG A N A L Y S E - KNOTEN)) (T (SET! A R B E I T S L I S T E (GET-NACHFOLGER A N A L Y S E - K N O T E N ) ) (FOR-EACH O F F E N E - K N O T E N (DIFFERENZ-KNOTEN (DIFFERENZ-KNOTEN ARBEITSLISTE LISTE-GESCHLOSSENE-KNOTEN) LISTE-EROEFFNETE-KNOTEN)) (FOR-EACH N E U B E R E C H N E N - O F F E N E - K N O T E N (GEMEINSAME-KNOTEN ARBEITSLISTE LISTE-EROEFFNETE-KNOTEN)) (A*-SUCHE)))))))) ;; Um A b b r u c h k r i t e r i u m fuer A U S G A B E - W E G zu haben. (PUTPROP START.KNOTEN NIL 'WEG) ;; I n i t i a l i s i e r u n g (PUTPROP S T A R T _ K N O T E N 0 ' E N T F E R N U N G - V O M - S T A R T ) (PUTPROP S T A R T . K N O T E N (ENTFERNUNGSSCHAETZUNG-START->ZIEL START_KNOTEN) 'SCHAETZUNG-GESAMTENTFERNUNG) ;; Start der (A*-SUCHE))) Programm 5.3-2.

Haupfunktion A*-SEARCH

Da das A*-Verfahren den kürzesten Weg ermittelt, können wir Ziel- und StartKnoten vertauschen und erhalten den gleichen Weg, wenn wir die erzeugte Liste umkehren. Bei der Tiefensuche ist dies nicht der Fall. eval > eval> eval> eval>

(DEPTH-FIRST 'U 'H) ==> (U E C H) (REVERSE (DEPTH-FIRST 'H 'U)) ==> (U E B A C H) (A*-SEARCH 'U 'H) = = > (U E C H) (REVERSE (A*-SEARCH 'H 'U)) = - > (U E C H)

164

II. Konstruktionen (Analyse und Synthese)

e v a l > ( A * - S E A R C H 'H X-KoY-Koordinate ordinate 8.5 11.5 15.5 10 8.5 6.5 8.5 5.5 14 6.5 15.5 4.5 15.5 3 0.5 0.5 17 6.5

Hauspoststationen A B C D E F G H U WEG

'U) = = >

P-Liste des Knotens S C H A E T Z U N G - ENTFERGESAMTENTUNG FERNUNG VOM SART

(H C E U)

(ohne O R T ) NACHFOLGER

c G F E

H H G F

22.,5 19. 20.Ì5 25.

14 17.5 19. 22.

D A

C C

23..5 27..5

15 19

Achtung! E

N E U B E R E C H N E N - O F F E N E - K N O T E N für E: C 22.5 19.5 ((C 5.5) (F 3)

B U ; U = Tab.

E 25.5 E 22.5 ZIEL_KN0TEN

24. 22.5

5 . 3 - 2 . Applikation des bei Startknoten Programmfragment

((D ((F ((E ((C

i:) (A 5:) (E 5,.5) (H 14)) (C) î..5) (H 17.5):) (G C) 3:) (G 1.,5)) (F C) 5.,5) (F 3) (B 4 . 5 ) (U 3)) (E C) ((C î:>) (D E) (A D E) ((C 5:> (B 8.,5))

((A 8 . 5 ) ((E 3))

(B 4 . 5 )

(E 4 . 5 ) )

OFFENE-KNOTEN-Konstruktes H, Zielknoten F und dem 5.3-1.

(U 3)) (E A D) (B A D) (U B A D) in Büro

A* von

5.4 Zusammenfassung: Liste, A-Liste und P-Liste Die Assoziationsliste (kurz: A-Liste) ist eine Liste, deren Elemente Sublisten sind, wobei das erste Element jeder Subliste die Aufgabe eines Zugriffsschlüssels hat. Statt Sublisten können auch Punkt-Paare stehen. Genutzt werden Konstruktionsmittel für die „einfache" Liste, d.h. die Konstruktoren CONS und LIST, die Selektoren CAR, CDR und LIST-REF, das Prädikat PAIR? und die Mutatoren SET!, SET-CAR! und SET-CDR!. Der Mutator SET! dient zum Benennen einer existierenden (Teil-)Liste. Die Mutatoren SET-CAR! und SET-CDR! ändern Listenelemente. Die Selektoren für ein A-Listen-Elenent (Subliste) prüfen das Schlüsselelement. Der Selektor ASSOC vergleicht mit EQUAL? und der Selektor ASSQ mit EQ?. Jedem Symbol ist eine zunächst leere Eigenschaftsliste (engl.: property list; kurz: P-Liste) zugeordnet. Das PUTPROP-Konstrukt fügt eine neue Eigenschaft

5. Abbildungsoption: Liste

165

ein bzw. überschreibt eine schon vorhandene. Das GETPROP-Konstrukt selektiert den Wert einer angegebenen Eigenschaft. Die P-Liste ist global. Dies hat Vor- und Nachteile. Vorteilhaft kann die P-Liste zur Kommunikation zwischen Konstrukten genutzt werden. Nachteilig ist die Gefahr ungewollter Nebeneffekte. Charakteristische

Beispiele für Abschnitt 5:

; ; ; ; Z i r k u l ä r e s Punkt-Paar e v a l > ( D E F I N E ENDLOS (CONS ' A ' B ) ) = = > ENDLOS e v a l X B E G I N ( S E T - C D R ! ENDLOS ENDLOS) ( S E T - C A R ! ENDLOS " L I S P : Das u l t i m a t i v e M o d e l l " ) #T) ¡Damit e i n e n d l i c h e r Rückgabewert b e s t e h t . = = > #T e v a l > ( L I S T - R E F ENDLOS 1 0 0 ) = = > " L I S P : Das u l t i m a t i v e ;;;; ;;; ;;; ;;;

Modell"

Zweidimensionale Matrix konstruiert als A - L i s t e . M i t H i l f e von M u t a t o r e n w i r d d i e l o k a l e MATRIX f o r t g e s c h r i eben. E i n E l e m e n t w i r d mit dem A S S O C - K o n s t r u k t s e l e k t i e r t ,

eval> (LET*

(DEFINE MAKE-X-Y-SPEICHER(LAMBDA ((MATRIX ;;

(LIST

Selektor

(GET

für

(LAMBDA (LET

()

'*MATRIX*))

(X

ein

Matrixelement

Y)

((ZEILE

(ASSOC X (CDR

(COND ( ( N U L L ? (T ( L E T

ZEILE)

MATRIX))))

NIL)

((SPALTE

(ASSOC Y (CDR

(COND ( ( N U L L ? (T ;;

Konstruktor/Mutator

(PUT!

(LAMBDA (LET

(CDR S P A L T E ) ) ) für

ein

ZEILE))))

NIL) ))))))

Matrixelement

(X Y WERT) ((ZEILE

(ASSOC X (CDR

(COND ( ( N U L L ?

(LIST

((SPALTE

MATRIX

X (CONS Y WERT))

(CDR (LET

MATRIX))))

ZEILE)

(SET-CDR! (CONS (T

SPALTE)

MATRIX))))

(ASSOC Y (CDR

(COND ( ( N U L L ?

ZEILE))):

SPALTE)

(SET-CDR! (CONS

ZEILE

(CONS Y WERT) (CDR

(T ( S E T - C D R !

ZEILE)))) SPALTE WERT))):

(PP

MATRIX)))

166

II. Konstruktionen (Analyse und Synthese) ;;

Verzweigungsfunktion

;;

jeweiligen

zum S e l e k t i e r e n

der

Operation

(DISPATCH (LAMBDA

(OP)

(COND

( ( E Q ? OP

'GET)

((EQ?

'PUT!)

(T

OP

(ERROR

GET) PUT!)

"Unbekannte

Operation:

" OP))))))

;;

Rückgabewert

DISPATCH)))

ist

==>

die

Verzweigungsfunktion

HAKE-X-Y-SPEICHER

eval>

(DEFINE

X-Y-MATRIX

eval>

(DEFINE

PUT!

(X-Y-MATRIX

'PUT!))

eval>

(DEFINE

GET

(X-Y-MATRIX

'GET))

eval>

(PUT!

1 2 "heute")

eval>

(PUT!

3 4 "gestern")

eval>

(PUT!

(*MATRIX* '(A

(3

B C)

(MAKE - X - Y - S P E I C H E R ) )

==>

(4

==>

(*MATRIX*

==>

X-Y-MATRIX

PUT! GET

(1

(2

.

"heute")))

(1

(2

.

"heute")))

==> .

#\a

==>

"gestern"))

"Alles

klar")

==>

(*MATRIX* (CA

eval> ;;;; ;;; ;;; ;;; ;;; ;;; ;;;

(GET

B C)

(#\a

. "Alles

(3

(4

.

"gestern"))

(1

(2

.

"heute")))

'(A

B C)

#\a)

==>

klar"))

"Alles

klar"

Geschachtelte GETPROP-Konstrukte Konten mit u n t e r s c h i e d l i c h e n TREND-Funktionen. Für e i n K o n t o , z . B . K001, w i r d über d i e E i g e n s c h a f t KONTO-TYP e i n Symbol s e l e k t i e r t . D i e s e s Symbol h a t a l s Wert s e i n e r E i g e n s c h a f t TREND, d i e anzuwendende T R E N D - F u n k t i o n . Das Argument f ü r d i e s e F u n k t i o n i s t u n t e r d e r E i g e n s c h a f t BUCHUNGEN g e s p e i c h e r t .

eval>

(PUTPROP

'K001

'VERMOEGEN

eval>

(PUTPROP

'K001

'(20

eva1>

(PUTPROP

'K002

'VERBRAUCH

eval>

(PUTPROP

'K002

'(10

eval>

(PUTPROP

'VERMOEGEN

(LAMBDA (MAP

30

'KONTO-TYP)

'BUCHUNGEN)

'KONTO-TYP)

100)

1.1

(GETPROP ==>

X)) 'BUCHUNGEN)))

#

VERMOEGEN

(20

==>

'BUCHUNGEN)

(X) KONTO

==>

==>

(KONTO) (LAMBDA (*

'TREND)

30)

30)

VERBRAUCH

==>

(10

30

100)

6. Abbildungsoption: Vektor

eval>

(PUTPROP

167

'VERBRAUCH

(LAMBDA (MAP

(KONTO) (LAMBDA (+

(X)

100

(GETPROP 'TREND) eval>

(DEFINE

==>

X)) KONTO

'BUCHUNGEN)))

#

TREND (LAMBDA

(KONTO)

((GETPROP (GETPROP

KONTO

'KONTO-TYP)

'TREND) KONTO)))

= = T R E N D

eval>

(TREND

'K001)

==>

(22.

33.)

eval>

(TREND

'K002)

==>

(110

130

200)

6. Abbildungsoption: Vektor LISP-Systeme ermöglichen neben der Definition von Listen auch die Definition von Vektoren. Dabei ist ein Vektor eine modifizierbare Struktur. Den Elementen dieser Strukur ist ein Index vom Typ ganzzahlige, positive Zahl (Integer-Zahl) zugeordnet. Die Vektorelemente können von beliebigem Typ sein, also auch wieder Vektoren. Z. B. ist ein zweidimensionales Feld (engl.: array) ein Vektor mit Vektoren als Elemente. Die Länge eines Vektors ist gegeben durch die Anzahl der Elemente, die er enthält. Sie wird zum Zeitpunkt der Vektordefinition festgelegt. Im Gegensatz zur Liste ist der Vektor daher eine „statische" Abbildungsoption. Ein Vektor hat eine signifikant andere Zugriffszeit-Charakteristik als eine Liste. Die Zugriffszeit ist konstant, d.h. sie ist unabhägig davon, ob auf ein Element am Anfang (kleiner Index) oder am Ende des Vektors (großer Index) zugegriffen wird. Bei der Liste steigt die Zugriffszeit linear mit der Position des gesuchten Elementes. Zunächst erörtern wir eingebaute Vektor-Konstrukte im Sinne des Verbundes von Konstruktor, Selektor, Prädikat und Mutator (Abschnitt 6.1). Mit diesen Konstrukten implementieren wir einen solchen Verbund für die Liste, d.h. wir bilden die CONS-Zelle als Vektor ab (vgl. Abschnitt 6.2). Anschließend nutzen wir die Abbildungsoption Vektor, um eine Aktenverwaltung als einen höhenbalancierten Baum abzubilden (vgl. Abschnitt 6.3). Dieses größere Beispiel möge gleichzeitig dem Training im Lesen fremder Programme dienen.

168

II. Konstruktionen (Analyse und Synthese)

6.1 Vektor- Konstrukte In Scheme unterscheidet sich die Notation eines Vektors von der einer Liste durch ein vorrangestelltes #-Zeichen. e v a l > # ( A B C D E) = = > # ( A B C 0 E) ¡ V e k t o r m i t 5 e v a l > # ( # ( A B) # ( C D) E) ;Vektor mit 3 = = > # ( # ( A B) # ( C D) E) e v a l > (VECTOR-LENGTH # ( # ( A ) ( B C D E ) ) ) = = > 2

Elementen Elementen

Wie die Beispiele zeigen, gilt für Vektoren die EVAL-Regel 1 (vgl. Abschnitt 1.2). Sie haben sich selbst als Wert. Wir können daher nicht (- wie im Fall der Liste -) direkt eine Funktionapplikation notieren. Enhält der Vektor eine Funktion und ihre Argumente und ist diese Funktion anzuwenden, dann ist der Vektor vorab in eine Liste zu überführen. Dazu dient das Konstrukt VECTOR->LIST. Das LIST->VECTOR-Konstrukt führt die umgekehrte Transformation aus. eval> eval> eval> eval> eval>

( D E F I N E F00 # ( + 1 2 3 ) ) = = > F00 F00 = = > # ( + 1 2 3 ) ( V E C T O R - > L I S T F 0 0 ) = = > ( + 1 2 3) (EVAL ( V E C T O R - > L I S T F 0 0 ) ) = = > 6 ( L I S T - > V E C T 0 R ' ( A B C ) ) = = > # ( A B C)

Konstruktoren:

eval>(VECTOR

eval> eval>

< e l _ 0 > < e l _ l > . . . < e l _ j > . . . < e l _ n > ) ==> #( . . . < w e r t _ j > . . . ) (MAKE-VECTOR k) ==> # ( . . . ) (MAKE-VECTOR k < i n i t - s e x p r > ) ==> # ( < v a l u e > . . . < v a l u e > )

mit:

#( . . . ) k

= j - t e Element des V e k t o r s = Wert von < e l _ j > , VECTOR und MAKE-VECTOR e v a l u i e r e n i h r e Argumente = Notation für einen Vektor = Anzahl der Elemente = I n i t i a l a u s d r u c k f ü r d i e g e n e r i e r t e n Elemente = Wert von < i n i t - s e x p r >

$e le letot" eval>

(VECTOR-REF < v e k t o r >

i)

==>

mit: i = 0 , 1, . . . ( - 1 + (VECTOR-LENGTH < v e k t o r > ) ) Das e r s t e E l e m e n t h a t den I n d e x 0 . Das l e t z t e E l e m e n t h a t den I n d e x d e r um 1 v e r m i n d e r t e n V e k t o r l ä n g e . VECTOR-REF i s t " z e r o b a s e d " .

169

6. Abbildungsoption: Vektor Prädikat: eval>

(VECTOR?

)

= = > #T

¡Falls ; ist,

ein sonst NIL.

ein

Vektor

Mutator: eval>

(VECTOR-SET!

i

) = >

#(

...

i

hat

den Wert

...)

mit:

::=

Das E l e m e n t .

von

Die folgenden Beispiele verdeutlichen diese VECTOR-Konstrukte. eval>

(DEFINE

eval> eval> eval> eval>

( V E C T O R ? * V 0 0 1 * ) = = > #1 ( V E C T O R - R E F * V 0 0 1 * 3 ) = = > ERROR . . . : V e k t o r i n d e x z u g r o ß (VECTOR-REF * V 0 0 1 * 1) = = > 2858 (VECTOR-SET! *V001* 0 " S c h i f f d o r f " ) ==> # ( " S c h i f f d o r f " 2858 "04703/5270) (MAKE-VECTOR 4 " J " ) ==> # ( " J " " J " " J " "J") ( V E C T O R ? (MAKE-VECTOR 3 0 ) ) = = > #T (VECTOR? ( L I S T 1 2 ) ) ==> N I L

eval> eval> eval>

*V001*

(VECTOR

"Sellstedt"

2858

"04703/5270")) ==> *V001*

eval>

(DEFINE M0DUL-V! ¡ D e s t r u k t i v e s K o n s t r u k t durch VECTOR-SET! (LAMBDA (OBJEKT INDEX NEUE.WERT) (COND ( ( A N D ( V E C T O R ? O B J E K T ) (INTEGER? INDEX) ( MODUL-V!

eval>

(MODUL-V!

eval>

*V001*

(MODUL-V!

*V001*

==> #{"Bremen"

1 2500) 0 "Bremen") ==> # ( " B r e m e n " 2500 " 0 4 7 0 3 / 5 2 7 0 " ) 2500 " 0 4 7 0 3 / 5 2 7 0 " )

Zum Vergleich ist für MODUL-V! im folgenden eine Listenlösung angegeben: eval> eval>

(DEFINE

*L001*

(LIST

"Schiffdorf"

2858

"04703/5270")) ==> * L001* (DEFINE MODUL-L! ¡ D e s t r u k t i v e s K o n s t r u k t d u r c h SET-CAR! (LAMBDA ( O B J E K T INDEX NEUE_WERT) (COND ( ( A N D ( P A I R ? O B J E K T ) (INTEGER? INDEX) ( MODUL-L!

170

II. Konstruktionen (Analyse und Synthese)

eval> (MODUL-L! (MODUL-L! *L001* 1 2500) 0 "Bremen") ==> ("Bremen" 2500 "04703/5270") eval> *L001* ==> ("Bremen" 2500 "04703/5270") Wann eine A-Liste effizienter als ein Vektor ist, d.h. eine kleinere durchschnittliche Selektionszeit hat, ist abhängig von der jeweiligen LISP-Implementation. Als Faustregel gilt, daß ein dünn besetzter Vektor besser als eine A-Liste zu implementieren ist (vgl. Abschnitt 5.4, zweidimensionale Matrix).

6.2 Abbildung der CONS-Zelle als Vektor Die CONS-Zelle verknüpft zwei symbolische Ausdrücke (vgl. Abschnitt 2.1). Sie ist das klassische Mittel um eine Liste zu konstruieren. Wir bilden im folgenden eine CONS-Zelle als Vektor ab. Den Konstruktor für diese CONS-Zelle nennen wir V-CONS. Damit vermeiden wir Namensgleichheit und infolgedessen das Überschreiben des eingebauten CONS-Konstruktes. Selektoren, Prädikate und Mutatoren haben aus gleichem Grund den Präfix „V-". Zum Konstruieren einer Liste gehen wir von NIL, der leeren Liste, aus. Wir definieren daher ein *V-NIL* mit dem Konstrakt VECTOR. Die Sternchen im Namen sollen ein versehenliches Überschreiben dieser globalen Variablen verhindern (Näheres zur Benennung vgl. Abschnitt 11.1). Das *V-NIL* kann, wie folgt, definiert werden: eval> (DEFINE *V-NIL* (VECTOR #\0 # \ 0 ) ) ==> *V-NIL* Das zugehörende Prädikat V-NULL? könnten wir so definieren, daß jeder Vektor mit den beiden Elementen #\0 ein *V-NIL* repräsentiert. Das V-NULL?-Prädikat stützt sich damit auf den Vergleich der Elemente: eval> (DEFINE V-NULL? (LAMBDA (V_PAIR) (AND (VECTOR? V_PAIR) (= (VECT0R-LENGTH V_PAIR) 2) (EQ? (VECT0R-REF V_PAIR 0) # \ 0 ) ) (EQ? (VECT0R-REF V_PAIR 1) # \ 0 ) ) ) ) ) ==> V-NULL? Wir unterstellen jedoch, daß es nur ein Objekt gibt, das *V-NIL* repräsentiert. Das Prädikat V-NULL? testet daher auf Identität und nicht auf Gleichheit von Strukturen (vgl. Abschnitt 2.1). Das V-NULL?-Konstrukt nutzt daher für den direkten Vektor-Vergleich das EQ?-Konstrakt. eval> (DEFINE V-NULL? (LAMBDA (0BJECT) (EQ? 0BJECT *V-NIL*))) ==> V-NULL? Der Konstruktor CONS generiert eine neue CONS-Zelle, um den CAR-Teil mit

6. Abbildungsoption: Vektor

171

dem CDR-Teil zu verknüpfen. Dies Verknüpfung realisieren wir durch das Konstruieren eines zweielementigen Vektors. eval>

( D E F I N E V-CONS (LAMBDA ( C A R _ T E I L C D R _ T E I L ) (VECTOR C A R . T E I L C D R _ T E I L ) ) )

==>

V-CONS

Die Selektoren V-CAR und V-CDR realiseren wir dann mit dem Selektor VECTOR-REF. eval>

eval>

(DEFINE V-CAR (LAMBDA ( V _ P A I R ) (VECTOR-REF V_PAIR

0)))

==>

V-CAR

( D E F I N E V-CDR ( L A M B D A ( V _ PA I R ) (VECTOR-REF V.PAIR

1)))

==>

V-CDR

Das erforderliche Prädikat V-PAIR? nutzt das eingebaute Prädikat VECTOR?. Das V-PAIR?-Konstrukt testet, ob eine V-CONS-Zelle vorliegt (bzw. mehrere). Da nicht jeder Vektor vom Typ V-CONS ist, überprüft V-PAIR? auch die Länge des Vektors. Wir unterstellen damit, daß jeder zweielementige Vektor eine VCONS-Zelle ist. eval>

(DEFINE V-PAIR? ( L A M B D A ( V _ PA I R ) (AND (VECTOR? V_PAIR) (= (VECTOR-LENGTH

V_PAIR)

2))))

==>

V-PAIR?

Die Mutatoren V-SET-CAR! und V-SET-CDR! sind mit dem eingebauten Mutator VECTOR-SET! leicht realisierbar. eval>

eval>

(DEFINE V-SET-CAR! (LAMBDA ( V _ P A I R C A R . T E I L ) (VECTOR-SET! V_PAIR 0 CAR_TEIL)))

==>

V-SET-CAR!

(DEFINE V-SET-CDR! ( L A M B D A ( V _ PA I R C D R_ T E I L ) (VECTOR-SET! V_PAIR 1 CDR_TEIL)))

==>

V-SET-CDR!

Unser Verbund von Konstruktor V-CONS, Selektoren V-CAR und V-CDR sowie Prädikat V-NULL? hat die wesentliche Eigenschaft: Das Zusammengefügte kann ohne Verlust wieder getrennt werden. eval> eval> eval>

( V - C A R ( V - C O N S 'X ' Y ) ) ==> X ( V - C D R ( V - C O N S 'X ' Y ) ) ==> Y (V-NULL? (V-CAR (V-CONS * V - N I L *

*Y)))

==>

#T

172

II. Konstruktionen (Analyse und Synthese)

e v a l > ( V - N U L L ? ( V - C D R ( V - C O N S 'X * V - N I L * ) ) ) = = > # T e v a l > ( D E F I N E F 0 0 ( V - C O N S 'X 'Y)) = = > F00 e v a l > F00 = = > # ( X Y) Der Wert von FOO „verrät", daß unsere V-CONS-Zellen als Vektoren abgebildet sind. Wir definieren ein geeignetes V-PP-Konstrukt, um auch nach außen hin das gewohnte Layout einer Listen-Darstellung vermitteln zu können. eval> (DEFINE V-PP (LAMBDA (V_PAIR) (LETREC ((V-ATOM? (LAMBDA (OBJEKT) (NOT ( V - P A I R ? O B J E K T ) ) ) ) (AUSGABE (LAMBDA (V_PAIR) (COND ( ( V - N U L L ? V _ P A I R ) ( D I S P L A Y ") ")) ((V-ATOM? (V-CAR V_PAIR)) (WRITE ( V - C A R V _ P A I R ) ) (COND ( ( V - P A I R ? ( V - C D R V _ P A I R ) ) (COND ( ( V - N U L L ? (V-CDR V _ P A I R ) ) ( A U S G A B E (V-CDR V _ P A I R ) ) ) (T ( D I S P L A Y " ") (AUSGABE (V-CDR V_PAIR))))) (T (DISPLAY " . ") (WRITE (V-CDR V_PAIR)) (AUSGABE *V-NIL*)))) (T ( D I S P L A Y "(") (AUSGABE (V-CAR V_PAIR)) (AUSGABE (V-CDR V_PAIR))))))) (COND ( ( V - P A I R ? V _ P A I R ) (DISPLAY "(") (AUSGABE V_PAIR)) (T (WRITE V _ P A I R ) ) ) ¡ A u s g a b e e i n e s s o n s t i g e n *THE-NON-PRINTING-OBJECT*))) ==> V-PP e v a l > ( D E F I N E FOO ( V - C O N S "Emma" ( V - C O N S " K r a u s e "

Objektes

*V-NIL*))) = = > FOO

e v a l > ( D E F I N E B A R ( V - C O N S "Frau" F O O ) ) = = > B A R e v a l > ( V - P P B A R ) = = > ( " F r a u " "Emma" " K r a u s e " ) eval> (V-PP ( V - C O N S '*TAB* ( V - C O N S ( V - C O N S 'A f B ) ( V - C O N S 'X * V - N I L * ) ) ) ) = = > ( * T A ß * (A . B) X)

6.3 Höhenbalancierter Baum Beispiel Aktenverwaltung Als Beispiel dient eine Verwaltung von Akten, bei der man neue Akten anlegen und angelegte Aktenbeschreibungen modifizieren kann; jedoch keine Akten entfernen (löschen) kann (vgl. Programm 6.3-1). Unterstellt wird für jede Akte ein eindeutiges Aktenzeichen (KEY). Jedem Aktenzeichen kann ein beliebiger Text

6. Abbildungsoption: Vektor

173

als Inhaltsbeschreibung zugeordnet werden. Die erfaßten Akten können in einer anzugebenden Datei abgelegt und von dieser wieder eingelesen werden. Da unterstellbar ist, daß in dieser Aktenverwaltung häufig gesucht und neue Akten eingefügt werden, speichern wir die Akten als „höhenbalancierten Baum" (engl.: balanced tree). Für einen binären Baum gilt folgende Definition der Ausgeglichenheit: Defintion: Ausgeglichenheit. Ein Baum ist genau dann ausgeglichen, wenn sich für jeden Knoten die Höhen der zugehörigen Teilbäume um höchstens 1 unterscheiden (Adelson-Velskii/Landis, 1962; zitiert nach Wirth, 1983, S.244). Dabei ist die Höhe eines Baumes gleich der maximalen Astlänge (größte Knotenzahl zu einem Baumblatt). Ein solcher ausgeglichener Baum wird zu Ehren von Adelson-Velskii und Landis kurz als AVL-Baum bezeichnet. In einigen LISP-Systemen sind Konstrukte für die Handhabung von AVL-Bäumen integriert (z.B. Cambridge LISP, vgl. Anhang B; Näheres zu AVL-Bäumen vgl. z.B. Kruse, 1984 oder Mühlbacher, 1978). Ein AVL-Baum ist durch einen Vektor abbildbar, der die Wurzel darstellt. Für unsere Aktenverwaltung ist es ein Vektor mit den fünf Elementen: o o o o o

Höhe des Baums, Aktenzeichen (KEY), zugeordnete Aktenbeschreibung (TEXT), linker Teilbaum und rechter Teilbaum.

Die sogenannte Balance B eines Baums ist für einen Knoten, wie folgt, definiert: B ::= Höhe des rechter Teilbaums minus Höhe des linken Teilbaums Zum Aufbau eines AVL-Baums dient ein Anfangsbaum, hier *LEERERBAUM*. In diesen leeren Baum werden zunächst Aktenzeichen, d.h. neue Knoten, eingefügt. Beim Einfügen ist zu überprüfen, ob weiterhin das Kriterium der Ausgeglichenheit gegeben ist. Liegt die Ausgeglichenheit nicht mehr vor, dann ist abhängig vom Baumzustand eine Rotation vorzunehmen. Bei der Rotation ist der jeweilige Wurzelknoten neu zu ermitteln. Die bisherigen Teilbäume sind „umzuhängen", so daß der linke Ast stets zu den kleineren Key-Werten und der rechte Ast zu den größeren Key-Wert führen. Bei dieser Rotation sind vier Fälle zu unterscheiden (vgl. Bild 6.3-1). Das Einfügen eines Knotens ist als Konstruieren eines neuen AVL-Baums implementiert und nicht mit Hilfe des Mutators VECTOR-SET!. Der Grund ist hier das leichtere Verstehen des Beispielprogramms.

174

II. Konstruktionen (Analyse und Synthese)

Fall

1:

Linker Vor der

Teilbaum =>

für L = -2 für G , D - -1 für A,H,Y = 0

Fall

Rechter

Teilbaum

Vor

Rotation

Bai a n c e

für für für für

der

Wurzel

(einfache

Rotation

Bai a n c e

2:

neue

Rotation)

Nach der Balance

vom

G = -2 E = -1 AL,DZ,EL,H,Y D,A,DL = 1

linken

Rotation

für A , H , Y , G = 0 für D = -1

T e i l b a u m => neue Wurzel (doppelte Rotation) Nach der Rotation Balance

für A , D L , G = für sonst =

+1 0

= 0

r

iL

k

'

Àl

Fall

'

3:

Rechter T e i l b a u m => Vor

Balance

für für für für

der

neue Wurzel

Rotation

^

(einfache

Nach

Y = -1 D,A,E,H,GA,HA,X,Z= L - 1 G = 2

1A

ÄL

r

i

der

Balance 0

n

fr

i

Rotation)

Rotation für für für

HA

Y = V = sonst =

H

L

-1 1 0

6. Abbildungsoption: Vektor

Fall

4:

Linker Vor

Teilbaum

der

für für für für

175

vom

rechten

T e i l b a u m => neue Wurzel (doppelte Rotation; Nach der Rotation

Rotation

L - -1 D, A,, E , G A , H A , Y , X , Z = H = 1 G - 2

Balance 0

GA für für s o n s t

G

í



J

= -1 = 0

H

1

i

,

I

1

fl

5T

J

1 HZ X

I

,

,

}

6

H Z

A

E

i

1

,

'

¡U I

HA

,

í

L-

HF H

HA Legende: A

Z

::= A u s p r ä g u n g e n d e s S c h l ü s s e l s ( K E Y ) f ü r d e n binären Baum. Sie repräsentieren einen Knoten als f o l g e n d e n V e k t o r : D

#(2

"D"

"Text"

)

mi t:

#(1

::= "A" < t e x t >

::=

#(1

"E"

KEY

zugeordnete

::= Z u m

*LEERER-BAUM*

*LEERER-BAUM*)

*LEERER-BAUM*

*LEERER-BAUM*)

Daten

eval> *LEERER-BAUM* ==> #(0 NIL NIL) B i l d 6 . 3 - 1 . Ausbai anderen eines Baums durch Rotation - Beispiel zum V e r s t e h e n des K o n s t r u k t e s AVL-BAUM-MONTAGE - (vgl.Pro-gramm 6.3-1)

Das Konstrukt AKTENVERWALTUNG nutzt die Möglichkeit eine LAMBDASchnittstelle (vgl. Abschnitt 1.4.1) für eine unbestimmte Anzahl von Argumenten zu definieren. eval>

(DEFINE AKTENVERWALTUNG (LAMBDA (OPERATION . PARAMETER) ¡Schnittstelle ... ) ;Funktionskörper ==> AKTENVERWALTUNG

176

II. Konstruktionen (Analyse und Synthese)

Die LAMBDA-Variable OPERATION wird an den Wert des ersten Arguments gebunden. Aufgrund der Angabe des Punkt-Paares werden alle Werte von weiteren Argumenten zunächst zu einer Liste zusammengefaßt, dann wird die LAMBDA-Variable PARAMETER an diese Liste gebunden. Gibt es keine weiteren Argumente, hat PARAMETER den Ersatzwert NIL. e v a l > (AKTENVERWALTUNG ' E I N G A B E " A - 1 2 " " F a l l ¡Während d e r A b a r b e i t u n g d i e s e r A p p l i k a t i o n ; düngen g e g e b e n : ; OPERATION — > EINGABE ; PARAMETER—> ("A-12" "Fall Krause")

K r a u s e " ) = = > #T sind folgende Bin-

Diese LAMBDA-Schnittstelle gestattet es z.B, die AKTENVERWALTUNG zum Anzeigen der erfaßten Aktenzeichen mit weniger Argumenten aufzurufen als beim Einfügen eines Aktenzeichens. e v a l > (AKTENVERWALTUNG ' S T R U K T U R - Z E I G E N ) = = > . . . ¡Während d e r A b a r b e i t u n g d i e s e r A p p l i k a t i o n s i n d ; düngen g e g e b e n : ; OPERATION — > S T R U K T U R - Z E I G E N ; PARAMETER — > ( ) ;Da k e i n A r g u m e n t h i e r f ü r a n g e g e b e n i s t , ; w i r d der E r s a t z w e r t ( e n g l . : d e f a u l t ; v a l u e ) angenommen.

folgende

Bin-

Die globale Variable *DATEN* hat den aktuellen Bearbeitungsstand, d.h. den AVL-Baum, als Wert. Wir sichern diesen mit Hilfe des eingebauten SchemeKonstruktes WITH-OUTPUT-TO-FILE. Das Laden des gesicherten AVL-Baumes erfolgt mit dem Konstrukt WITH-INPUT-FROM-FILE. Diese beiden Konstrukte arbeiten mit einem LAMBDA-Konstrukt ohne LAMBDA-Variable („thunk", vgl. Abschnitt 2.3). Es wird ausgeführt mit der angegebenen Datei als aktuelle Datenquelle bzw. Datensenke. eval>

eval>

; ; S i c h e r u n g d e s AVL-Baumes (WITH-OUTPUT-TO-FILE "C: W L I S P - E X W A V L . D A T " (LAMBDA ( ) (WRITE * D A T E N * ) ) = = > . ; ; Laden d e s A V L - B a u m e s (SET! *DATEN* (WITH-INPUT-FROM-FILE "C: W L I S P - E X W A V L . D A T " (LAMBDA ( ) ( R E A D ) ) ) ) = = >

; Si cherungsdatei ;Speicherungsfunktion

; Si cherungsdatei ; Ei n i e s e f u n k t i on

177

6. Abbildungsoption: Vektor

Dokument 6.3-1 Titel: Programm AKTENVERWALTUNG Erstellt: 25.09.89; letzte Aenderung:

19.10.90

AI AI.1

AKTENVERWALTUNG speichert einen Aktenbestand. E i n e A k t e b e s t e h t a u s e i n e m A k t e n z e i c h e n (= K E Y ) u n d e i n e m E r l ä u t e r u n g s t e x t (= T E X T ) . A I . 2 Ein h ö h e n b a l a n c i e r t e r B a u m ( A V L - B a u m ) r e p r ä s e n t i e r t d e n Aktenbestand. A2

Ein n e u e r KEY ist in d e n A V L - B a u m e i n f ü g b a r . g e p r ü f t , ob d e r B a u m a u s g e g l i c h e n i s t , f a l l s w i r d ein neuer A V L - B a u m k o n s t r u i e r t .

A3

Ein T E X T

A4

Eingespeicherter 1öschbar

El

J e d e r B a u m k n o t e n ist e i n V e k t o r , d e r B a u m h ö h e , T E X T und die beiden T e i l b ä u m e a b b i l d e t :

#(

ist durch

einen

KEY

neuen

istausdem

ersetzbar. AVL-Baumnicht

Typ

Zeichenkette

KEY,

)

E2

KEY

E3 E4

TEXT hat einen b e l i e b i g e n Typ ;vgl . A l . l Zum A u s b a l a n c i e r e n des A V L - B a u m e s (beim E i n f ü g e n von neuen Aktenzeichen) wird eine KEY-Gleichheit mit dem S T R I N G - C I = ? - K o n s t r u k t f e s t g e s t e l l t . Es g i b t d a h e r k e i n e Unterscheidung durch eine Gross-/Kl einschreibung. Z . B . : Ist "A-1 - 1 2 3 " d a m i t g l e i c h " a - 1 - 1 2 3 " .

E5 E5.1

hat den

Dabei w i r d nicht,

;vgl.

Al.l

Benutzerschnittstelle: Laden eines A k t e n b e s t a n d e s : ( A K T E N V E R W A L T U N G 'START "") E5.2 Sichern eines A k t e n b e s t a n d e s : ( A K T E N V E R W A L T U N G 'ENDE "") E5.3 Eingeben/Modifizieren einer Akte: (AKTENVERWALTUNG 'EINGABE "" "") E5.4 A n z e i g e n A k t e : (AKTENVERWALTUNG 'GET-TEXT "") E5.5 Kleinste A k t e n z e i c h e n : (AKTENVERWALTUNG 'GET-MIN-KEY) E5.6 G r ö ß t e A k t e n z e i c h e n : (AKTENVERWALTUNG 'GET-MAX-KEY) E5.7 Nächstes A k t e n z e i c h e n : (AKTENVERWALTUNG 'GET-NEXT-KEY) E5.7 B e s t a n d an A k t e n z e i c h e n als A V L - B a u m z e i g e n : (AKTENVERWALTUNG 'STRUKTUR-ZEIGEN)

178

II. Konstruktionen (Analyse und Synthese) T1 B e i s p i e l Tl.l

Aktenbestand: eval > (BEGIN (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG (AKTENVERWALTUNG

'EINGABE 'EINGABE 'EINGABE 'EINGABE 'EINGABE 'EINGABE 'EINGABE 'EINGABE 'ENDE "C:

" L - a - 2 " " F a l l Meyer") " L - a - 2 / b " " F o r t s e t z u n g Meyer") "G-b-3-2" "Plan Westfeld") "Y-345-6" " F a l l Schulze") "Dortmund" " S t a d t p l a n u n g " ) "Herbert" " F a l l Herbert") "A-l-122" "Rechnerbeschaffung": "G-b-3-2" "Plan Neu-Westfeid") W L I S P - E X W A V L . D A T " ) ) ==> . . .

; ; ; T l . 2 E r z e u g t e r AVL-Baum ; // ;;; e v a l > (AKTENVERWALTUNG 'STRUKTUR-ZEIGEN) ==> ;;; Y-345-6 III ;;; L-a-2/b III ;;; L - a - 2 IM ;;; Herbert III ;;; G - b - 3 - 2 131 ;;; Dortmund /2/ ;;; A-l-122 III (DEFINE *DATEN* ( ) ) ( D E F I NE AKTENVERWALTUNG (LAMBDA (OPERATION . PARAMETER) (LETREC ;;;;

K o n s t r u k t o r e n , S e l e k t o r e n und P r ä d i k a t e ((MAKE-BAUM (LAMBDA (HOEHE KEY TEXT L I N K E R - T E I L RECHTER-TEIL) (VECTOR HOEHE KEY TEXT L I N K E R - T E I L R E C H T E R - T E I L ) ) ) (AVL-BAUM? (LAMBDA (BAUM) (COND ((LEERER-BAUM? BAUM) BAUM) ((AND (VECTOR? BAUM) ( = (VECTOR-LENGTH BAUM) 5) (INTEGER? (BAUM-HOEHE BAUM)) ( P O S I T I V E ? (BAUM-HOEHE BAUM)) (STRING? (BAUM-KEY BAUM)) (AVL-BAUM? (LINKER-TEILBAUM BAUM)) (AVL-BAUM? (RECHTER-TEILBAUM BAUM)) (= (BAUM-HOEHE BAUM) (1+ (MAX (BAUM-HOEHE (LINKER-TEILBAUM BAUM)) (BAUM-HOEHE (RECHTER-TEILBAUM BAUM))))) (= (BALANCE BAUM) - 1 ) (OR (LEERER-BAUM? (LINKER-TEILBAUM BAUM)) (STRING-CK? (BAUM-KEY (LINKER-TEILBAUM BAUM)) (BAUM-KEY BAUM))) (OR (LEERER-BAUM? (RECHTER-TEILBAUM BAUM)) (STRING-CK? (BAUM-KEY BAUM) (BAUM-KEY (RECHTER-TEILBAUM BAUM)))))

179

6. Abbildungsoption: Vektor BAUM) (T NIL)))) ;: Zur B e s t i m m u n g der A u s g e g l i c h e n h e i t (BALANCE (LAMBDA (BAUM) (COND ((LEERER-BAUM? BAUM) 0) (T (- (BAUM-HOEHE ( R E C H T E R - T E I L B A U M BAUM)) (BAUM-HOEHE ( L I N K E R - T E I L B A U M B A U M ) ) ) ) ) ) ) ( * L E E R E R - B A U M * (MAKE-BAUM 0

NIL N I L ) )

(LEERER-BAUM? (LAMBDA (BAUM) (AND (VECTOR? BAUM) (= (VECTOR-LENGTH BAUM) 5) (= (BAUM-HOEHE BAUM) 0) (STRING-NULL? (BAUM-KEY BAUM)) (STRING-NULL? (BAUM-TEXT BAUM)) (NULL? ( L I N K E R - T E I L B A U M BAUM)) (NULL? ( R E C H T E R - T E I L B A U M BAUM))))) (BAUM-HOEHE (LAMBDA (BAUM) ( V E C T O R - R E F BAUM 0 ) ) ) (BAUM-KEY (LAMBDA (BAUM) ( V E C T O R - R E F BAUM 1))) (BAUM-TEXT (LAMBDA (BAUM) ( V E C T O R - R E F BAUM 2 ) ) ) (LINKER-TEILBAUM (LAMBDA (BAUM) (VECTOR-REF BAUM 3 ) ) ) (RECHTER-TEILBAUM (LAMBDA (BAUM) (VECTOR-REF BAUM 4 ) ) ) (GET-KEY-AVL-BAUM (LAMBDA (KEY BAUM) (COND ( ( L E E R E R - B A U M ? BAUM) BAUM) ((STRING-CI=? KEY (BAUM-KEY BAUM)) BAUM) ( ( S T R I N G - C K ? KEY (BAUM-KEY BAUM)) ( G E T - K E Y - A V L - B A U M KEY (LINKER-TEILBAUM BAUM))) ( ( S T R I N G - C K ? (BAUM-KEY BAUM) KEY) ( G E T - K E Y - A V L - B A U M KEY ( R E C H T E R - T E I L B A U M BAUM))) (T (ERROR "Fehler bei G E T - K E Y - A V L - B A U M ! " BAUM))))) (GET-MINIMUM-KEY-AVL-BAUM (LAMBDA (BAUM) (COND ( ( L E E R E R - B A U M ? BAUM) BAUM) ((LEERER-BAUM? ( L I N K E R - T E I L B A U M BAUM)) BAUM) (T ( G E T - M I N I M U M - K E Y - A V L - B A U M (LINKER-TEILBAUM B A U M ) ) ) ) ) ) (GET-MAXIMUM-KEY-AVL-BAUM (LAMBDA (BAUM) (COND ( ( L E E R E R - B A U M ? BAUM) BAUM) ((LEERER-BAUM? ( R E C H T E R - T E I L B A U M BAUM)) BAUM) (T ( G E T - M A X I M U M - K E Y - A V L - B A U M ( R E C H T E R - T E I L B A U M BAUM))))))

II. Konstruktionen (Analyse und Synthese) (GET-NEXT-KEY-AVL-BAUM (LAMBDA (KEY BAUM) (COND ((LEERER-BAUM? BAUM) BAUM) ( ( S T R I N G - C K ? KEY (BAUM-KEY BAUM)) (LET ((L-TEIL (GET-NEXT-KEY-AVL-BAUM KEY (LINKER-TEILBAUM BAUM)))) (COND ((LEERER-BAUM? L-TEIL) BAUM) (T L - T E I L ) ) ) ) ((STRING-CI=? KEY (BAUM-KEY BAUM)) (GET-MINIMUM-KEY-AVL-BAUM ( R E C H T E R - T E I L B A U M BAUM))) ( ( S T R I N G - C K ? (BAUM-KEY BAUM) KEY) (GET-NEXT-KEY-AVL-BAUM KEY ( R E C H T E R - T E I L B A U M BAUM))) (T (ERROR "Fehler bei G E T - N E X T - K E Y - A V L - B A U M :" BAUM))))) (AVL-BAUM-MONTAGE (LAMBDA (BAUM L_TEILBAUM R_TEILBAUM) (LET* ((L-HOEHE (BAUM-HOEHE L_TEILBAUM)) (R-HOEHE (BAUM-HOEHE R_TEILBAUM) ) (KNOTENBILDUNG (LAMBDA (KEY TEXT L_BAUM R_BAUM) (MAKE-BAUM (1+ (MAX (BAUM-HOEHE L_BAUM) (BAUM-HOEHE KEY TEXT L_BAUM R.BAUM))))

R.BAUM)))

(COND (;; A u s g e g g l i c h e n (AND (< L - H O E H E (+ R - H O E H E 2)) (< R - H O E H E (+ L - H O E H E 2 ) ) ) (KNOTENBILDUNG (BAUM-KEY BAUM) (BAUM-TEXT BAUM) L_TEILBAUM R_TEILBAUM)) ;; Fall 1 (Bild 6.3-1) ((AND (> L - H O E H E R-HOEHE) (< (BALANCE L.TEILBAUM) 1)) (LET* ((L-L-TEIL ( L I N K E R - T E I L B A U M L.TEILBAUM)) (R-L-TEIL ( R E C H T E R - T E I L B A U M L_TEILBAUM)) (R-L-TEIL&R-TEIL (KNOTENBILDUNG (BAUM-KEY BAUM) (BAUM-TEXT BAUM) R-L-TEIL R.TEILBAUM))) (KNOTENBILDUNG (BAUM-KEY L.TEILBAUM) (BAUM-TEXT L.TEILBAUM) L-L-TEIL R-L-TEIL&R-TEIL)))

6. Abbildungsoption: Vektor

181

;; Fall 2 (Bild 6.3-1) ( ( A N D ( > L-HOEHE R-HOEHE) ( = (BALANCE L _ T E I L B A U M ) 1 ) ) ( L E T * ((NEUE-WURZEL (RECHTER-TEILBAUM L_TEILBAUM)) ( L - R - L - T E I L (LINKER-TEILBAUM NEUE-WURZEL)) ( L - L - T E I L (LINKER-TEILBAUM L_TEILBAUM)) ( R - R - L - T E I L (RECHTER-TEILBAUM NEUE-WURZEL)) (L-L-TEIL&L-R-L-TEIL (KNOTENBILDUNG (BAUM-KEY L _ T E I L B A U M ) (BAUM-TEXT L _ T E I L B A U M ) L-L-TEIL L-R-L-TEIL)) (R-R-L-TEIL&R-TEIL (KNOTENBILDUNG (BAUM-KEY BAUM) (BAUM-TEXT BAUM) R - R - L - T E I L R_TEILBAUM))) (KNOTENBILDUNG (BAUM-KEY NEUE-WURZEL) (BAUM-TEXT NEUE-WURZEL) L-L-TEIL&L-R-L-TEIL R-R-L-TEIL&R-TEIL))) ;; Fal1 3 ( B i l d 6 . 3 - 1 ) ( ( A N D ( < L-HOEHE R-HOEHE) ( > (BALANCE R _ T E I L B A U M ) - 1 ) ) ( L E T * ( ( R - R - T E I L (RECHTER-TEILBAUM R_TEILBAUM)) ( L - R - T E I L (LINKER-TEILBAUM R_TEILBAUM)) (L-TEIL&L-R-TEIL (KNOTENBILDUNG (BAUM-KEY BAUM) (BAUM-TEXT BAUM) L_TEILBAUM L - R - T E I L ) ) ) (KNOTENBILDUNG (BAUM-KEY R _ T E I L B A U M ) (BAUM-TEXT R . T E I L B A U M ) L-TEIL&L-R-TEIL R-R-TEIL))) ;; Fal1 4 ( B i l d 6 . 3 - 1 ) ( ( A N D ( < L-HOEHE R-HOEHE) ( = (BALANCE R _ T E I L B A U M ) - 1 ) ) ( L E T * ((NEUE-WURZEL (LINKER-TEILBAUM R_TEILBAUM)) (R-L-R-TEIL (RECHTER-TEILBAUM NEUE-WURZEL)) ( R - R - T E I L (RECHTER-TEILBAUM R_TEILBAUM)) (L-L-R-TEIL ( L I N K E R - T E I L B A U M NEUE-WÜRZEL)) (R-L-R-TEIL&R-R-TEIL (KNOTENBILDUNG (BAUM-KEY R _ T E I L B A U M ) (BAUM-TEXT R _ T E I L B A U M ) R-L-R-TEIL R-R-TEIL)) (L-TEIL&L-L-R-TEIL (KNOTENBILDUNG (BAUM-KEY BAUM) (BAUM-TEXT BAUM) L_TEILBAUM L - L - R - T E I L ) ) )

II. Konstruktionen (Analyse und Synthese) (KNOTENBILDUNG (BAUM-KEY N E U E - W U R Z E L ) (BAUM-TEXT N E U E - W U R Z E L ) L-TEIL&L-L-R-TEIL R-L-R-TEIL&R-R-TEIL) ) ) ) ) ) ) (EINFUEGEN-IN-AVL-BAUM (LAMBDA (KEY TEXT BAUM) (COND ((LEERER-BAUM? BAUM) (MAKE-BAUM 1 KEY TEXT * L E E R E R - B A U M * * L E E R E R - B A U M * ) ) ((STRING-CI=? KEY (BAUM-KEY BAUM)) (MAKE-BAUM (BAUM-HOEHE BAUM) (BAUM-KEY BAUM) TEXT ( L I N K E R - T E I L B A U M BAUM) ( R E C H T E R - T E I L B A U M BAUM))) ( ( S T R I N G - C K ? KEY (BAUM-KEY BAUM)) (AVL-BAUM-MONTAGE BAUM (EINFUEGEN-IN-AVL-BAUM KEY TEXT ( L I N K E R - T E I L B A U M BAUM)) ( R E C H T E R - T E I L B A U M BAUM))) ( ( S T R I N G - C K ? (BAUM-KEY BAUM) KEY) (AVL-BAUM-MONTAGE BAUM ( L I N K E R - T E I L B A U M BAUM) (EINFUEGEN-IN-AVL-BAUM KEY TEXT (RECHTER-TEILBAUM BAUM))))))) (AVL-BAUM-STRUKTUR-ZEIGEN (LAMBDA (BAUM A N Z A H L _ S P A C E ) (LETREC ((WRITE-SPACE (LAMBDA (N) (COND ((= N 0) NIL) (T (WRITE-CHAR " ") ( W R I T E - S P A C E ( - 1 + N)))))) (ZEIGEN (LAMBDA (BAUM E I N G E R U E C K T ) (COND ( ( L E E R E R - B A U M ? BAUM) *THE-NON-PRINTING-OBJECT*) (T (ZEIGEN ( R E C H T E R - T E I L B A U M BAUM) (+ EINGERUECKT A N Z A H L . S P A C E ) ) ( W R I T E - S P A C E EINGERUECKT) (WRITELN (BAUM-KEY BAUM) " /" (BAUM-HOEHE BAUM) "/ ") (ZEIGEN ( L I N K E R - T E I L B A U M BAUM) (+ EINGERUECKT A N Z A H L _ S P A C E ) ) ) ) ) ) ) (ZEIGEN BAUM A N Z A H L _ S P A C E ) ) ) ) )

183

6. Abbildungsoption: Vektor ;;;;

Auswahl des B e n t z e r k o m m a n d o s

(dispatch

function)

(COND ((EQ? OPERATION 'START) (SET! *DATEN* (WITH -1NPUT-FROM-FILE (CAR PARAMETER) (LAMBDA ( ) (READ)))) T) ((EQ? OPERATION 'ENDE) (WITH-OUTPUT-TO-FILE (CAR PARAMETER) (LAMBDA ( ) (WRITE * D A T E N * ) ) ) T) ((AVL-BAUM? *DATEN*) (COND ((EQ? OPERATION 'EINGABE) (SET! *DATEN* (EINFLIEGEN - IN-AVL-BAUM (CAR PARAMETER) (CADR PARAMETER) *DATEN*)) T) ((EQ? OPERATION 'GET-TEXT) (BAUM-TEXT (GET-KEY-AVL-BAUM (CAR PARAMETER) * D A T E N * ) ) ) ((EQ? OPERATION ' G E T - M I N - K E Y ) (BAUM-KEY (GET-MINIMUM-KEY-AVL-BAUM * D A T E N * ) ) ) ((EQ? OPERATION 'GET-MAX-KEY) (BAUM-KEY (GET-MAXIMUM-KEY-AVL-BAUM * D A T E N * ) ) ) ((EQ? OPERATION 'GET-NEXT-KEY) ( I F (STRING-CI=? (CAR PARAMETER) (BAUM-KEY (GET-MAXIMUM-KEY-AVL-BAUM *DATEN*))) (BEGIN (WRITELN " H i n w e i s : E i n g a b e war g r o e s s t e r Key! " ) (CAR PARAMETER)) (BAUM-KEY (GET-NEXT-KEY-AVL-BAUM (CAR PARAMETER) * D A T E N * ) ) ) ) ((EQ? OPERATION 'STRUKTUR-ZEIGEN) (AVL-BAUM-STRUKTUR-ZEIGEN *DATEN* 2 ) ) (T (ERROR " O p e r a t i o n n i c h t i m p l e m e n t i e r t : " OPERATION)))) ((AND (NULL? *DATEN*) (EQ? OPERATION ' E I N G A B E ) ) (SET! *DATEN* (EINFUEGEN-IN-AVL-BAUM (CAR PARAMETER) (CADR PARAMETER) *LEERER-BAUM*)) T) (T (ERROR " K e i n e w o h l s t r u k t u r i e r t e n D a t e n : " *DATEN*)))))) Programm 6 . 3 - 1 Aktenverwaltung

mittels

AVL-Baum

(Basis:

Vektor)

184

II. Konstruktionen (Analyse und Synthese)

6.4 Zusammenfassung: Vektor Ein Vektor ist eine Struktur mit konstanter Länge. Seine Elemente können beliebige symbolische Ausdrücke sein, also auch wieder Vektoren. Ein Vektor hat sich selbst als Wert (EVAL-Regel 1, vgl. Abschnitt 1.2). Die Zugriffzeit ist unabhängig davon, ob auf ein Element mit kleinem oder großem Index zugegriffen wird. Für einen dünn besetzten Vektor ist die A-Liste eine zweckmäßige Alternative (vgl. Abschnitt 5.2). Charakteristisches

Beispiel für Abschnitt 6:

; ; ; ; V e r w a l t u n g von A d r e s s e n e v a l > ( D E F I NE A D R E S S E N - S T R U K T U R # ( " A n r e d e & T i t e l " " V o r n a m e & Name" " S t r a s s e & Nummer" "Postfach" "Landeskenner & Postleitzahl & Ortsname")) ==> ADRESSEN-STRUKTUR e v a l > ( D E F I NE P R I V A T - A D R E S S E ( L A M B D A ( A N R E D E NAME S T R A S S E O R T ) (LET ((ADRESSE (MAKE-VECTOR (VECTOR-LENGTH ADRESSEN-STRUKTUR) ()))) (VECTOR-SET! ADRESSE 0 ANREDE) ( V E C T O R - S E T ! A D R E S S E 1 NAME) (VECTOR-SET! ADRESSE 2 STRASSE) ( V E C T O R - S E T ! A D R E S S E 4 ORT) ADRESSE))) ==> PRIVAT-ADRESSE eval> eval>

eval>

( D E F I NE K R A U S E ( P R I V A T - A D R E S S E " H e r r " "Hans Krause" "Entenweg

7"

"D-1000

Berlin")) = = > KRAUSE

(DEFINE DRUCKE-ADRESSE (LAMBDA ( A D R E S S E ) (LET ((V-LAENGE (VECTOR-LENGTH A D R E S S E ) ) ) (DO ( ( I 0 ( 1 + I ) ) ) ( ( = I V-LAENGE) *THE-NON-PRINTING-OBJECT*) ( I F (VECTOR-REF ADRESSE I ) (WRITELN (VECTOR-REF ADRESSE I))))))) ==> DRUCKE-ADRESSE (DRUCKE-ADRESSE Herr Hans K r a u s e Entenweg 7 D-1000 B e r l i n

KRAUSE)

==>

7. Abbildungsoption: Zeichenkette und Symbol Eine Zeichenkette (engl.: string) ist eine Sequenz aus einzelnen Zeichen (Elementen). Im Unterschied zur Liste oder zum Vektor können die Elemente dieser

7. Abbildungsoption: Zeichenkette und Symbol

185

Sequenz jedoch nicht wieder Zeichenketten sein. Eine Zeichenkette hat sich selbst als Wert (EVAL-Regel 1, vgl. Abschnitt 1.2). Bisher haben wir daher eine Zeichenkette als Konstante benutzt. In diesem Abschnitt konstruieren, selektieren und manipulieren wir solche „Konstanten". Die Zeichenkette ist damit eine eigenständige Abbildungsoption wie die Liste oder der Vektor. Als Beispiel dient die Aufgabe zwei Muster miteinander zu vergleichen (engl.: pattern matching). Ein entsprechendes Konstrukt realisieren wir zunächst mit der Abbildungsoption Liste (vgl. Abschnitt 7.1). Dieses SIMPLE-MATCH-Konstrukt arbeitet „naturgemäß" mit CAR- und CDR-Selektoren. Um Zeichenketten vergleichen zu können, implementieren wir entsprechende CAR-STRING- und CDR-STRING-Konstrukte mit Hilfe der eingebauten String-Konstrukten (vgl. Abschnitt 7.2). LISP-Systeme verfügen über ein große Zahl spezieller String-Konstrukte. Wir wollen hier primär den Aspekt Abbildungsoption vertiefen und erwähnen daher nur einige Konstrukte aus dem String-Konstrukte-Repertoire von PC Scheme. Im Rahmen des Vergleichs zweier Muster führen wir Variable (engl.: match variables) ein. Das so erweiterte Konstrukt MUSTERVERGLEICH nutzt die Repräsentation des Symbols, d.h. das Ergebnis der PRINT-Phase (kurz: PRINTName) als Informationsträger. Bisher haben wir ein Symbol mit einem Wert oder einer Eigenschaft (P-Liste) verknüpft. Der im READ-EVAL-PRINT-Zyklus (vgl. Abschnitt 1.2) zurückgegebene PRINT-Name diente zur Identifizierung. Er wurde als eine Konstante (Literalatom) betrachtet. Hier wird dieser PRINT-Namen selbst selektiert und neu konstruiert (vgl. Abschnitt 7.3). Daß leistungsfähige MATCH-Konstrukte einen eigenständigen „Denkrahmen" (vgl. Abschnitt 4) bilden, skizzieren wir mit einem kleinen Vorübersetzer („Precompiler" oder „Präprozessor"). Er setzt Pseudocode-Formulierungen in LISP-Code um. Er transformiert z.B. eine „WHILE ... DO ... OD"-Iteration (vgl. Abschnitt 2.2.3) in ein rekursives LISP-Konstrukt. Dazu ist ein neues Symbol zu generieren, das keine Namensgleichheit mit anderen Symbolen hat. Es darf nicht mit schon existierenden Symbolen bzw. mit Symbolen, die als spätere Eingabe vorkommen, verwechselbar sein. Ein solches, einmaliges Symbol generiert das eingebaute Konstrukt GENSYM (vgl. Abschnitt 7.4).

7.1 Mustervergleich Programme, die Muster miteinander vergleichen, sind ein klassisches Anwendungsgebiet für LISP. Die LISP-Literatur befaßt sich eingehend mit dem Thema „Pattern Matching" (vgl. z.B. Müller, 1985, S. 151 ff; Stoyan/Görz, 1984, S. 235 ff oder Winston/Horn, 1984, S. 253 ff). Dabei ist anzumerken, daß der Terminus „Mustervergleich" eine unzureichende Übersetung darstellt. Er vermittelt spontan den Eindruck, es wird nur verglichen im Sinne einer Klassifikation.

186

II. Konstruktionen (Analyse und Synthese)

Vielmehr zielt „pattern matching" jedoch auf ein Paradigma, auf einen eigenständigen „Denkrahmen" für die Problemanalyse und Lösungsfindung. Ein Muster beschreibt den Typ einer „Einheit". Beim Vergleich wird entschieden, ob ein symbolischer Ausdruck „paßt", d.h. ob er dieser Typbeschreibung entspricht. Ein Muster selbst besteht aus einer linearen Folge von Elementen. Das Muster wird von links nach rechts mit dem zu prüfenden symbolischen Ausdruck verglichen. Wir nehmen zunächst an, daß das Muster und der zu prüfende symbolische Ausdruck Listen sind. Die Listenelemente unseres Musters können sein: o ? (ein Fragezeichen), o * (ein Stern) oder o ein zu erfüllendes Kennwort (ein sonstiges Symbol). Ein Fragezeichen im Muster steht für ein beliebiges Element im Prüfling. Anders formuliert: Das Fragezeichen deckt ein Element. Der Stern deckt eine Folge von beliebigen Elementen (incl. einer leeren Folge). Er deckt ein ganzes Segment. Jedes andere Symbol im Muster deckt nur das gleiche Symbol im Prüfling. Ein solch einfacher Mustervergleich kann z.B. folgende Entscheidungen treffen: eval>

eval>

(SIMPLE-MATCH ' ( E I N E ? ANALYSE SICHERT * ) ' ( E I N E ZWECKMAESSIGE ANALYSE SICHERT DEN AUTOMATIONSERFOLG) ) ==> #T (SIMPLE-MATCH ' ( E I N E * ANALYSE SICHERT ? ) ' ( E I N E ZWECKMAESSIGE ANALYSE SICHERT DEN AUTOMATIONSERFOLG) ) ==> ()

¡Muster ¡Prüfling ¡Ergebnis ¡Muster ¡Prüfling ;Ergebni s

Für den Vergleich von Listen definieren wir das SIMPLE-MATCH-Konstrukt als eine rekursive Funktion, wie folgt:

7. Abbildungsoption: Zeichenkette und Symbol eval>

( D E F I N E S I M P L E - M A T C H (LAMBDA (MUSTER L I S T E ) (COND ( ( A N D ( N U L L ? MUSTER) ( N U L L ? L I S T E ) ) T) ((AND (NULL? L I S T E ) ( N U L L ? (CDR M U S T E R ) ) ( E Q ? ' * (CAR M U S T E R ) ) ) T) ( ( O R ( N U L L ? MUSTER) ( N U L L ? L I S T E ) ) N I L ) (COR ( E Q ? (CAR MUSTER) (CAR L I S T E ) ) ( E Q ? ' ? (CAR M U S T E R ) ) ) ( S I M P L E - M A T C H (CDR MUSTER) (CDR L I S T E ) ) ) ;; E r l ä u t e r u n g s i e h e Text ( ( E Q ? ' * (CAR M U S T E R ) ) (COND ( ( S I M P L E - M A T C H (CDR MUSTER) (CDR L I S T E ) ) T) ((SIMPLE-MATCH MUSTER (CDR L I S T E ) ) T ) ((SIMPLE-MATCH (CDR MUSTER) L I S T E ) T ) (T N I L ) ) ) (T N I L ) ) ) ) = = > S I M P L E - M A T C H

187

;*-Fall

A

;*-Fall

B

; * - Fa 11 C ; * - Fa 11 D

Die erste Klausel des ersten obigen COND-Konstruktes prüft, ob das Muster (hier LAMBDA-Variabale MUSTER) und der Prüfling (hier LAMBDA-Variable LISTE) zum gleichen Zeitpunkt abgearbeitet sind; also faktisch gleich lang sind. Ist die Liste abgearbeitet und das letzte Element von Muster ein Stern, dann paßt das Muster, da wir unterstellen, daß der Stern auch ein leeres Segment deckt. Ist die Liste kürzer, dann kann das Muster nicht passen (3. Klausel). Ist das erste Element im Muster gleich dem ersten Element im Prüfling, dann entscheidet der Mustervergleich der Restlisten, ob das Muster paßt oder nicht (4. Klausel). Ist das erste Element im Muster das Fragezeichen, dann paßt es auf jedes erste Element im Prüfling. Auch dann entscheidet der Mustervergleich der Restlisten (daher auch 4. Klausel). Ist das erste Element des Musters ein Stern, dann sind folgende Fälle zu unterscheiden (5. Klausel): o Der Stern steht für ein Element im Prüfling (*-Fall A). Der Mustervergleich ist mit den Restlisten fortzusetzen. Ist der Vergleich der Restlisten erfolgreich, dann paßt das Muster. o Der Stern steht für mehrere Elemente im Prüfling (*-Fall B). Wir behalten das aktuelle Muster bei, d.h. verkürzen es nicht und vergleichen es mit dem Rest des Prüflings. Dadurch ist es beim erneuten Vergleich (CAR MUSTER) wieder der Stern, der dann mit dem nächsten Element des Prüflings verglichen wird. o Der Stern steht für kein Element im Prüfling (*-Fall C). Wir verkürzen das Muster, so daß wir den Stern „aufgeben", und vergleichen das verkürzte Muster mit dem aktuellen Prüfling.

II. Konstruktionen (Analyse und Synthese)

188

o Die rekursiven SIMPLE-MATCH-Anwendungen stehen im COND-Konstrukt als Prädikate. Führt keines zu einem Wert ungleich NIL, dann paßt das Muster nicht. Zur leichteren Erkennbarkeit ist eine entsprechende T-Klausel formuliert (*-Fall D).

7.2 STRING-Konstrukte Beispiel: Einfacher Mustervergleich

für

Zeichenketten

Im bisherigen SIMPLE-MATCH-Konstrukt (vgl. Abschnitt 7.1) sind das Muster und der Prüfling Listen. Wir entwickeln daraus ein SIMPLE-MATCH-Konstrukt, das mit Zeichenketten arbeitet. Statt Symbole in einer Liste zu prüfen, sind Teile von Zeichenketten zu prüfen. Das obige Beispiel ist für dieses stringbasierte SIMPLE-MATCH-Konstrukt, wie folgt, zu notieren: eval> (SIMPLE-MATCH "Eine ? Analyse s i c h e r t *" "Eine zweckmaessige Analyse s i c h e r t den Automationserfolg")) ==> #T An die Stelle des Prädikates NULL? für die Liste tritt das in Scheme eingebaute Prädikat STRING-NULL?. Das Prädikat EQ? wird durch das eingebaute Prädikat STRING=? ersetzt. Der Wert des STRING-NULL?-Konstruktes ist nur dann ungleich NIL, also „wahr", wenn die Länge der Zeichenkette gleich Null ist. Die Länge einer Zeichenkette bestimmt das Konstrukt STRING-LENGTH. Das STRING=?-Konstrukt vergleicht seine beiden Argumente Zeichen für Zeichen. Es hat einen Wert ungleich NIL, wenn für jedes Zeichen Gleichheit gilt und die beiden Zeichenketten gleich lang sind. Die folgenden Beispiele zeigen die beiden Prädikate für Zeichenketten. eval> eval> eval> eval> eval> eval>

(STRING-NULL? "") ==> #T (DEFINE F00 " ") ==> F00 (STRING-LENGTH F00) ==> 1 (STRING-NULL? F00) ==> () (STRING=? "more" "more") ==> #T (STRING=? "MoRe " "MoRe") ==> () ¡Länge ungleich

Zum Ersatz der Selektoren CAR und CDR sind entsprechende Selektoren für eine Zeichenkette selbst zu definieren. Wir nennen sie CAR-STRING und CDR-STRING. Dazu betrachten wir das Zeichen Zwischenraum, allgemein auch Space oder Blank genannt, als Trennzeichen für die Zeichenteilketten. Der Zwischenraum als einzelnes Zeichen (engl.: character) hat in Scheme die Repräsentation #\SPACE. Ob eine Zeichenkette vorliegt, stellt das Prädikat STRING? fest. Ob ein einzelnes Zeichen vorliegt, stellt das Prädikat CHAR? fest. Ein Zeichen einer Zeichenkette selektiert das STRING-REF-Konstrukt. Es ist, wie folgt, definiert:

7. Abbildungsoption: Zeichenkette und Symbol eval>

(STRING-REF

)

189 — >

mit:

::= E i n e

Zeichenkette.

::= E i n e g a n z z a h l i g e , n i c h t n e g a t i v e A n g a b e d e r P o s i t i o n d e s zu s e l e k t i e r e n d e n Z e i c h e n s . Die Z ä h l u n g d e r P o s i t i o n b e g i n n t bei 0. B e d i n g u n g ist: ( A N D ( ) (< (STRING-LENGTH )))

::= Das s e l e k t i e r t e Z e i c h e n d e r Z e i c h e n k e t t e . A c h t u n g ! Es i s t v o m T y p Z e i c h e n ( e n g l . : c h a r a c t e r ) nicht vom Typ Z e i c h e n k e t t e (engl.: string). Vgl. dazu die folgenden Beispiele.

eval>

(DEFINE

eval>

(STRING-LENGTH

F00

"Requirements FOO) = = >

Engineering")

==>

F00

24

eval>

(STRING-REF

F00 3) = = >

eval>

(STRING-REF

F00 2 4 ) = = >

#\u ERROR

eval>

(STRING-REF

FOO 2 3 ) = = >

#\g

eval>

(STRING-REF

FOO

#\SPACE

eval>

(STRING?

FOO) = = >

eval>

(STRING?

(STRING-REF

eval>

(CHAR?

(STRING-REF

eval>

(CHAR?

FOO) = = >

12) = = >

...

;Index

zu

groß

#T FOO

FOO

12)) ==>

12)) ==>

()

#T

()

Der Wert des STRING-REF-Konstruktes ist vom Typ Zeichen, d.h. das Prädikat CHAR? ist erfüllt, und nicht vom Typ Zeichenkette, d.h. das Prädikat STRING? wird nicht erfüllt. Zum Prüfen eines mit dem STRING-REF-Konstrukt selektiertem Zeichen auf #\SPACE, können wir daher nicht das Prädikat STRING=? nutzen. Es ist mit dem Prädikat CHAR=? zu prüfen. Wir definieren daher ein Konstrukt SPACE?, das prüft, ob an einer bestimmten Stelle ein Zwischenraum in der Zeichenkette gegeben ist, wie folgt: eval>

(DEFINE SPACE? (LAMBDA (STRING (CHAR=? (STRING-REF STRING

POS) POS) # \ S P A C E ) ) )

==>

SPACE?

Analog zu einer Liste, bei der zur Trennung der Elemente ein Zwischenraum ausreicht, jedoch beliebig viele Zwischenräume stehen können, fassen wir für das Selektieren einer Zeichenteilkette ebenfalls beliebig viele Zwischenräume als ein Trennzeichen auf. Für die zu definierenden Konstrukte CAR-STRING und CDR-STRING unterstellen wir folgende Ergebnisse: eval>

(CAR-STRING " ==> "more"

m o r e L I S P ") ¡Keine f ü h r e n d e n und ; Zwischenräume.

nachfolgenden

190

II. Konstruktionen (Analyse und Synthese)

eval > ( C D R - S T R I N G " = = > " LISP "

m o r e L I S P ") ¡Führende und n a c h f o l g e n d e ; bleiben erhalten.

Zwischenräume

Die Annahme, daß mehrere Zwischenräume als ein Trennzeichen gewertet werden, erleichtert das Formulieren des Musters. Ein Nachteil ist jedoch, daß wir die Zusicherung aufgeben, aus den selektierten Teile wieder das Ganze in ursprünglicher Form erstellen zu können (vgl. Abschnitt 4.2). Das STRINGAPPEND-Konstrukt verbindet zwei Zeichenketten zu einer. Wenden wir dieses Konstrukt auf die selektierten Zeichenteilketten an, dann fehlen dem Ergebnis die Zwischenräume, die zu Beginn der Zeichenkette standen. eval>

(STRING-APPEND (CAR-STRING (CDR-STRING

" "

more more

LISP LISP

") ")) = = >

"more

LISP "

Unsere Selektoren CAR-STRING und CDR-STRING nutzen den in PC Scheme eingebauten Selektor SUBSTRING. Das SUBSTRING-Konstrukt ist, wie folgt, definiert: eval>

(SUBSTRING



) ==>

mit:





::= E i n e

Zeichenkette.

::= E i n e g a n z z a h l i g e , n i c h t n e g a t i v e A n g a b e d e r P o s i t i o n d e s e r s t e n Z e i c h e n s d e r zu s e l e k t i e r e n d e n Zeichentei1 kette. Die Zählung der P o s i t i o n b e g i n n t bei 0. B e d i n g u n g i s t : ( A N D ( ) ( ( S T R I N G - L E N G T H < s t r i n g > ) ) ) ::= E i n e g a n z z a h l i g e , n i c h t n e g a t i v e A n g a b e d e r Position des ersten Z e i c h e n s , das nicht m e h r zur Zeichentei1 kette gehören soll. Die Zählung d e r P o s i t i o n b e g i n n t bei 0. B e d i n g u n g i s t : (AND ( ) ( ) . V g l . d a z u d i e f o l g e n den B e i s p i e l e .

eval>

(DEFINE

eval>

(SUBSTRING

F00

F00 0 12) = = >

"Requirements

"Requirements"

Engineering")

eval>

(SUBSTRING

F00 0 24) = = >

"Requirements

==>

F00

Engineering"

191

7. Abbildungsoption: Zeichenkette und Symbol

eval>

( S U B S T R I N G F00 24 2 4 ) = = >

eval>

(STRING-NULL?

eval>

(SUBSTRING

F00 13 12) = = > ERROR

eval>

(SUBSTRING

F00 0 2 7 ) = = > ERROR

(SUBSTRING

"" F00 13 1 3 ) ) ... ...

= = > #T ¡Ende v o r

Start

¡Endeangabe

zu

groß

Das CAR-STRING-Konstrukt definieren wir als eine LISP-Funktion mit dem eigentlichen Selektionskonstrukt VON-BIS-ANALYSE und den Hilfsgrößen wie z.B. SPACE? als lokale Funktionen. Die rekursive VON-BIS-ANALYSEFunktion ermittelt für die Anwendung des SUBSTRING-Konstruktes die - und -Werte. Sie arbeitet mit zwei Zeigern. Der erste Zeiger ist der Wert ihrer LAMBDA-Variablen CAR-START. Dieser Zeiger wird auf das erste Zeichen, das ungleich #\SPACE ist, gesetzt. Der zweite Zeiger ist der Wert ihrer LAMBDA-Variablen AKTUELLE-POSITION. Dieser wird so lange verschoben, bis er auf dem ersten Zwischenraum nach einem Zeichen ungleich #\SPACE steht oder das Ende der Zeichenkette erreicht wurde. Zum Überspringen von führenden Zwischenräumen verwenden wir die lokale Variable VORSPANN-SPACE? im Sinne eines Schalters. Zunächst ist ihr Wert T. Wird das erste Zeichen ungleich #\SPACE erkannt, erhält sie den Wert NIL. Ist ihr Wert NIL und wird ein Zeichen gleich #\SPACE erkannt, dann ist das Ende der Zeichenteilkette erreicht. Zu beachten ist, daß das SUBSTRING-Konstrukt als -Wert die Position nach unserer CAR-STRING-Zeichenteilkette benötigt. Wir definieren somit folgendes CAR-STRING-Konstrukt: eval>

( D E F I N E C A R - S T R I N G (LAMBDA ( S T R I N G ) (LETREC ( ( E N D E - P O S I T I O N ( - 1 + (STRING-LENGTH S T R I N G ) ) ) ( V O R S P A N N - S P A C E ? T) ( S P A C E ? (LAMBDA ( P O S ) (CHAR=? ( S T R I N G - R E F S T R I N G POS) # \ S P A C E ) ) ) ( V O N - B I S - A N A L Y S E (LAMBDA ( C A R - S T A R T A K T U E L L E - P O S I T I O N ) (COND ( ( < = A K T U E L L E - P O S I T I O N E N D E - P O S I T I O N ) (COND ( ( A N D V O R S P A N N - S P A C E ? (SPACE? AKTUELLE-POSITION)) (VON-BIS-ANALYSE (1+ CAR-START) (1+ AKTUELLE-POSITION))) ((SPACE? AKTUELLE-POSITION) (SUBSTRING STRING CAR-START AKTUELLE-POSITION)) (T ( S E T ! V O R S P A N N - S P A C E ? N I L ) (VON-BIS-ANALYSE CAR-START (1+ AKTUELLE-POSITION))))) ( ( = AKTUELLE-POSITION (1+ ENDE-POSITION)) (SUBSTRING STRING CAR-START AKTUELLE-POSITION)) (T (ERROR " K e i n C A R - S T R I N G b e s t i m m t : " STRING)))))) (VON-BIS-ANALYSE 0 0 ) ) ) ) ==> CAR-STRING

192

II. Konstruktionen (Analyse und Synthese)

Das CDR-STRING-Konstrukt ist analog zum CAR-STRING-Konstrukt aufgebaut. Die eigentliche Berechnung der Positionen für das SUBSTRING-Konstrukt vollzieht die lokale Funktion VON-ANALYSE. Hier ist nur die Anfangsposition der zu selektierenden Zeichenteilkette durch den Zeiger AKTUELLEPOSITION zu ermitteln. Die Endeposition ist identisch mit der gesamten Zeichenkette; sie ist mit dem STRING-LENGTH-Konstrukt bestimmbar. Wenn eine Zeichenkette leer ist oder nur eine Zeichenteilkette für das CAR-STRING-Konstrukt enthält, ist der Wert des CDR-STRING-Konstruktes die leere Zeichenkette. Im Sinne unseres Verbundes aus Konstruktor, Selektor, Prädikat und Mutator erstellen wir die leere Zeichenkette mit dem Konstruktor MAKE-STRING. Das MAKE-STRING-Konstrukt ist, wie folgt, definiert: eval>

(MAKE-STRING





{})

==>

: := E i n e g a n z z a h l i g e , n i c h t n e g a t i v e A n g a b e d e r L ä n g e d e r Z e i c h e n k e t t e . In PC S c h e m e b e s t e h t die R e s t r i k t i o n : (AND ( ) ( 1 6 3 8 0 ) ) ::= Ein e i n z e l n e s Z e i c h e n als F ü l l z e i c h e n f ü r d i e Z e i c h e n k e t t e . Ist k e i n F ü l l z e i c h e n a n g e g e b e n v e r w e n d e t PC S c h e m e # \ S P A C E . ::= E i n e

eval>

(MAKE-STRING

eval>

(STRING-NULL?

eval>

(MAKE-STRING

eval>

(DEFINE

eval>

(MAKE-STRING

Zeichenkette

5 #\g) ==>

(* 2 3) # \ a ) #\|)

==>

4 STRICH)

angegebenen

"ggggg"

(MAKE-STRING

STRICH

mit der

0)) ==> ==>

#T

"aaaaaa"

STRICH ==>"||||"

Wir definieren damit das CDR-STRING-Konstrukt, wie folgt: eval>

(DEFINE CDR-STRING (LAMBDA (STRING) (LETREC ((ENDE-POSITION (-1+ (STRING-LENGTH STRING))) ( V O R S P A N N - S P A C E ? T) (SPACE? (LAMBDA (POS) (CHAR=? ( S T R I N G - R E F S T R I N G POS) # \ S P A C E ) ) )

Länge.

7. Abbildungsoption: Zeichenkette und Symbol

193

(VON-ANALYSE (LAMBDA ( A K T U E L L E - P O S IT ION) (COND ( ( < = A K T U E L L E - P O S I T I O N E N D E - P O S I T I O N ) (COND ((AND V O R S P A N N - S P A C E ? (SPACE? AKTUELLE-POSITION)) (VON-ANALYSE (1+ AKTUELLE-POSITION))) ((SPACE? AKTUELLE-POSITION) (SUBSTRING STRING AKTUELLE-POSITION (1+ ENDE-POSITION))) (T ( S E T ! VORSPANN-SPACE? N I L ) (VON-ANALYSE (1+ AKTUELLE-POSITION))))) ( ( = AKTUELLE-POSITION (1+ ENDE-POSITION)) (MAKE-STRING 0 ) ) (T (ERROR " K e i n C D R - S T R I N G b e s t i m m t : " STRING)))))) (VON-ANALYSE 0 ) ) ) ) = = > C D R - S T R I N G

Mit den erstellten Selektoren CAR-STRING und CDR-STRING können wir den obigen Mustervergleich für Zeichenketten formulieren. Das SIMPLE-MATCHKonstrukt hat dann folgende Definition: eval>

( D E F I N E S I M P L E - M A T C H (LAMBDA (MUSTER STRING) (COND ( ( A N D ( S T R I N G - N U L L ? MUSTER) ( S T R I N G - N U L L ? S T R I N G ) ) T) ((AND

(STRING-NULL? (STRING-NULL? (STRING=? " * "

((OR

(STRING-NULL? (STRING-NULL?

((OR

(STRING=?

STRING) (CDR-STRING MUSTER)) (CAR-STRING MUSTER))) MUSTER) STRING))

T)

NIL)

(CAR-STRING

MUSTER) (CAR-STRING (STRING=? " ? " (CAR-STRING MUSTER))) ( S I M P L E - M A T C H ( C D R - S T R I N G MUSTER) (CDR-STRING STRING)))

STRING))

( ( S T R I N G = ? " * " (CAR-STRING MUSTER)) (COND ( ( S I M P L E - M A T C H ( C D R - S T R I N G MUSTER) (CDR-STRING STRING)) ( ( S I M P L E - M A T C H MUSTER (CDR-STRING STRING)) ( ( S I M P L E - M A T C H ( C D R - S T R I N G MUSTER) S T R I N G ) T) (T N I L ) ) ) (T N I L ) ) ) ) = = > S I M P L E - M A T C H eval>

eval>

(SIMPLE-MATCH "Eine ? Analyse sichert *" "Eine Analyse sichert E r f o l g " ) ==> () ¡ K e i n Element f ü r das ? . (SIMPLE-MATCH " * haben * W e r t " " V i e l e S y m b o l e haben e i n e n k o m p l e x e n W e r t " )

= = > #T

T) T)

194

II. Konstruktionen (Analyse und Synthese)

eval >

eval>

(SIMPLE-MATCH "? P h a s e n p l a n b e s t i m m t * V o r g e h e n " "Der l i n e a r e P h a s e n p l a n b e s t i m m t das V o r g e h e n " ) = = > () ;Mehr als ein E l e m e n t für das ?. (SIMPLE-MATCH "* P h a s e n p l a n b e s t i m m t * V o r g e h e n " "Der l i n e a r e P h a s e n p l a n b e s t i m m t das V o r g e h e n " ) = = >

#T

7.3 Symbol und PRINT-Name Ein Symbol (Literalatom) ist eine „Einheit" mit einer eigenen Identität (vgl. Abschnitt 1.4.2). Seine Elementarteilchen sind abhängig vom jeweiligen LISPSystem. So haben Common LISP-Systeme im Gegensatz zu Scheme-Systemen das Elementarteilchen FUNCTION-cell (vgl. Bild 7.3-1). Im Mittelpunkt dieses Abschnittes steht das Elementarteilchen PRINT-Name.

pSYMBOL PRINT-name V A L U E - eel 1 FUNCTION-cel1 P- LI ST PACKAGE-cel1 Legende: D a r g e s t e l l t s i n d d i e E l e m e n t a r t e i l c h e n bei e i n e m C o m m o n L I S P - S y s t e m . Das S c h e m e - K o n z e p t v e r z i c h t e t b e w u ß t auf d i e F U N C T I O N - c e l l und g e w i n n t d a d u r c h an S t r i n g e n z . PRINT-name VALUE-cel 1 FUNCTION-cell P-LIST PACKAGE-cel1

3ild 7 . 3 - 1

I d e n t i f i z i e r u n g s - E l e m e n t des S y m b o l s S p e i c h e r für (den Z e i g e r auf) den Symbol wert S p e i c h e r für (den Z e i g e r a u f ) den F u n k tionswert eines Symbols S p e i c h e r für (den Z e i g e r a u f ) d i e E i g e n s c h a f t s l i s t e ( P r o p e r t y LIST, vgl. Abschnitt 5.3) S p e i c h e r für (den Z e i g e r auf) d i e Z u o r d n u n g zu e i n e r e i g e n e n S y m b o l s a m m l u n g (z.B. S y s t e m , B e n u t z e r , E d i t o r , e t c . )

Elementarteilchen

eines

Symbols

(Literalatoms)

7. Abbildungsoption: Zeichenkette und Symbol

195

Wird ein Symbol eingelesen, dann konvertiert ein LISP-System in der READPhase des READ-EVAL-PRINT-Zyklus (vgl. Abschnitt 1.2) alle kleinen Buchstaben in große Buchstaben. So wird z.B. aus FoO oder fOo jeweils FOO. Dies ist ein Kennzeichen für das Alter von LISP. Als LISP Ende der 50er Jahre konzipiert wurde, konnten die Terminals (z.B. einfache Kartenleser und Drucker) nur mit Großbuchstaben umgehen. Als Terminals mit Kleinbuchstaben auf den Markt kamen, wurden Betriebssysteme und Programmiersprachen zunächst nur leicht modifiziert. Sie setzten die Kleinbuchstaben in Großbuchstaben um und konnten damit die Verarbeitung wie bisher durchführen. Heute ermöglichen viele LISP-Systeme das Ausschalten dieser Konvertierung. Als ein Beispiel von vielen sei Allegro Common LISP genannt (vgl. Anhang B). Die externe Repräsentation eines Symbols ergibt sich aus der Zeichenfolge im Slot PRINT-name. Dort wollen wir möglichst alle beliebigen Zeichen ablegen können z.B. auch Sonderzeichen wie Leerzeichen, öffendene oder schließende Klammern. Dazu müssen wir verhindern, daß solche Sonderzeichen in der READ-Phase entsprechend ihrer üblichen Aufgabe interpretiert werden. Damit solche Zeichen als Teil des Symbolnamens behandelt werden, sind sie durch ein Fluchtsymbol zu kennzeichnen. Die Zeichen \ (ASCII 124) und \ (ASCII 92) dienen in Scheme als Fluchtsymbole. Das Zeichen ! wird mehrfaches Fluchtsymbol genannt. Das Zeichen \ ist eine einfaches Fluchtsymbol. Beim mehrfachen Fluchtsymbol werden vom ersten Zeichen ! bis zum nächsten Zeichen I alle Zeichen vom READ-Prozeß als Symbolname interpretiert (vgl. Abschnitt 11.1). eval> (OEFINE !(! 5) ==> !(! eval> (OEFINE ¡ s o z i a l e Marktwirtschaft! ' \ ( ) ==>¡soziale Marktwirtschaft, eval> ¡ s o z i a l e Marktwirtschaft! ==> !(! eval> (EVAL i s o z i a l e M a r k t w i r t s c h a f t ! ) ==> 5 Beispiel: Mustervergleich

mit Variablen fiir Listen

Wir betrachten erneut das SIMPLE-MATCH-Konstrukt (vgl. Abschnitt 7.1) für Listen und erweitern dieses um sogenannte Mustervariablen (engl.: match variables). Das erweiterte Konstrukt nennen wir MUSTERVERGLEICH. Wir vereinbaren, daß Symbole mit einem Fragezeichen oder einem Stern zu Beginn ihres PRINT-Namens Mustervariablen sind. Solche Mustervariablen decken Elemente im Prüfling ab. Dabei wirken sie wie die Symbole ? bzw. *. Paßt das Muster, dann sind die abgedeckten Werte durch die Mustervariablen verfügbar. Wir binden unter der Eigenschaft MATCH-VALUE den von der Mustervariablen abgedeckten Wert an ein namensähnliches Symbol, dessen PRINT-Name nicht das Kennzeichen ? bzw. * aufweist. Da der Stern für mehrere Symbole im Prüfling steht, fassen wir unter der Eigenschaft MATCH-VALUE alle abgedeckten Elemente zu einer Liste zusammen. Das folgende Beispiel hat die beiden Mustervariablen ?WER und *WAS.

II. Konstruktionen (Analyse und Synthese)

196 eval> eval> eval>

( M U S T E R V E R G L E I C H ' ( ? W E R BEGRUENDEN * W A S ) ' ( R E G E L N BEGRUENDEN E I N E N S T I L DER P R O G R A M M I E R U N G ) = = > #T ( G E T P R O P 'WER ' M A T C H - V A L U E ) = = > REGELN (GETPROP 'WAS ' M A T C H - V A L U E ) = = > ( E I N E N S T I L DER P R O G R A M M I E R U N G )

Wie das Beispiel verdeutlicht, ist aus dem Namen einer Mustervariablen der Name des zugehörenden Symbols für die Wertebindung zu selektieren. So ist aus der Mustervariablen ?WER der Teil WER und aus der Mustervariablen *WAS der Teil WAS zu ermitteln. Dazu verwenden wir das in PC Scheme eingebaute EXPLODE-Konstrukt. Es hat als Wert eine Liste von Einzeichensymbolen, die den PRINT-Namen seines Arguments repräsentieren. Das EXPLODEKonstrukt ist in PC Scheme auf Symbole, Zeichenketten und ganze Zahlen anwendbar. Das Gegenstück zum EXPLODE-Konstrukt ist das eingebaute IMPLODE-Konstrukt. Es verlangt eine Liste und baut daraus den PRINT-Namen des Symbols. Die folgenden Beispiele zeigen die Umsetzung der PRINT-Repräsentation in die Elemente einer Liste bzw. ihre Zusammenfassung zu einem Symbol. eval > (EXPLODE eval > (EXPLODE ==> (?

' ?WER ) "?Wer" W le!

==>

(?

W E R)

K l e i n e B u c h s t a b e n werden durch e i n e n unterbrochenen senkrechten S t r i c h umgeben. Das F l u c h s y m b o l verhindert ihre Konvertierung in Großbuchstaben .

eval>

(SYMBOL?

(CADDR

eval>

(IMPLODE

(EXPLODE

'?WER))

eval>

(IMPLODE

(EXPLODE

"?Wer"))

eval>

(IMPLODE

(CDR

eval>

(DEFINE

eval>

(SYMBOL?

eval>

(EQ?

eval>

(EXPLODE

-1234)

eval>

(NUMBER?

(IMPLODE

(CDR

(EXPLODE

-1234))))

==>

()

e v a " '>

(SYMBOL?

(IMPLODE

(CDR

(EXPLODE

-1234))))

==>

#T

F00

(EXPLODE

(IMPLODE

F00)

' |Wer!

(EXPLODE

==>

F00)

"?Wer"))) ==> ==>

'?WER)))

(CDR

==>

#T

?WER !?Wer| ==>

(EXPLODE

WER "?Wer"))))

==>

F00

#T ==>

==>

#T (-

¡1]

|2|

13]

¡4])

Das Konstrukt MUSTERVERGLEICH arbeitet sowohl mit Mustervariablen als auch mit den einfachen Deckelementen Fragezeichen und Stern. Es bietet sich daher an, in einem ersten Schritt die Unterschiede in der Notation im Muster in eine einheitliche Notation zu überführen. Dazu definieren wir ein Konstrukt ANALYSE-LITERAL-ATOM. Eine Mustervariable bzw. ein einfaches Deckelement werden in eine zweielementige Liste transformiert. Ihr erstes Element (ein Fragezeichen bzw. ein Stern) gibt den Typ an. Im Fall der Mustervariablen ist

7. Abbildungsoption: Zeichenkette und Symbol

197

das zweite Element das Symbol, dessen Eigenschaft MATCH-VALUE den abgedeckten Wert aufnimmt. Liegt keine Mustervariable vor, sondern nur ein Fragezeichen bzw. ein Stern, dann ist das zweite Element NIL. Eine solche Transformation bietet sich auch als Eingabe an, wenn das verwendete LISP-System keine EXPLODE- und IMPLODE-Konstrukte enthält. eval> (ANALYSE-LITERAL-ATOM ' ( ? PROGRAMM * BEDINGT *X UND *Y)) ==> ((? NIL) PROGRAMM (* NIL) BEDINGT (* X) UND (* Y)) Mit der Annahme, daß das Eingabe-Muster eine Liste ist, deren Elemente alle Symbole sind, können wir das Transformations-Konstrukt, wie folgt, definieren: eval> (DEFINE ANALYSE-LITERAL-ATOM (LAMBDA (LISTE) (COND ((NULL? LISTE) NIL) ((SYMBOL? (CAR LISTE)) (LET* ((ALLE-ZEICHEN (EXPLODE (CAR LISTE))) (MATCH-TYP (CAR ALLE-ZEICHEN)) (MATCH-VARIABLE (COND ((NULL? (CDR ALLE-ZEICHEN)) NIL) (T (IMPLODE (CDR ALLE-ZEICHEN)))))) (COND ((EQ? '? MATCH-TYP) (CONS (LIST '? MATCH-VARIABLE) (ANALYSE-LITERAL-ATOM (CDR LISTE)))) ((EQ? '* MATCH-TYP) (CONS (LIST '* MATCH-VARIABLE) (ANALYSE-LITERAL-ATOM (CDR LISTE)))) (T (CONS (CAR LISTE) (ANALYSE-LITERAL-ATOM (CDR LISTE))))))) (T (ERROR "Fuer diesen Elementtyp nicht implementiert: " (CAR LISTE)))))) ==> ANALYSE-LITERAL-ATOM Auf der Basis dieser Eingabe-Transformation sind Prädikate zu definieren, die erkennen, ob ein Deckelement bzw. eine bestimmte Mustervariable vorliegt. Wir definieren z.B. für den Stern bzw. für die Mustervariable, deren PRINTName mit einem Stern beginnt, folgende Prädikate: eva1> (DEFINE *-SYMB0L? (LAMBDA (L) (AND (PAIR? L) (EQ? '* (CAR L)) (NULL? (LIST-REF L 1 ) ) ) ) ) ==> *-SYMB0L? eval> (DEFINE *-VARIABLE? (LAMBDA (L) (AND (PAIR? L) (EQ? '* (CAR L)) (LIST-REF L 1 ) ) ) ) ==> ^-VARIABLE?

II. Konstruktionen (Analyse und Synthese)

198

Wir benötigen Selektoren und Mutatoren für den Wert einer Mustervariablen. Wir verwenden hier die Eigenschaftsliste des Symbols (vgl. Abschnitt 5.3), um abgedeckte Elemente zu speichern. Die Entscheidung für die Eigenschaftsliste verhindert einerseits das Überschreiben bestehender Symbol-Wert-Bindungen und dient andererseits zum Training der P-Listen-Konstrukte. Die Selektoren und den Mutator definieren wir, wie folgt: eval> eval>

eval>

(DEFINE GET-MATCH-VARIABLE (LAMBDA ( L ) ( L I S T - R E F L 1 ) ) ) ( D E F I N E GET-MATCH-VALUE (LAMBDA (SYMBOL) (GETPROP

==>

SYMBOL

GET-MATCH-VARIABLE 'MATCH - V A L U E ) ) ) ==> GET-MATCH-VALUE

(DEFINE SET-MATCH-VALUE! ( L A M B D A ( S Y M B O L WERT) ( P U T P R O P SYMBOL WERT ' M A T C H - V A L U E ) T ) ) ==> SET-MATCH-VALUE!

Die Erweiterung um Mustervariablen bedingt einige Anpassungen am obigen SIMPLE-MATCH-Konstrukt (vgl. Abschnitt 7.1). Zur Erläuterung sind die betroffenen Änderungsstellen in der folgenden Wiederholung des SIMPLEMATCH-Konstruktes markiert. eval>

(DEFINE (COND

S I M P L E - M A T C H (LAMBDA (MUSTER L I S T E ) ((AND (NULL? MUSTER) (NULL? L I S T E ) ) T) ( ( A N D ( N U L L ? L I S T E ) ( N U L L ? (CDR M U S T E R ) ) ( E Q ? ' * (CAR M U S T E R ) ) ) T) :Ergänzung A *-VARIABLE? als letztes M u s t e r e l e m e n t , dann Wertbindung NIL v o l l z i e h e n . ((OR (NULL? MUSTER) (NULL? L I S T E ) ) N I L ) ( ( O R ( E Q ? (CAR MUSTER) (CAR L I S T E ) ) ( E Q ? ' ? (CAR M U S T E R ) ) ) ( S I M P L E - M A T C H (CDR MUSTER) (CDR L I S T E ) ) ) ;Ergänzung B ; ?-VARIABLE? auswerten ( ( E Q ? ' * (CAR MUSTER)) (COND ( ( S I M P L E - M A T C H

(T

(CDR MUSTER) (CDR L I S T E ) ) T) ((SIMPLE-MATCH MUSTER (CDR L I S T E ) ) T ) ((SIMPLE-MATCH (CDR MUSTER) L I S T E ) T) (T N I L ) ) ) ;Ergänzung C ; *-VARIABLE? auswerten N I L ) ) ) ) ==> SIMPLE-MATCH

Die Ergänzung A betrifft den Fall, daß der Prüfling vollständig abgearbeitet ist, d.h. die LAMBDA-Variable L I S T E ist an NIL gebunden, aber das Muster enthält noch Elemente. Dann ist festzustellen, ob das Muster nur noch ein Element enthält und dieses entweder das Prädikat * - S Y M B O L ? oder das Prädikat *-VA-

7. Abbildungsoption: Zeichenkette und Symbol

199

RIABLE? erfüllt. Ist * - S Y M B O L ? bzw. *-VARIABLE? erfüllt, dann paßt das Muster, so daß wir T zurückgeben können und im Fall *-VARIABLE? noch das zugehörende Symbol an NIL binden. Wir unterstellen damit, daß unser Muster auch dann paßt, wenn der Stern für eine leere Folge von Elementen im Prüfling steht. Die Ergänzung B betrifft den Fall, daß im Muster das Prädikat ?-VARIABLE? erfüllt ist. Hierzu ist eine entsprechende Klausel einzufügen. Wenn der Rest des Musters mit dem Rest des Prüflings paßt, ist das zugehörende Symbol an den Wert zu binden. Wir können diese Klausel, wie folgt, definieren: ((AND

( ? - V A R I A B L E ? (CAR MUSTER)) (MATCH (CDR MUSTER) (CDR L I S T E ) ) ) (SET-MATCH-VALUE! (GET-MATCH-VARIABLE (CAR L I S T E ) ) T)

(CAR

MUSTER))

Die Ergänzung C betrifft den Fall, daß im Muster das Prädikat »-VARIABLE? erfüllt ist. Im SIMPLE-MATCH ist quasi der Fall formuliert, der * - S Y M B O L ? abdeckt. Statt (EQ? ' * (CAR MUSTER)) ist ( * - S Y M B O L ? (CAR MUSTER)) zu setzen. Die einzelnen Klauseln dieses *-SYMBOL?-Falles sind hier für den Fall »-VARIABLE? ebenfalls zu formulieren, allerdings sind dann die Wertbindungen zu ergänzen. Wir können diese Ergänzung, wie folgt, definieren: ( ( * - V A R I A B L E ? (CAR MUSTER)) (COND ( ; ; VARIABLE? deckt ein Element! (MATCH (CDR MUSTER) (CDR L I S T E ) ) ( S E T - M A T C H - V A L U E ! (GET-MATCH - V A R I A B L E (CAR MUSTER)) ( L I S T (CAR L I S T E ) ) ) ) ; ; * - V A R I A B L E ? deckt mehrere Elemente! ; ; Der b i s h e r i g e Wert i s t zu e r g ä n z e n . ((MATCH MUSTER (CDR L I S T E ) ) ( S E T - M A T C H - V A L U E ! ( G E T - M A T C H - V A R I A B L E (CAR MUSTER)) (CONS (CAR L I S T E ) (GET-MATCH-VALUE (GET-MATCH - VARIABLE (CAR M U S T E R ) ) ) ) ) T) ; ; * - V A R I A B L E ? deckt kein Element! ((MATCH (CDR MUSTER) L I S T E ) T ) ; ; Muster paßt n i c h t . (T N I L ) ) )

Dieses erweiterte MATCH-Konstrukt und seine Hilfsfunktionen wie Prädikate, Selektoren, Mutatoren sowie das ANALYSE-LITERAL-ATOM sind als lokale Funktionen im Konstrukt MUSTERVERGLEICH definiert (vgl. Programm 7.3-1).

200

;;;; ;;;; ;;;;

II. Konstruktionen (Analyse und Synthese)

Dokument 7 . 3 - 1 T i t e l : Programm MUSTERVERGLEICH E r s t e l l t am: 3 1 . 1 2 . 8 9 ¡ l e t z t e Ä n d e r u n g :

;;; ;;; ;:; ;;; ;;: ;;; ;;; ::; ;;; ;;; ;;; ;;; ;;; ;;; ;;;

AI

;;; ;;; ;;; ;;; ;;; :;;

El

;;; ;;; ;;; ;::

T1

;;;

A2 A2.1 A2.2 A2.3

A2.4

A2.5

El.1 El.2 El.3 El.3

8.11.90

MUSTERVERGLEICH a n a l y s i e r t zwei L i s t e n , d i e L i s t e PATTERN und d i e L i s t e OBJEKT, mit dem Z i e l Werte f ü r M A T C H - V a r i a b l e zu b e s t i m m e n . PATTERN kann a u s f o l g e n d e n S y m b o l e n b e s t e h e n : ? : : = D e c k t e i n L i s t e n e l e m e n t im OBJEKT. * : : = D e c k t e i n e F o l g e von L i s t e n e l e m e n t e n im OBJEKT. ? : : = D e c k t e i n L i s t e n e l e m e n t im OBJEKT und b i n d e t d i e s e s L i s t e n e l e m e n t u n t e r der E i g e n s c h a f t MATCH-VALUE an d a s Symbol . * : : = D e c k t e i n e F o l g e von L i s t e n e l e m e n t e n im OBJEKT und b i n d e t d i e s e F o l g e a l s L i s t e u n t e r der E i g e n s c h a f t MATCH-VALUE an d a s Symbol . < s y m b o l > : : = D e c k t das g l e i c h e < s y m b o l > im OBJEKT. K o n v e r t i e r u n g der M a t c h v a r i a b l e n 2 Elementen, wie f o l g t : ? ==> (? NIL) * ==> ( * NIL) ? = = > ( ? ) * = = > ( * )

in eine

Liste

mit

Bei s p i e l e v a l > (MUSTERVERGLEICH ' ( * X HAT ? WARTBARE ? Y ) ' ( D E R ENTWURF HAT E I N E WARTBARE STRUKTUR) eval> eval>

(GETPROP (GETPROP

'X 'Y

'MATCH-VALUE) 'MATCH-VALUE)

==> ==>

(DER ENTWURF) STRUKTUR

—>

#T

( D E F I N E MUSTERVERGLEICH (LAMBDA (PATTERN OBJECT) (LETREC ( ;; K o n v e r t i e r u n g der M a t c h v a r i a b l e n i n e i n e ;; z w e i e l e m e n t i g e L i s t e ( A N A L Y S E - L I T E R A L - A T O M (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) N I L ) ( ( S Y M B O L ? (CAR L I S T E ) ) ( L E T * ( ( A L L E - Z E I C H E N (EXPLODE (CAR L I S T E ) ) ) (MATCH-TYP (CAR A L L E - Z E I C H E N ) ) (MATCH-VARIABLE (COND ( ( N U L L ? (CDR A L L E - Z E I C H E N ) ) N I L ) (T ( I M P L O D E (CDR A L L E - Z E I C H E N ) ) ) ) ) ) (COND ( ( E Q ? ' ? MATCH-TYP) (CONS ( L I S T ' ? M A T C H - V A R I A B L E ) (ANALYSE-LITERAL-ATOM (CDR L I S T E ) ) ) ) ( ( E Q ? ' * MATCH-TYP) (CONS ( L I S T ' * M A T C H - V A R I A B L E ) (ANALYSE-LITERAL-ATOM (CDR L I S T E ) ) ) ) (T (CONS (CAR L I S T E ) (ANALYSE-LITERAL-ATOM (CDR L I S T E ) ) ) ) ) ) ) (T (ERROR "Fuer diesen Elementtyp nicht implementiert: " (CAR L I S T E ) ) ) ) ) )

7. Abbildungsoption: Zeichenkette und Symbol ; ; ; ; Prädi kate ( ? - S Y M B O L ? (LAMBDA ( X ) (AND ( P A I R ? X ) ( E Q ? ' ? (CAR X ) ) (NULL? ( L I S T - R E F X ( ? - V A R I A B L E ? (LAMBDA ( X ) (AND ( P A I R ? X ) ( E Q ? ' ?

1))))) (CAR

( * - SYMBOL? (LAMBDA ( X ) (AND ( P A I R ? X ) (EQ? ' * (CAR X ) ) (NULL? ( L I S T - R E F X ( * - V A R I A B L E ? (LAMBDA ( X ) (AND ( P A I R ? X ) ( E Q ? ' * ; ;;;

Seiektoren ( G E T - M A T C H - V A R I AB LE

X))

(LIST-REF

X

1))))

(LIST-REF

X

1))))

1))))) (CAR

(LAMBDA

(X)

X))

(LIST-REF

X

1)))

( G E T - M A T C H - V A L U E (LAMBDA (SYMBOL) ( G E T P R O P SYMBOL ' M A T C H - V A L U E ) ) ) ; ;;;

Mutator ( S E T - M A T C H - V A L U E ! ( L A M B D A ( S Y M B O L WERT) ( P U T P R O P SYMBOL WERT ' M A T C H - V A L U E ) T))

;;;;

MATCH-Konstrukt (MATCH ( L A M B D A ( M U S T E R L I S T E ) (COND ( ( A N D ( N U L L ? M U S T E R ) ( N U L L ? L I S T E ) ) T ) ((NULL? LISTE) (COND ( ( A N D ( * - S Y M B O L ? ( C A R M U S T E R ) ) ( N U L L ? (CDR M U S T E R ) ) ) T) ( ( A N D ( * - V A R I A B L E ? (CAR M U S T E R ) ) ( N U L L ? (CDR M U S T E R ) ) ) (SET-MATCH-VALUE! ( G E T - M A T C H - V A R I A B L E (CAR M U S T E R ) ) NIL) T) (T N I L ) ) ) ((OR (EQUAL? (CAR MUSTER) (CAR L I S T E ) ) ( ? - SYMBO L ? ( C A R M U S T E R ) ) ) (MATCH ( C D R M U S T E R ) ( C D R L I S T E ) ) ) ((AND

( ? - V A R I A B L E ? (CAR MUSTER)) (MATCH ( C D R M U S T E R ) ( C D R L I S T E ) ) ) (SET-MATCH-VALUE! ( G E T - M A T C H - V A R I AB LE ( C A R M U S T E R ) ) (CAR L I S T E ) ) T)

( ( * - SYMBOL? (CAR M U S T E R ) ) (COND ( ( M A T C H ( C D R M U S T E R ) ( C D R L I S T E ) ) ((MATCH MUSTER (CDR L I S T E ) ) T) ((MATCH (CDR MUSTER) L I S T E ) T) (T N I L ) ) )

T)

202

II. Konstruktionen (Analyse und Synthese) ( ( * - V A R I A B L E ? (CAR M U S T E R ) ) (COND ((MATCH (CDR MUSTER) (CDR L I S T E ) ) (SET-MATCH-VALUE! ( G E T - M A T C H - V A R I AB LE (CAR M U S T E R ) ) ( L I S T (CAR L I S T E ) ) ) ) ((MATCH MUSTER (CDR L I S T E ) ) (SET-MATCH-VALUE! ( G E T - M A T C H - V A R I AB LE (CAR M U S T E R ) ) (CONS (CAR L I S T E ) (GET-MATCH-VALUE (GET-MATCH-VARIABLE (CAR M U S T E R ) ) ) ) ) T) ((MATCH (CDR MUSTER) L I S T E ) T) (T N I L ) ) ) (T

NIL)))))

(MATCH ( A N A L Y S E - L I T E R A L - A T O M

PATTERN)

OBJECT))))

Programm 7 . 3 - 1 Mustervergleich mit Mustervariablen (Abb i 1 d u n g s b a s i s : L i s t e n )

7.4 Generieren eines Symbols Wird der Name eines Symbol eingelesen, dann ist zu prüfen, ob dieses Symbol schon existiert oder nicht. Da wir laufend mit Symbolen umgehen, ist so effizient wie möglich zu prüfen, ob es sich um ein vorhandenes oder neues, zu erzeugendes Symbol handelt. Z.B. benötigt das DEFINE-Konstrukt das Prüfergebnis, um zu entscheiden, ob nur der Wert eines existierenden Symbols zu ändern oder zunächst ein entsprechendes Symbol zu erzeugen und dann in der Umgebung mit seinem Wert einzutragen ist. eval> eval> eval>

FOO = = > ERROR . . . ( D E F I N E FOO 6) = = > FOO ( D E F I N E FOO ' ( A B = = > FOO

;In

der

Umgebung n i c h t

definiert

;FOO g e n e r i e r t und Wert z u g e w i e s e n , C)) ;FOO neuen Wert z u g e w i e s e n .

Diese Prüfung nimmt üblicherweise Bezug auf eine Symboltabelle. Dabei werden neue Symbole erzeugt und in die Tabelle eingetragen. Diesen Vorgang nennt man auch „Internmachen". Über das Einlesen des Namens des Symbols können daher nur intern gemachte Symbole wieder erreicht werden. Erzeugt man ein Symbol, ohne es „intern zu machen", d.h. ohne Eintragung in die Symboltabelle, dann ist es später durch die READ-Phase nicht erreichbar. Obwohl ein „nicht interngemachtes" Symbol über das Einlesen seines Namens nicht referenzierbar ist, kann es sehr nützlich sein. Es ist keinesfalls mit Symbolen zu verwechseln, die überhaupt nicht mehr erreichbar sind und als Platzver-

7. Abbildungsoption: Zeichenkette und Symbol

203

schwendung (Müll) zu betrachten sind. Erreichbar ist ein solches „nicht internes" Symbol (engl.: uninterned symbol) z.B. wenn es als Wert eines internen Symbols dient. Ein nicht „interngemachtes" Symbol generiert das GENSYM-Konstrukt. Ein von GENSYM erzeugtes Symbol hat keine Gleichheit (im Sinne von EQ?) mit existierenden oder neu eingelesenen Symbolen. Geht es daher um die Vermeidung von Überschreibungen mit bestehenden oder noch entstehenden Symbolen, dann ist das GENSYM-Konstrukt angebracht. eval>

(GENSYM

{})

— >

mit:

::= < s t r i n g >



(DEFINE

F00

eval>

F00 = = >

GO

eval> eval>

(SYMBOL? (EQ? 'GO

eval>

(EQ?

eval>

(DEFINE

BAR

eval>

BAR ==>

Gl

eval>

(DEFINE

FOO

F00

eval > FOO = >

Cnonnegative-integer>

::= D i e Z e i c h e n k e t t e w i r d a l s P r ä f i x d e s Symbol namens v e r w e n d e t . ::= D i e Zahl s e t z t d e n Z ä h l e r . Das n ä c h s t e g e n e r i e r t e S y m b o l h a t d i e s e Zahl als l a u f e n d e Nummer. ::= Ein n e u e s n i c h t i n t e r n g e m a c h t e s S y m b o l . Ist k e i n < s t r i n g > a n g e g e b e n beginnt sein P R I N T - N a m e mit G gef o l g t von einer l a u f e n d e n Nummer. Jede GENSYM-Applikation erhöht die laufende Nummer.

eval>

|

(GENSYM))

F00) ==> F00) = = >

#T ()

F00) = = >

#T

==>

F00

¡Achtung! Keine Gleichheit ; gleichem PRINT-Namen

(GENSYM))

==>

BAR

(GENSYM))

==>

FOO

trotz

G2

eval>

(EVAL

(LIST

eval>

(EVAL

FOO) = = >

'DEFINE

FOO

13)) = = >

G2

13

Wollen wir ein nicht „interngemachtes" Symbol mit vorgegebenen Namen generieren, dann ist das in PC Scheme eingebaute STRING->UNINTERNEDSYMBOL-Konstrukt zu verwenden. eval> eval>

(DEFINE FOO = = >

FOO ( S T R I N G - > U N I N T E R N E D - S Y M B O L |M 0 R E!

"M 0 R E " ) ) = = >

FOO

204 eval> (EQ? F00

II. Konstruktionen (Analyse und Synthese) 1

IM 0 R E!) ==> ()

Im Vergleich dazu, zeigt das folgende Beispiel den Effekt des "Internmachens": eval> (DEFINE F00 (STRING->SYMBOL "M 0 R E")) ==> F00 eval> F00 ==> ¡ M O R E ! eval> (EQ? F00 '!M 0 R E!) ==> #T Beispiel: Pseudo-Code- Vorübersetzer In Kapitel I ist für die Alternative und die Iteration eine Notation angegeben, die häufig in den ersten Phasen des Lebenszyklus eines Software-Produktes (vgl. Bild 2-1) eingesetzt wird. Man spricht in diesem Zusammenhang von einer Pseudocode-Notation, weil die formulierten Konstrukte prinzipiell noch nicht „lauffähig" sind. Sie enthalten Formulierungen, die später noch zu präzisieren sind. Wir fassen hier jedoch die Pseudo-Code-Notation nur als eine syntaktische Erleichterung auf und unterstellen eine „lauffähige" Notation. Wir definieren mit Hilfe des Konstruktes MUSTERVERGLEICH einen Pseudocode-Vorübersetzer. Unser P-CODE-Konstrukt kann folgende PseudocodeNotationen in LISP-Konstrukte übersetzen: o Alternative (vgl. Abschnitt 2.2.2) (IF . . . THEN . . . ELSE . . . FI) (IF . . . THEN . . . FI) o Iteration (vgl. Abschnitt 2.2.3) (WHILE . . . DO . . . OD) (DO . . . UNTIL . . . OD) Diese Pseudocode-Angaben betrachten wir als eine Musterbeschreibung. Wir formulieren z.B. für die Alternative das Muster (IF ?P THEN *JA ELSE *NEIN). Paßt das Muster, dann sind die Mustervariablen an die „aktuellen" Werte gebunden. Unser P-CODE-Konstrukt braucht nur die JA- und NEIN-Werte an die entsprechenden Stellen des geeigneten LISP-Konstruktes zu setzen. Da eine Mustervariable mit einem Stern die abgedeckten Elemente zu einer Liste zusammenfaßt, befindet sich das erste abgedeckte Element in dieser Liste in funktionaler Stellung. Es würde als Operator interpretiert. Dies verhindern wir durch das Einfügen des BEGIN-Konstruktes in die JA- bzw. NEIN-Liste. eval> (P-CODE ' ( I F P THEN A B C ELSE D E FI)) ==> (IF P (BEGIN A B C )

(BEGIN D E F))

Für die beiden Iterationen formulieren wir die beiden Muster (WHILE ?P DO *BODY OD) und (DO *BODY UNTIL ?Q OD). Mit den Werten der Mustervariablen ?P bzw. ?Q und *BODY konstruieren wir eine rekursive LISP-Funk-

7. Abbildungsoption: Zeichenkette und Symbol

205

tion. Diese Funktion nutzt das LETREC-Konstrukt zur Definition einer lokalen, rekursiven Funktion. Benötigt wird ein Funktionsnamen (Symbol) für den Selbstbezug. Jedes Symbol, das wir dafür rein zufällig wählen, kann zu ungewollten Nebeneffekten führen. Im folgenden Beispiel haben wir unterstellt, wir hätten das Symbol AAA als Funktionsnamen gewählt. Tritt in der Pseudo-Code-Formulierung zufällig auch das Symbol AAA auf, dann kommt es zu Fehlern. Der erste Fall hat keine Namensgleichheit mit dem Symbol AAA; der zweite hat eine solche und führt zu einem ungewollten Effekt. Im 3. Fall verwenden wir ein Symbol, das mit dem Konstrukt GENSYM generiert wurde, und verhindern damit einen solchen Nebeneffekt. Fall 1: Keine zufällige Namensgleichheit mit der Funktion AAA eval> (DEFINE I 0) ==> I eval> (DEFINE J 'DM) ==> J eval> (DEFINE F00 (P-CODE '(DO (WRITELN I " " J) (SET! I (1+ I ) ) UNTIL (> I 3) OD))) ==> F00 eval> F00 ==>(LETREC ((AAA (LAMBDA () (WRITELN I " " J) (SET! I (1+ I ) ) (COND ((> I 3) ( ) ) (T (AAA)))))) (AAA)) eval> (EVAL F00) ==> 0 DM 1 DM 2 DM 3 DM () Fall 2: eval> eval> eval>

Zufällige Namensgleichheit mit der Funktion AAA (DEFINE I 0) ==> I (DEFINE AAA 'DM) ==> AAA ¡Namensgleiche g l o b a l e Variable (DEFINE F00 (P-CODE '(DO (WRITELN I " " AAA) (SET! I (1+ I ) ) UNTIL (> I 3) OD))) ==> F00 eval> F00 ==>(LETREC ((AAA (LAMBDA () (WRITELN I " " AAA) (SET! I (1+ I ) ) (COND ((> I 3) ( ) ) (T (AAA)))))) (AAA))

206 eval> (EVAL F00) ==> 0 # 1 # 2 # 3 # ()

II. Konstruktionen (Analyse und Synthese)

¡Achtung! Ungewollter Nebeneffekt! ; Gewünschte Lösung vgl. Fall 1

Im P-CODE-Konstrukt (vgl. Programm 7.4-1) ist das Symbol für die lokale, rekursive Funktion mit Hilfe des GENSYM-Konstruktes konstruiert. Selbst in dem ungünstigsten Fall, d.h. der PRINT-Name dieses generierten Symbols ist gleich mit dem PRINT-Namen eines eingelesen Symbols, besteht keine Referenz zu diesem anderen Symbol. Der Fall 3 zeigt eine solche, schwer durchschaubare Situation. Zu einem ungewollten Nebeneffekt kommt es jedoch nicht. Fall 3: Symbol für die Funktion mit GENSYM generiert eval> (DEFINE I 0) ==> I eval> (DEFINE G6 'DM) ==> G6 ¡ Z u f ä l l i g e PRINT-Namensgi e i c h h e i t ; mit dem anschließend g e n e r i e r t e n ; Symbol eval> (DEFINE F00 (P-CODE '(DO (WRITELN I " " G6) (SET! I (1+ I ) ) UNTIL (> I 3) OD))) ==> F00 eval> F00 ==>(LETREC ((G6 (LAMBDA () ¡Generiertes Symbol (WRITELN I " " G6) ¡Eingegebenes Symbol (SET! I (1+ I ) ) (COND ((> I 3) ( ) ) (T ( G 6 ) ) ) ) ) ) ¡Generiertes Symbol (G6)) ; Generiertes Symbol eval> (EVAL F00) ==> 0 DM 1 DM 2 DM 3 DM () Im Fall der Iteration setzt das P-CODE-Konstrukt seinen Rückgabewert (= LETREC-Konstrukt) mit Hilfe der wiederholten Anwendung des LIST-Konstruktes zusammen. Die geschachtelten LIST-Konstrukte sind nicht sehr übersichtlich (vgl. Programm 7.3-1). Mit Hilfe der Makros QUASIQUOTE, UNQUOTE und UNQUOTE-SPLICING wäre eine syntaktisch einfachere Notation möglich. Mit Makros befassen wir uns eingehend im Abschnitt 8.3.

7. Abbildungsoption: Zeichenkette und Symbol

Dokument 7.4-1 Titel: Programm P-CODE Erstellt: 31.12.89 ¡letzte Aenderung: AI A2 A2.1 A2.2 A3 A3.1 A3.2 T1

(DEFINE (COND

207

8.11.90

P - C O D E ü b e r f ü h r t s o g e n a n n t e P s e u d o c o d e - N o t a t i o n e n in ablauffähige LISP-Konstrukte. Implemtiert sind die A l t e r n a t i v e (vgl. A b s c h n i t t 2.2.2) (IF ... T H E N ... E L S E ... FI) (IF ... T H E N ... FI) und die Iteration (vgl. A b s c h n i t t 2.2.3) ( W H I L E ... DO ... O D ) (DO ... U N T I L ... O D )

Beispiel eval> (P-CODE ' ( W H I L E ( (LETREC ((Gl ( L A M B D A () ( C O N D ( ( < = I 5) ( P R I N T I) (T N I L ) ) ) ) ) (Gl))

I)

(SET!

(SET!

I (1+

P-CODE (LAMBDA (SEXPR) ( ( M U S T E R V E R G L E I C H '(IF ?X T H E N *Y E L S E *Z (LET ( ( P R A E D I K A T ( G E T P R O P 'X ' M A T C H - V A L U E ) ) (BODY - L I S T E - 1 ( G E T P R O P 'Y ' M A T C H - V A L U E ) ) (BODY-LISTE-2 ( G E T P R O P 'Z ' M A T C H - V A L U E ) ) ) ( L I S T 'IF PRAEDIKAT ( C O N S ' B E G I N BODY - L I S T E - 1 ) (CONS 'BEGIN B O D Y - L I S T E - 2 ) ) ) ) ( ( M U S T E R V E R G L E I C H '(IF ?X T H E N *Y FI) (LET ( ( P R A E D I K A T ( G E T P R O P 'X ' M A T C H - V A L U E ) ) (BODY-LISTE ( G E T P R O P 'Y ' M A T C H - V A L U E ) ) ) ( L I S T 'IF PRAEDIKAT (CONS 'BEGIN B O D Y - L I S T E ) NIL)))

I (1+

FI)

SEXPR)

I))

I))

OD))

(Gl))

SEXPR)

208

II. Konstruktionen (Analyse und Synthese) ( ( M U S T E R V E R G L E I C H ' ( W H I L E ?X DO * Y OD) S E X P R ) (LET ((SYMBOL (GENSYM) ) (PRAEDIKAT (GETPROP ' X ' M A T C H - V A L U E ) ) (BODY-LISTE (GETPROP ' Y ' M A T C H - V A L U E ) ) ) ( L I S T 'LETREC ( L I S T ( L I S T SYMBOL ( L I S T 'LAMBDA N I L ( L I S T 'COND (APPEND (CONS P R A E D I K A T BODY-LISTE) (LIST (LIST SYMBOL))) '(T NIL))))) (LIST SYMBOL)))) ( ( M U S T E R V E R G L E I C H ' ( D O *Y U N T I L ?X OD) S E X P R ) (LET ((SYMBOL (GENSYM)) (PRAEDIKAT (GETPROP ' X ' M A T C H - V A L U E ) ) (BODY-LISTE (GETPROP ' Y 'MATCH-VALUE))) ( L I S T 'LETREC ( L I S T ( L I S T SYMBOL (APPEND (CONS ' L A M B D A (CONS N I L B O D Y - L I S T E ) ) ( L I S T ( L I S T 'COND ( L I S T PRAEDIKAT N I L ) (LIST 'T (LIST SYMBOL))))))) (LIST SYMBOL)))) (T

(ERROR "Keine P-Code-Transformation SEXPR)))))

Programm 7 . 3 - 1 .

definiert

fuer:

"

P-CODE-Vorübersetzer

7.5 Zusammenfassung: EXPLODE, IMPLODE und GENSYM Die Zeichenkette (engl.: string) kann mit einer Vielzahl spezieller Konstrukte, z.B. MAKE-STRING, S U B S T R I N G oder STRING-REF, bearbeitet werden. In Bezug zur Liste tritt an die Stelle des Prädikates NULL? das eingebaute Prädikat STRING-NULL?. Das Prädikat STRING=? entspricht der EQUAL?-Prüfung. Ein Symbol, auch Literalatom genannt, ist eine „Einheit" mit eigener Identität. Der PRINT-Name ist eines der Elementarteilchen der „Einheit" Symbol. Er kann durch Nutzung von Fluchtzeichen (z.B in PC Scheme die Zeichen ! und \) auch mit Sonderzeichen konstruiert werden. Mit Hilfe der Konstrukte E X PLODE und IMPLODE ist der PRINT-Name als Informationsträger manipulierbar. Ein nicht „interngemachtes" Symbol (engl.: uninterned symbol) ist über den

7. Abbildungsoption: Zeichenkette und Symbol

209

PRINT-Namen nicht referenzierbar. Ein neues, nicht „interngemachtes" Symbol generiert das GENSYM-Konstrukt. Eine GENSYM-Applikation ist zweckmäßig, wenn ein Namenskonflikt mit existierenden oder später generierten Symbolen zu verhindern ist. Im Gegensatz zu klassischen LISP-Systemen hat das Symbol in Scheme keine Funktionszelle als besonderes Elementarteilchen. Damit werden Ungereimtheiten zwischen funktionalen und nichtfunktionalen Werten vermieden. Mit dem GENSYM-Konstrukt ist auch in Scheme eine besondere Funktionszelle mit Hilfe der Eigenschaftsliste realisierbar. eval> (DEFINE GET-F) ==> GET-F eval> (DEFINE SET-F1) ==> SET-F! eva1> (LET ((EIGENSCHAFT (GENSYM))) (SET! GET-F (LAMBDA (SYMBOL) (GETPROP SYMBOL EIGENSCHAFT))) (SET! SET-F! (LAMBDA (SYMBOL FUNKTION) (PUTPROP SYMBOL FUNKTION EIGENSCHAFT))) T) ==> #T eval> (SET-F! 'F00 (LAMBDA (X Y) (LIST X Y))) ==> # eval> (DEFINE F00 1) ==> 1 eval> ((GET-F 'F00) 'A 'B) ==> (A B) Charakteristische Beispiele für Abschnitt 7: ;;;; P a s c a l s c h e s Dreick a u s g e b e n . eva1> (DEFINE ¡ P a s c a l s c h e s D r e i e c k ! (LAMBDA (N_ENDZEILE) (LETREC ((PASCAL (LAMBDA (GRUNDSEITE I_ZEILE) (COND ( ( = N_ENDZEILE I_ZEILE) (AUSGABEGRUNDSEITE)) (T (AUSGABE GRUNDSEITE) (PASCAL (MAKE-NEUE-DREI ECK-SEITE GRUNDSEITE) (1+ I _ Z E I L E ) ) ) ) ) ) (MAKE-NEUE-DREIECK-SEITE (LAMBDA (L) (COND ( ( = 1 (LENGTH L)) (LIST 1 1 ) ) (T(APPENDC LIST 1) (APPEND (SUMME L) (LIST 1 ) ) ) ) ) ) )

210

II. Konstruktionen (Analyse und Synthese)

(SUMME (LAMBDA (COND

(L_ZAHLEN) ((=

(LENGTH

(LIST

(+

L_ZAHLEN)

(CAR

2)

L.ZAHLEN) (CADR

(T

L_ZAHLEN))))

(APPEND (LIST

(+

(CAR

L_ZAHLEN) (CADR

(SUMME

(CDR

(LAENGE-AUSGABE-ZEILE

L_ZAHLEN ) ) )

L_ZAHLEN)))))))

70)

(ANZAHL-ZIFFERN-GRUNDSEITE (LAMBDA (COND

(L) ((NULL?

L)

0) ;;

(T

(+

(LENGTH

Auf

Zahlen

(EXPLODE

(CAR

angewendet! L)))

(ANZAHL-ZIFFERN-GRUNDSEITE (CDR

L)))))))

(ANZAHL-SPACE-GRUNDSEITE (LAMBDA (-1+

(L) (LENGTH

L))))

(EINRUECKEN-AUSGABE-ZEILE (LAMBDA

(L)

(MAKE-STRING (FLOOR (/

(-

LAENGE-AUSGABE-ZEILE (+

(ANZAHL-ZIFFERN-GRUNDSEITE (ANZAHL-SPACE-GRUNDSEITE

L) L)))

2)) #\SPACE))) (AUSGABE (LAMBDA

(L)

(WRITELN (PASCAL

(LIST

(EINRUECKEN-AUSGABE-ZEILE

L)

1)

Dreieck!

0))))

==>

¡Pascalsches

L))))

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

eval>

(I P a s c a l s c h e s

Dreieck!

12)

211

==> (1) (1

(1

1)

2

1)

( 1 3 3 1) ( 1 4 6 4 1) ( 1 5 10 10 5 1 ) ( 1 6 15 20 15 6 1 ) (1 7 21 35 35 21 7 1) ( 1 8 2 8 56 7 0 5 6 2 8 8 1 ) (1 9 36 84 126 126 84 36 9 1) ( 1 10 4 5 1 2 0 2 1 0 2 5 2 2 1 0 1 2 0 4 5 10 1 ) ( 1 1 1 5 5 1 6 5 3 3 0 4 6 2 4 6 2 3 3 0 1 6 5 5 5 11 1 ) ( 1 1 2 6 6 2 2 0 4 9 5 7 9 2 9 2 4 7 9 2 4 9 5 2 2 0 6 6 12 1 ) ;;;;

Vergleich

eval>

(DEFINE

zweier ¡X

(LAMBDA (LET (DO

PRINT-Namen

ASCII

(CAR

Y-SYM))))

(SET!

WERT

T)

(SET!

WERT

NIL))))))

==>

(IX

(IX

C!X

#T

eval >

(GENSYM)

eval >

(IX

==> '60

==>

|X

eval >

#T

8. Abbildungsoption: Funktion mit zugeordneter Umgebung Der klassische LISP-Kern konzipiert von McCarthy, das sogenannte "Pure LISP", ist eine applikative Sprache, da die Applikation (Anwendung) von Funk-

II. Konstruktionen (Analyse und Synthese)

212

tionen das wesentliche Konstruktionsmittel ist. Dieses „Pure L I S P " ist gleichzeitig eine funktionale Sprache, da die Abstraktion auf der Komposition von Funktionen (= evaluierten LAMBDA-Konstrukten) beruht. Diese Schachtelung ermöglicht das Kombinieren primitiver Konstrukte zu komplexen anwendungsspezifischen Lösungen. "Pure L I S P " ist der Stammvater vieler Sprachen für das funktionale Programmieren (vgl. z.B. Henderson, 1980; Darlington u.a., 1982; Henson, 1987 oder Eisenbach, 1987). Zu nennen sind z.B. FP (vgl. Backus, 1978), HOPE (genannt nach „Hope Park Square", Adresse von Edinburgh's Department of Computer Science) oder M L (vgl. Gordon u.a., 1984). Leitidee der funktionalen Programmierung ist der Funktionswert, der nur durch Werte der Argumente bestimmt wird. Sooft wir auch eine Funktion mit den gleichen Argumenten aufrufen, stets hat sie den gleichen Wert. Diese Eigenschaft (engl.: referential transparency) erleichtert wesentlich das Durchschauen einer Konstruktion. Beschränken wir uns auf Konstrukte mit dieser Eigenschaft, dann ist eine Funktion problemlos durch eine effizientere Funktion, die den gleichen Wert bei gleichen Argumenten hat, austauschbar. Diesen Vorteil gibt es nicht kostenlos. Solche nebeneffektfreien Konstrukte erstellen neue Objekte statt modifizierte Versionen von ihren Argumenten. Die Neukonstruktion hat im Regelfall einen höheren Rechenzeit- und Speicherbedarf. Stets ist die Funktion im Zusammenhang mit ihrer Umgebung zu sehen. Erst diese Verknüpfung zum sogenannten CLOSURE-Konzept meistert Funktionen höherer Ordnung. Die folgende Definition, in TLC-LISP formuliert, zeigt explizit die Verknüpfung der Umgebung (ENVironemt) mit dem LAMBDA-Konstrukt durch das explizit notierte CLOSURE-Konstrukt. T-eval>

( D E ADON ( N ) ;; " A b s c h l i e ß u n g " f ü r das f o l g e n d e LAMBDA-Konstrukt (CLOSURE ;; H i e r i s t N e i n e f r e i e V a r i a b l e (LAMBDA (M) (ADD N M ) ) ; ; Umgebung s p e i c h e r t V a r i a b l e N (ENV ' N N ) ) ) = = > ADDN

T-eval>

(SETQ

T-eval>

P L U S 3 (ADDN 3 ) ) = = > (CLOSURE ( P L U S 3 7 ) = = > 10

(LAMBDA

(M)

(ADD

N M))

(ENV

'N

3))

Die Bindungsstrategie bestimmt, zu welchem Zeitpunkt die Variablen ihren Wert erhalten. Die jeweilige Implementation bestimmt, auf welche Art und Weise der Wert einer Variablen gespeichert und "nachgeschaut" wird. In Abschnitt 1.4.2 haben wir die „dynamische" und die „lexikalische" Bindungsstrategie erörtert. Die beiden wesentlichen Implementationskonzepte werden als „deep access" und „shallow access" gekennzeichnet. Die Implementation „deep

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

213

access" hat im Vergleich zu „shallow access" den Vorteil ohne großen Aufwand den Wechsel der aktuellen Umgebung vollziehen zu können. „Shallow access" hat demgegenüber den Vorteil auf den Wert der Variablen schnell zugreifen zu können. Im folgenden werden diese Implementationskonzepte nicht weiter diskutiert (Näheres dazu vgl. z.B. Allen, 1978). Wir unterstellen die lexikalische Bindungsstrategie und behandeln damit die Funktion und ihre Umgebung als Option, lokale Werte abbilden zu können. Zunächst vertiefen wir Funktionen höherer Ordnung (Abschnitt 8.1). Funktionen höherer Ordnung können Funktionen als Werte von LAMBDAVariablen haben. Dabei zeigen wir, daß es sinnvoll sein kann, sogar die Funktion selbst an ihre LAMBDA-Variable zu binden. Mit dem allgemeinen Kontrollstruktur-Konstrukt CALL-WITH-CURRENT-CONTINUATION läßt sich an die LAMBDA-Variable die Fortsetzung der Verarbeitung, d.h. die RestKonstruktion, binden. Das Continuation-Konzept von Scheme ermöglicht damit eine beliebige Fortsetzungen einer begonnen Abarbeitung. Es gestattet quasi Sprünge in ihre „Zukunft" und ihre „Vergangenheit". Wir erläutern diese allgemeine Kontrollstruktur und behandeln den Unterschied zu einem einem einfachen „GOTO"-Sprung (Abschnitt 8.2). Eine hierarchische Ordnung von Umgebungen ermöglicht das Arbeiten mit abgerenzten Paketen. Als Beispiel dient erneut die Kontenverwaltung (Abschnitt 8.4). Dabei werden Makros als syntaktische Verbesserungen genutzt. Wir befassen uns daher vorab mit der Definition von Makros (Abschnitt 8.3). Funktionsgeprägte Konstruktionen können (müssen aber nicht) im Vergleich zu imperativgeprägten weniger effizient sein. Eine Möglichkeit der Effizienzverbesserung ist das Compilieren. Wir skizzieren kurz die Möglichkeiten in PC Scheme mit einer mehrfach rekursiven Funktion (Abschnitt 8.5).

8.1 Funktionen höherer Ordnung In diesem Abschnitt vertiefen wir das Verständnis für Funktionen, die Funktionen als Argumente und/oder als Wert haben. Das Konstruieren mit solchen Funktionen höherer Ordnung (engl.: higher-order function) wird anhand von drei Beispielen erörtert. Das erste Beispiel bildet den Listen-Konstruktor CONS als Funktion ab (Abschnitt 8.1.1). Das zweite Beispiel definiert Y, den Fixpunktoperator für Funktionen. Die Y-Definition hat hier die Aufgabe, das Verständnis für rekursive Funktionen zu vertiefen. Sie ermöglicht die Definition rekursiver Funktionen ohne Verwendung des DEFINE- oder LETREC-Konstruktes (Abschnitt 8.1.2). Im dritten Beispiel konstruieren wir eine Funktion ALTERNATE. Sie bearbeitet die Teilaufgaben von zwei Prozessen abwechselnd (Abschnitt 8.1.3).

214

II. Konstruktionen (Analyse und Synthese)

8.1.1 Funktionale Abbildung der CONS-Zelle Die funktionale CONS-Abbildung nennen wir F-CONS, um eine Namensgleichheit und infolgedessen das Überschreiben des eingebauten CONS-Konstruktes zu vermeiden. Zu F-CONS definieren wir die Selektoren F-CAR , F-CDR, die Prädikate F-PAIR?, F-NULL? und die Mutatoren F-SET-CAR! und F-SET-CDR!. Die Konstruktionsbasis („Mutter Erde") einer Liste ist NIL, die leere Liste. Analog dazu ist eine funktionale Anfangsgröße, ein funktionales NIL, zu definieren. Wir nennen es *F-NIL*. Die Sternchen im Namen dienen dazu, eine versehentliche Namensgleichheit beim Programmieren zu erschweren, weil wir nur wichtigen globalen Variablen einen Namen mit Sternchen geben (vgl. Abschnitt 11.1). eva1>

(DEFINE * F - N I L *

(LAMBDA

())

= = > ERROR

... ;Kein

Funktionskörper

Das LAMBDA-Konstrukt erfordert zumindest einen symbolischen Ausdruck als Funktionskörper. Um Konflikte mit anderen symbolischen Ausdrücken zu vermeiden, könnte man das GENSYM-Konstrukt (vgl. Abschnitt 7.4) verwenden. Wir nutzen hier einfach das Symbol *F-NIL* selbst. eval>

(DEFINE * F - N I L *

(LAMBDA

()

*F-NIL*))

==>

*F-NIL*

/Exkurs: Funktionale Konstante. Die Funktion (LAMBDA (X) *F-NIL*) wäre kein zweckmäßiges Beispiel für einen konstanten Rückgabewert; denn ihre Applikation kann unter Umständen zum Fehler führen: e v a l > ((LAMBDA

(X)

*F-NIL*)

(/ 1 0 ) )

==>

ERROR... ;Divison

durch Null

]

Die Aufgabe eines Konstruktors ist das Zusammenfügen von Teilen. Beim CONS-Konstrukt wird der CAR-Teil mit dem CDR-Teil verknüpft. Diese Verknüpfung ist abbildbar, indem wir die beiden Teile als Werte in der Umgebung einer Funktion speichern. eval>

(DEFINE

F-CONS (LAMBDA

(CAR_TEIL CDR_TEIL)

...

) ==>

F-CONS

Beim Applizieren des Funktionskörpers von F-CONS, d.h. innerhalb des LAMBDA-Konstruktes, sind die LAMBDA-Variablen CAR_ TEIL und CDR_ TEIL an Werte gebunden. Diese Bindungen sind in der Funktions-Umgebung als Assoziationen gespeichert. Mit Hilfe des THE-ENVIRONMENT-Konstruktes ist diese Umgebung als Rückgabewert definierbar. Das ACCESS-Konstrukt ermöglicht die Selektion der Werte in der angegebenen Umgebung (vgl. Abschnitt 1.4.2).

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

215

eval> (DEFINE F-CONS (LAMBDA (CAFLTEIL CDR_TEIL) (THE-ENVIRONMENT))) ==> F-CONS eval> F-CONS ==> # eval> (ACCESS CAR_TEIL (F-CONS 'A 'B)) ==> A eval> (ACCESS C D R_ T EIL (F-CONS 'A 'B)) ==> B Statt die Umgebung explizit als Rückgabewert zu definieren, verändern wir das F-CONS-Konstrukt so, daß sein Wert eine LISP-Funktion ist. eval> (DEFINE F-CONS (LAMBDA (CAR-TEIL CDR_TEIL) (LAMBDA (OPERATION) . . . eval> (F-CONS 'A 'B) ==> #

))) ==> F-CONS

Diese (Rückgabe-)Funktion wurde in einer Umgebung erzeugt, in der CAR TEIL an den Wert A und CDR TEIL an den Wert B gebunden sind. Erzeugt heißt: Aus der Liste mit dem Symbol LAMBDA in Präfixnotation ist durch Evaluieren eine LISP-Funktion, nach außen durch # angezeigt, entstanden. Wird diese (Rückgabe-)Funktion angewendet, dann haben ihre freien Variablen, hier CAR_ TEIL und CDR_ TEIL die Werte der Definitionsumgebung, weil in Scheme als Standardfall die lexikalische Bindungsstrategie implementiert ist. Sie legt fest, daß die Definitionsumgebung einer LISP-Funktion maßgeblich für die Bindung von freien Variablen ist (vgl. Abschnitt 1.4.2). Wir definieren den Konstruktor F-CONS, wie folgt: eval> (DEFINE F-CONS (LAMBDA (C A R_ T EIL C D R_ T EIL) (LAMBDA (OPERATION) (COND ((EQ? OPERATION 'F-PAIR?) (LAMBDA () T)) ((EQ? OPERATION 'F-CAR) (LAMBDA () CAR_TEIL)) ((EQ? OPERATION 'F-CDR) (LAMBDA () C D R_ T EIL)) ((EQ? OPERATION 'F-SET-CAR!) (LAMBDA (X) (SET! CAR_TEIL X))) ((EQ? OPERATION 'F - S ET -CDR!) (LAMBDA (X) (SET! CDR_TEIL X))) (T (ERROR "Unbekannte Operation: " OPERATION)))))) ==> F-CONS Die Selektoren CAR und CDR erfordern eine Liste bzw. ein Punkt-Paar als Argument. Diese Voraussetzung ist mit dem Prädikat PAIR? prüfbar. Für die Selektoren F-CAR und F-CDR definieren wir ein entsprechendes Prädikat F-PAIR?. eval> (DEFINE F-PAIR? (LAMBDA (OBJEKT) (COND ((AND (PROCEDURE? OBJEKT) ((OBJEKT 'F-PAIR?))) T) (T (ERROR "Keine f u n k t i o n a l e CONS-Zelle: " OBJEKT))))) ==> F-PAIR? In LISP-Implementationen können die Selektoren CAR und CDR auf die leere Liste angewendet werden, obwohl ihre Werte dann prinzipiell nicht definiert

216

II. Konstruktionen (Analyse und Synthese)

sind. Der Rückgabewert ist in PC Scheme NIL. Ob NIL vorliegt, ist mit dem NULL?-Konstrukt prüfbar. eval> eval>

(NULL? (NULL?

(CDR (CAR ()))) ==> #T (CAR (CDR ()))) ==> #T

Entsprechend definieren wir ein F-NULL?-Konstrukt. Ist die „leere Liste" gegeben, hat F-NULL? den Wert *F-NIL*. Dieser Wert ist ungleich NIL und wird daher vom LISP-System als „wahr" interpretiert. Liegt nicht *F-NIL* vor, dann ist der Wert von F-NULL? gleich NIL als Repräsentation von „nicht wahr". *F-NIL* übernimmt nicht die Mehrfachfunktion von NIL (Wahrheitswert-Repräsentation, „bottom-element" einer Liste). *F-NIL* dient nur als „Urbaustein" für den funktionalen Konstruktor F-CONS. eval>

(DEFINE F-NULL? (LAMBDA (OBJEKT) (COND ((AND (PROCEDURE? OBJEKT) (T NIL)))) ==>

eval>

eval>

eval>

(EQ? OBJEKT

F-NULL?

(DEFINE F-CAR (LAMBDA (OBJEKT) (COND ( F-NULL? OBJEKT) OBJEKT) ( F - P A I R ? OBJEKT) ((OBJEKT ( NIL)))) ==> F-CAR (DEFINE F CDR (LAMBDA (OBJEKT) (COND ( F-NULL? OBJEKT) OBJEKT) ( F-PAIR? OBJEKT) ((OBJEKT ( NIL)))) ==> F-CDR

*F-NIL*)) *F-NIL*)

'F-CAR)))

'F-CDR)))

(DEFINE F SET-CAR! (LAMBDA (OBJEKT WERT) (COND ( F-NULL? OBJEKT) (ERROR "Objekt ist leer! " OBJEKT) F-PAIR? OBJEKT) ((OBJEKT 'F-SET-CAR!) WERT)) NIL)))) ==> F - S E T - C A R !

èva 1> (DEFINE F SET-CDR! (LAMBDA (OBJEKT WERT) (COND ( F-NULL? OBJEKT) (ERROR "Objekt ist leer! " OBJEKT) ( F-PAIR? OBJEKT) ((OBJECT 'F-SET-CDR!) WERT)) (T NIL)))) ==> F - S E T - C D R !

Das folgende F-CONS-Beispiel zeigt den (gewollten) Nebeneffekt des Mutators F-SET-CAR!. Seine Wirkung entspricht der von SET-CAR!. eval> eval> eval> eval> eva1> eval> eval> eva1>

(DEFINE F00 (F-CONS 'A *F-NIL*)) ==> F00 (DEFINE F00 (F-CONS 'B F00)) ==> F00 (DEFINE BAZ F00) ==> BAZ (F-CAR BAZ) ==> B (F-CDR BAZ) ==> # < P R O C E D U R E F-C0NS> (F-CAR (F-CDR (F-CDR BAZ))) ==> # < P R O C E D U R E *F-NIL*> (F-SET-CAR! BAZ (F-CONS 1 *F-NIL*)) ;BAZ-Modifikation ==> # < P R O C E D U R E F-C0NS> (F-CAR F00) ==> # < P R O C E D U R E F-C0NS>

8. Abbildungsoption: Funktion mit zugeordneter Umgebung eval>

(F-CAR

(F-CAR

F00))

==>

1

eval> eval>

(F-CAR (F-CAR

(F-CDR (F-CDR

F00)) ==> A (F-CDR F 0 0 ) ) )

217

; N e b e n e f f e k t ! F00 ; auch v e r ä n d e r t ==> #

Ausgangspunkt einer (noch stärker) funktionsgeprägten Abbildung der CONSZelle können Funktionen für die Wahrheitswert „true" und "false" sein. eval> eval>

(DEFINE (DEFINE

TRUE ( L A M B D A (W F) W ) ) = = > TRUE F A L S E ( L A M B D A (W F) F ) ) = = > F A L S E

Das F-CONS-Konstrukt ist wieder eine Funktion höherer Ordnung. Ihr Rückgabewert ist wieder eine Funktion, in deren Funktionskörper die CAR- und CDRWerte als Argumente dienen. eval>

(DEFINE F-CONS ( L A M B D A (X Y ) (LAMBDA ( C ) (C X Y ) ) ) )

==>

F-CONS

Mit Hilfe der TRUE- und FALSE-Funktionen können die Selektoren als Funktionen, wie folgt, definiert werden: eval>

(DEFINE

F-CAR

(LAMBDA

(X)

eval>

(DEFINE

F-CDR

(LAMBDA

(X)

(X T R U E ) ) ) ==> F-CAR (X F A L S E ) ) ) ==> F-CDR

;TRUE-Wert=Funktion!! ;FALSE-Wert=Funktion!!

Der Wert einer F-CONS-Applikation ist eine Funktion, die wiederum von den Selektoren appliziert wird. eval> eval> eval>

( F - C D R (F-CONS 2 (F-CONS 1 0 ) ) ) = = > # (F-CAR (F-CONS 2 (F-CONS 1 0 ) ) ) ==> 2 (F-CDR (F-CDR (F-CONS 1 0 ) ) ) = = > ERROR . . . ¡ V e r s u c h , e i n n i c h t f u n k t i o n a l e s O b j e k t ; a p p l i z i e r e n : (0 # )

zu

Zum besseren Verstehen eines Selektionsvorganges, „reduzieren" wir schrittweise die LAMBDA-Konstrukte, d.h. wir setzen die Werte ihrer Argumente im Funktionskörper an die Stelle der entsprechenden LAMBDA-Variablen. Die einzelnen Reduktionsschritte trennen wir mit dem Zeichen „=". eval>

(F-CDR (F-CONS 1 0 ) ) ( F - C D R ( ( L A M B D A (X Y ) ( L A M B D A ( C ) (C X Y ) ) ) 1 ( F - C D R ( L A M B D A ( C ) (C 1 0 ) ) ) ( ( L A M B D A ( X ) (X F A L S E ) ) ( L A M B D A ( C ) (C 1 0 ) ) ) ( ( L A M B D A ( C ) (C 1 0 ) ) F A L S E ) (FALSE 1 0) ( ( L A M B D A (W F ) F) 1 0 ) ==>0

0))

II. Konstruktionen (Analyse und Synthese)

218

8.1.2 Fixpunktoperator Y Wir betrachten jetzt Funktionen, die sich selbst als Argument haben. Der sogenannte Fixpunktoperator Y ist eine solche Funktion. Zu seiner Erläuterung nehmen wir als Beispiel an, daß die Summe der numerischen Elemente einer Liste zu berechnen ist. Die Beispielliste nennen wir ZAHLUNGEN. eval>

( D E F I N E ZAHLUNGEN

'(10.00

"DUBIOS"

5 . 0 0 55.00 "DUBIOS" 2 . 0 0 8 . 0 0 ) ) ==> ZAHLUNGEN

Mit dem Iterationskonstrukt FOR-EACH und einer Hilfsvariablen AKKUMULATOR ist die Summe einfach ermittelbar: eval>

(LET

((AKKUMULATOR 0 ) ) (FOR-EACH (LAMBDA ( X ) ( I F (NUMBER? X) ( S E T ! AKKUMULATOR ( + AKKUMULATOR X ) ) ) ) ZAHLUNGEN) AKKUMULATOR) ==> 8 0 .

An Stelle des iterativen FOR-EACH-Konstruktes mit dem eingebetteten Modifikator S E T ! („Zuweisungsoperator") definieren wir eine rekursive Funktion. eval>

eval>

( D E F I N E SUMME (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) 0 ) ((NUMBER? (CAR L I S T E ) ) ( + (CAR L I S T E ) (SUMME (CDR L I S T E ) ) ) ) ( T (SUMME (CDR L I S T E ) ) ) ) ) ) ==> SUMME (SUMME ZAHLUNGEN) ==> 8 0 .

Zur Vermeidung einer globalen Funktion ist S U M M E jetzt als lokale Funktion innerhalb eines LETREC-Konstruktes definiert. eval>

(LETREC

((SUMME (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) 0 ) ((NUMBER? (CAR L I S T E ) ) ( + (CAR L I S T E ) (SUMME (CDR ( T (SUMME (CDR L I S T E ) ) ) ) ) ) ) (SUMME ZAHLUNGEN)) ==> 8 0 .

LISTE))))

Unser Ziel ist eine rekursive Lösung ohne die Nutzung des LETREC-Konstruktes. Gesucht ist ein Mechanismus, der rekursive Lösungen konstruieren kann, die nur auf dem LAMBDA-Konstrukt bzw. dem LET-Konstrukt (als dessen syntaktische Vereinfachung) basieren, und kein SET!-Konstrukt verwenden. Hier wollen wir daher nicht das LETREC-Konstrukt entsprechend Abschnitt 2.2.1 diskutieren, da dieses auf dem SET!-Konstrukt aufbaut. Ein solcher Mechanismus ist der Fixpunktoperator Y (engl.: the applicative-

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

219

order fixed point operator for functionals). Der Fixpunktoperator Y erfüllt die folgende Bedingung: ((F (Y F)) x) - ((Y F) x) Anders formuliert: ((F f ) x) = (f x) . Dabei ist f der Fixpunkt von F, d.h. F „überführt" f in f . Für unser Probfem ist das F-Konstrukt, wie nach den weiteren Ausführungen deutlich wird, wie folgt, definiert: eval>

(DEFINE F (LAMBDA (SUMME) (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) 0) ((NUMBER? (CAR L I S T E ) ) ( + ( C A R L I S T E ) (SUMME ( C D R L I S T E ) ) ) ) ( T (SUMME ( C D R L I S T E ) ) ) ) ) ) ==> F

Der Fixpunktoperator Y ist eine Funktion, die eine Funktion nimmt, die als rekursive Funktion betrachtbar ist und eine andere Funktion zurückgibt, welche die rekursive Funktion implementiert. Die Befassung mit dem Fixpunktoperator Y zwingt zum funktionalen Denken, wie der vorherstehende Satz offensichtlich demonstriert. Zum Verstehen des Fixpunktoperators entwicklen wir schrittweise eine Y-Definition (vgl. Gabriel, 1988). Dazu nutzen wir die drei Überlegungen: o Einführung einer zusätzlichen LAMBDA-Variablen zur Vermeidung von rekursiven Scheme-Konstrukten, wie LETREC oder DEFINE. o Konvertierung einer Funktion mit mehreren LAMBDA-Variablen in geschachtelte Funktionen mit einer LAMBDA-Variablen (Curryfizierung). Ziel ist, die Manipulation der (neuen) LAMBDA-Variablen für den Selbstbezug von der Manipulation der bisherigen LAMBDA-Variablen zu trennen. o Definition gleichwertiger Funktionen durch Analyse eines Konstruktes (Abstraktion). Zum (Wieder-)Einfinden in die funktionale Denkwelt betrachten wir zunächst die folgende einfache Funktion FOO. Sie hat eine Funktion als Rückgabewert. eval>

(DEFINE

FOO

e v a l > ( ( F O O 2) 3) Wert von N : = 2 Wert von M : = 3 6

(LAMBDA ( N ) ( L A M B D A (M) (WRITELN "Wert (WRITELN "Wert ( * N M ) ) ) ) ==> ==>

von N : = von M : = FOO

") ")

Wenden wir uns nun der Definition der Funktion SUMME zu und nennen sie für die folgende Erörterung vorübergehend :

220

II. Konstruktionen (Analyse und Synthese) (LET

((

(LAMBDA (COND

(

(LISTE) ((NULL? L I S T E ) 0) ( ( N U M B E R ? (CAR L I S T E ) ) ( + (CAR L I S T E ) (SUMME ( C D R L I S T E ) ) ) ) (T (SUMME (CDR L I S T E ) ) ) ) ) ) ) ZAHLUNGEN))

Im Sinne der rekursiven Problemlösungsmethode, bei der angenommen wird, daß eine Teillösung (quasi) existiert, unterstellen wir auch hier, die Funktion S U M M E wäre verfügbar (woher auch immer). Wenn die Funktion S U M M E schon (gedanklich) existiert, dann könnte sie allerdings auch über die LAMBDA-Schnittstelle dem Funktionskörper von übergeben werden. Die Funktion hätte dann zwei LAMBDA-Variablen: FUNKTION für die zu übergebende Funktion S U M M E und LISTE, die bisherige Variable. Wenn wir im Funktionskörper die übergebene Funktion aufrufen, also SUMME, dann ist zu beachten, daß diese jetzt zwei Argumente benötigt. eval>

(LET

((SUMME

(SUMME

(LAMBDA (FUNKTION L I S T E ) (COND ( ( N U L L ? L I S T E ) 0 ) ( ( N U M B E R ? (CAR L I S T E ) ) ( + (CAR L I S T E ) (FUNKTION FUNKTION (T ( F U N K T I O N F U N K T I O N SUMME Z A H L U N G E N ) ) = = > 8 0 .

(CDR (CDR

LISTE)))) LISTE)))))))

Zunächst bindet das LET-Konstrukt in seiner Umgebung das Symbol S U M M E an eine Funktion mit den beiden LAMBDA-Variablen FUNKTION und LISTE. Diese Funktion hat keinen Selbstbezug zum Symbol SUMME. In ihrem Funktionskörper wird die LAMBDA-Variable FUNKTION einerseits appliziert und andererseits als Argument genutzt. Die Besonderheit ist das Zusammenfallen der Punkte "applizierte Funktion" und „Argument". Wird die LAMBDA-Variable FUNKTION durch die Notation als erstes Element einer Liste als Funktion appliziert, dann ist sie selbst ihr erstes Argument. Die Technik, sich selbst als Argument zu nutzen, ergibt den rekursiven Bezug. Etwas leger formuliert: Wir „überlisten" das LET-Konstrukt, indem wir die Rekursivität „durch die Hintertür" als Parameterübergabe realisieren. Die zusätzliche LAMBDA-Variable für die Selbstübergabe wollen wir von den anderen LAMBDA-Variablen trennen, mit dem Ziel, den SelbstübergabeMechanismus isoliert betrachten zu können. Für diese Trennung nutzen wir das sogenannte Curryfizieren von Funktionen. H. Curry hat nachgewiesen, daß zu jeder Funktion höherer Ordnung F eine Funktion F existiert, für die gilt (vgl. Curry u.a., 1958): (F X}

x2

x3

...

xn)

=

(...(((Fc

x^)

x2)

x3)

...

xn)

221

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

Das folgende Beispiel zeigt das Curryfizieren (engl.: currying) anhand von drei LAMBDA-Variablen: eval> eval>

(DEFINE (F00 'A

eval>

(DEFINE CURRYFIZIERTE-F00 (LAMBDA ( X ) (LAMBDA ( Y ) (LAMBDA ( Z ) (LIST X Y Z ) ) ) ) ) ==> CURRYFIZIERTE-F00 ( ( ( C U R R Y F I Z I E R T E - F 0 0 ' A ) ' B ) ' C ) = = > (A B C )

eval>

F 0 0 ( L A M B D A (X Y Z ) ' B ' C ) = = > (A B C)

(LIST

X Y Z)))

==>

F00

Bezogen auf die Definition von SUMME erhalten wir: eval>

(LET

((SUMME (LAMBDA ( F U N K T I O N ) (LAMBDA ( L I S T E ) (COND ( ( N U L L ? L I S T E ) 0 ) ( { N U M B E R ? (CAR L I S T E ) ) ( + (CAR L I S T E ) ( ( F U N K T I O N FUNKTION) (CDR (T ( ( F U N K T I O N F U N K T I O N ) ( C D R ( ( S U M M E SUMME) Z A H L U N G E N ) ) = = > 8 0 .

LISTE)))) LISTE))))))))

Das Curryfizieren hat eine Hierarchie von zwei LAMBDA-Konstrukten bewirkt und die LAMBDA-Variable FUNKTION auf die obere Ebene gehoben. Die Selbstübergabe (FUNKTION FUNKTION) ist jedoch noch tief in dem Konstrukt (LAMBDA ( L I S T E ) . . . ) enthalten. Der nächste Schritt gilt dem Herauslösen von (FUNKTION FUNKTION). Dazu definieren wir eine lokale Funktion SUM mit den beiden LAMBDA-Variablen: FKT und LST. Wir übergeben SUM unseren Selbstbezug (FUNKTION FUNKTION) als erstes Argument. eval>

(LET

((SUMME (LAMBDA (FUNKTION) (LAMBDA ( L I S T E ) ( L E T ((SUM (LAMBDA (FKT L S T ) (COND ( ( N U L L ? L S T ) 0 ) ( ( N U M B E R ? (CAR L S T ) ) ( + (CAR L S T ) (FKT (CDR (T ( F K T ( C D R L S T ) ) ) ) ) ) ) (SUM ( F U N K T I O N FUNKTION) LISTE)))))) ( ( S U M M E SUMME) Z A H L U N G E N ) ) = = > 8 0 .

LST))))

Da die lokale Funktion SUM hat zwei LAMBDA-Variablen (FKT und LST) hat, Curryfizieren wir SUM. Dabei ist zu beachten, daß auch der SUM-Aufruf anzupassen ist.

222 eval>

II. Konstruktionen (Analyse und Synthese) (LET

((SUMME (LAMBDA ( F U N K T I O N ) (LAMBDA ( L I S T E ) ( L E T ((SUM (LAMBDA ( F K T ) (LAMBDA ( L S T ) (COND ( ( N U L L ? L S T ) 0) ((NUMBER? (CAR L S T ) ) (+ (CAR L S T ) ( F K T (CDR L S T ) ) ) ) (T ( F K T (CDR L S T ) ) ) ) ) ) ) ) ((SUM (FUNKTION F U N K T I O N ) ) LISTE)))))) ( ( S U M M E SUMME) Z A H L U N G E N ) ) ==> 8 0 .

Analysiert man die bisher erreichte Lösung, so zeigt sich, daß es keinen Grund gibt, die lokale Funktion SUM auf einer so tiefen Ebene zu definieren. Wir können sie z.B. als eine globale Funktion SUM definieren, so daß sie der obigen F-Definition entspricht. Unsere Lösung besteht dann aus der SUM-Definition und dem Rest des LET-Konstruktes: eval>

(DEFINE

SUM ( L A M B D A ( F K T ) (LAMBDA ( L S T ) (COND ( ( N U L L ? L S T ) 0) ((NUMBER? (CAR L S T ) ) (+ (CAR L S T ) ( F K T (CDR L S T ) ) ) ) ( T ( F K T ( C D R L S T ) ) ) ) ) ) ) ==> SUM ((SUMME (LAMBDA ( F U N K T I O N ) (LAMBDA ( L I S T E ) ((SUM (FUNKTION F U N K T I O N ) ) LISTE))))) ( ( S U M M E SUMME) Z A H L U N G E N ) ) ==> 8 0 .

eval>

(LET

Die Analyse dieser Lösung ergibt: Für eine allgemeine Anwendung ist es ungünstig, daß in der lokalen Funktion SUMME die Funktion SUM explizit aufgerufen wird. Um diesen Mangel zu beheben, bauen wir die Definition von SUMME in eine Funktion YO ein, deren LAMBDA-Variable FUN dann an SUM gebunden wird, so daß FUN appliziert werden kann. Der explizite SUMAufruf wird durch einen FUN-Aufruf ersetzt. Bei der YO-Definition sorgen wir dafür, daß ihr Rückgabewert (SUMME SUMME) ist. Die Lösung hat dann folgende Struktur: (LET

C C S (YO SUM))) (S Z A H L U N G E N ) )

und YO folgende Definition: eval>

(DEFINE

YO

(LAMBDA ( F U N ) ( L E T ((SUMME (LAMBDA ( F U N K T I O N ) (LAMBDA ( L I S T E ) ((FUN (FUNKTION FUNKTION)) (SUMME S U M M E ) ) ) ) ==> YO

LISTE)))))

Die so abstrahierte Definition YO stellt den Fixpunktoperator Y dar. Für seine allgemeine Anwendung, benennen wir die LAMBDA-Variablen von YO um,

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

223

und zwar: FUN in F, SUMME in G, FUNKTION in H und LISTE in X. Damit erhalten wir das folgende Y-Konstrukt: eval> (DEFINE Y (LAMBDA (F) (LET ((G (LAMBDA (H) (LAMBDA (X) ((F (H H)) X))))) (G G)))) ==> Y Das Erläuterungsbeispiel, die Summe der numerischen Elemente einer Liste, ergibt mit dem Fixpunktoperator Y und der eingesetzten SUM-Definition damit folgende Lösung: eval> (LET ((S (Y (LAMBDA (FKT) (LAMBDA (LST) (COND ((NULL? LST) 0) ((NUMBER? (CAR LST)) (+ (CAR LST) (FKT (CDR LST)))) (T (FKT (CDR L S T ) ) ) ) ) ) ) ) ) (S ZAHLUNGEN)) ==> 80. Diese Y-Definition hat die oben geforderte Eigenschaft eines Fixpunktoperators. Y erfüllt die folgende Gleichung: eval> (EQUAL? ((F (Y F)) ZAHLUNGEN) ((Y F) ZAHLUNGEN)) ==> #T Der bisherige Y-Operator ist definiert, um eine rekursive Funktion einzurichten. Damit eine Erweiterung auf zwei gegenseitig rekursive Funktionen möglich wird, ändern wir die Y-Definition, wie folgt: eval> (DEFINE Y (LAMBDA (F) (LET C CG (LAMBDA (H R) (LAMBDA (X) ((R (H H F)) X))))) (G G F)))) ==> Y Es wurde eine zusätzliche LAMBDA-Variable R eingeführt, damit F stets als Argument verwendet wird. Diese Y-Definition hat denselben Effekt. Allerdings hat G eine zusätzliche LAMBDA-Variable. So ist es möglich, daß G mit mehr als einer Funktion auf einmal umgehen kann. Zum leichteren Verstehen benennen wir in Y die Funktionen um, und zwar H in G: eval> (DEFINE Y (LAMBDA (F) (LET ((G (LAMBDA (G R) (LAMBDA (X) (CR (G G F)) X))))) (G G F)))) ==> Y

224 eval>

II. Konstruktionen (Analyse und Synthese) (DEFINE

Y-2

(LAMBDA (F (LET ((G

H) ( L A M B D A (G R) (LAMBDA (N) ( ( R (G G F) (G G F ) ) ) ) = = > Y - 2

(G G H ) )

N)))))

Ein einfaches Beispiel für die Y-2-Nutzung ist die Prüfung, ob eine vorgegebene Zahl gerade oder ungerade ist (vgl. Gabriel, 1988). eval>

((Y-2

( L A M B D A ( F H) (LAMBDA ( N ) ( L A M B D A ( F H) (LAMBDA (N)

(IF

(


(LETREC ( ( F (LAMBDA (H ( L A M B D A

(N) (N)

(IF (IF

(< (
" U n g e r a d e "

8.1.3 Funktion ALTERNATE Mit einer abwechselnden Abarbeitung der Teilaufgaben zweier Prozesse zeigen wir erneut die Möglichkeit, die Umgebung einer Funktion als Repräsentation von lokalen Variablen zu nutzen, die selbst wiederum Funktionen darstellen. Ein Prozeß ist als Kellerspeicher (engl.: Stack) abgebidelt. Er hat beim Aufruf die jeweils als nächste abzuarbeitende Teilaufgabe als Rückgabewert. Der Stack ist eine Funktion, deren (Definitions-)Umgebung mit dem SET'.-Konstrukt modifiziert wird. Zur Konstruktion dieser Funktion und ihrer Umgebung dient das folgende PROCESS-STACK-Konstrukt: eval>

(DEFINE PROCESS-STACK ( L A M B D A ( T A S K_ L I S T ) (LAMBDA ( ) (COND ( ( N U L L ? T A S K _ L I S T ) N I L ) (T ( B E G I N O (CAR T A S K _ L I S T ) ( S E T ! T A S K _ L I S T (CDR TASK_ L I S T ) ) ) ) ) ) ) ) ==> PROCESS-STACK

In PC Scheme können wir die Symbol-Wert-Assoziationen der Definitionshülle mit dem ENVIRONMENT-BINDINGS-Konstrukt ausgeben. eval> eval>

(CLOSURE? (PROCESS-STACK ' ( T A S K - A TASK-B T A S K - C ) ) ) ==> (ENVIRONMENT? (PROCESS-STACK ' ( T A S K - A TASK-B T A S K - C ) ) ) = = >

eval>

(ENVIRONMENT-BINDINGS (PROCESS-STACK '(TASK-A TASK-B T A S K - C ) ) ) ==> ((TASK_LIST TASK-A TASK-B

#T ( )

TASK-C))

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

225

Das Abwechseln zwischen den beiden Prozessen ist ebenfalls als Modifikation der Umgebung einer Funktion ALTERNATE abgebildet. Wird diese Funktion ALTERNATE angewendet, dann entsteht eine Funktion, deren Umgebung die lokalen Variablen E X E C U T E , PROCESS und WAIT_ PROCESS aufweist. Das Vertauschen der Werte dieser lokalen Variablen simuliert das Alternieren. eval>

( D E F I N E ALTERNATE (LAMBDA ( E X E C U T E _ P R O C E S S W A I T _ P R O C E S S ) (LAMBDA ( ) (LET ( ( A C T I V EXECUTE.PROCESS)) (SET! EXECUTE_PROCESS WAIT.PROCESS) (SET! WAIT_PROCESS ACTIV) ( A C T I V ) ) ) ) ) ==> ALTERNATE

Als Beispiel dient die globale Variable PROZESSWECHSEL, wobei als Startwert E X E C U T E . PROCESS an einen Stack mit den Teilaufgaben TASK-A, T A S K - B und TASK-C gebunden wird. WAIT_ PROCESS ist zu Beginn an einen Stack mit den Teilaufgaben TASK-1, TASK-2, TASK-3 und TASK-4 gebunden. PROZESSWECHSEL ist eine Funktion, in deren Umgebung die lokalen Variablen E X E C U T E . PROCESS und WAIT_ PROCESS jeweils an Funktionen gebunden sind, die einen Stack abbilden. eval>

( D E F I N E PROZESSWECHSEL (ALTERNATE (PROCESS-STACK '(TASK-A (PROCESS-STACK ' ( T A S K - 1

TASK-B TASK-2

TASK-C)) TASK-3 T A S K - 4 ) ) ) ) ==> PROZESSWECHSEL

Die Werte in den geschachtelten Umgebungen werden durch die Anwendung des ENVIRONMENT-BINDINGS-Konstruktes in Verbindung mit dem AListen-Selektor ASSOC deutlich: eval>

(ENVIRONMENT-BINDINGS PROZESSWECHSEL) ==> ((EXECUTE_PROCESS . #) (WAIT_PROCESS . #

(ENVIRONMENT-BINDINGS

(CDR

(ENVIRONMENT-BINDINGS

(ASSOC

'EXECUTE_PROCESS

PROZESSWECHSEL)))) ((TASK_LIST

TASK-A

==> TASK-B

TASK-C))

Die obige PRINT-Repräsentation # < P R O C E D U R E PROCESS-STACK> führt leicht zu Mißverständnissen, weil sie für zwei unterschiedliche Fälle verwendet wird, wie die folgende FOO- und BAZ-Definitionen zeigen.

226

II. Konstruktionen (Analyse und Synthese)

eval> eval>

( D E F I N E F00 P R O C E S S - S T A C K ) = = > F00 ( D E F I N E BAZ ( P R O C E S S - S T A C K ' ( T A S K - A

eval> eval>

F00 = = > # #

;; " P r e t t y - P r i n t " - K o n s t r u k t (PP F 0 0 ) = = > (LAMBDA (TAS K_ L I S T ) (LAMBDA ( ) (COND ( ( N U L L ? T A S K _ L I S T ) N I L ) (T ( B E G I N O (CAR T A S K _ L I S T ) ( S E T ! TASK_ L I ST (CDR T A S K _ L I S T ) ) ) ) ) ) )

eval>

(PP BAZ) = = > (LAMBDA ( ) (COND ( ( N U L L ? TASK_ L I S T ) N I L ) (T (BEGINO (CAR T A S K _ L I S T ) ( S E T ! TASK_ L I ST (CDR T A S K _ L I S T ) ) ) ) ) )

PROCESS-STACK> PR0CESS-STACK>

TASK-B

TASK-C))) = = > BAZ ;LAMBDA-Abstraktion ;Appli kation

Zur Verdeutlichung der Abarbeitungsschritte definieren wir die folgende Ausgabefunktion: eval>

(DEFINE ABARBEITUNGS-SCHRITTE (LAMBDA (N) (LETREC ( ( S T E P (LAMBDA (M) (COND ( ( > M N) ' F E R T I G ) (T (WRITELN M " . S c h r i t t : (PROZESSWECHSEL)) (STEP (1+ M ) ) ) ) ) ) ) (STEP 1 ) ) ) ) ==> ABARBEITUNGS-SCHRITTE

e v a l > (ABARBEITUNGS-SCHRITTE 1. S c h r i t t : T A S K - A 2. S c h r i t t : T A S K - 1 3. S c h r i t t : TASK-B 4. S c h r i t t : TASK-2 5. S c h r i t t : T A S K - C 6. S c h r i t t : T A S K - 3 7. S c h r i t t : ( ) FERTIG

7)

"

==>

8.2 Kontrollstruktur: Continuation Kennzeichnend für funktionsgeprägte Konstruktionen ist die Komposition von Funktionen (= evaluierte LAMBDA-Konstrukte). Die EVAL-Regeln legen ihre Abarbeitungs-Reihenfolge („Kontrollstruktur", vgl. Abschnitt 2.2) fest, d.h sie bestimmen, welche Funktion wann auszuwerten ist. Kern der Kontrollstruktur ist die Sequenz: 1. Bestimmung der Werte der Argumente und 2. Ermittlung des Funktionswertes mit den übernommenen Werten der Argumente (vgl. EVAL-Regel 2; vgl. Abschnitt 1.2).

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

227

Beispielsweise hat die Komposition (F (G (H FOO))) die folgende Kontrollstruktur: S^ Wert von F ermitteln. Da dieser Wert eine Funktion ist, ist zunächst sein Argument (G (H FOO)) zu evaluieren, daher: S 2 : Wert von G ermitteln. Da dieser Wert eine Funktion ist, ist zunächst sein Argument (H FOO) zu evaluieren, daher: S 3 : Wert von H ermitteln. Da dieser Wert eine Funktion ist, ist zunächst sein Argument FOO zu evaluieren, daher: S 4 : Wert von FOO ermitteln. Dieser sei V. S5: V in H Ubernehmen und Funktionswert H berechnen. Dieser Wert sei W. Sfi: W in G übernehmen und Funktionswert G berechnen. Dieser Wert sei X. S ? : X in F übernehmen und Funktionswert F berechnen. Dieser Wert sei Y . S g : Y ist der Wert der Komposition. Bei jedem Schritt S. der Folge S ( bis S g steht fest, welcher Schritt S ist, d.h die Fortsetzung (engl.: continuation) der Abarbeitung ist bekannt. Befinden wir uns beispielsweise beim Schritt S 5 , dann besteht die zukünftige Abarbeitung in der Applikation der Funktionen G und F. Dieses „Kontrollwissen" über die Zukunft hätte man gern beim Abarbeiten von S 5 direkt verfügbar. Man könnte dann z.B. entscheiden, ob es sich lohnt die „Zukunft" überhaupt abzuarbeiten. Ist das „Kontrollwissen" als Wert einer Variable speicherbar, dann kann man die Abarbeitung später fortsetzen. Außerdem ist sie dann beliebig oft wiederholbar. Scheme stellt mit dem Konstrukt CALL-WITH-CURRENT-CONTINUATION, kurz: CALL/CC, einen entsprechenden Mechanismus bereit. Als Variable, die an die „Zukunft" gebunden ist, dient die LAMBDA-Variable des LAMBDA-Konstruktes, das mit CALL/CC appliziert wird. Die Funktionsweise des CALL/CC-Konstruktes erläutern wir anhand der obigen Komposition (F (G (H FOO))). Darauf aufbauend wird gezeigt, daß das CALL/CC-Konstrukt als eine allgemeine Kontrollstruktur betrachtbar ist, die beliebige Sprünge in die „Zukunft" und „Vergangenheit" einer Abarbeitung ermöglicht. Mit dem CALL/CC-Konstrukt ist daher das klassische LISP-Srung-Paar C A T C H und THROW leicht abbildbar (vgl. Abschnitt 8.6). Vorab definieren wir für unsere Kompostion (F (G (H FOO))) ein simples Beispiel, wie folgt: eval>

(DEFINE

eval > ( D E F I N E

eval>

(DEFINE

FOO

7) = = >

FOO

H ( L A M B D A (N) (WRITELN "Funktion (1+ N ) ) ) = = > H

H:

" N)

G ( L A M B D A (N) (WRITELN "Funktion (1+ N))) = = > G

G:

" N)

II. Konstruktionen (Analyse und Synthese)

228

F (LAMBDA ( N ) (WRITELN "Funktion ( 1 + N ) ) ) ==> F

eval>

(DEFINE

eval>

( F ( G (H F 0 0 ) ) ) F u n k t i o n H: 7 Funktion G Funktion F

F:

10

Die „Zukunft", gesehen vom Zeitpunkt der Abarbeitung von H aus, speichern wir als globale Variable *KONTROLLWISSEN-BEI-H*. Diese Variable erhält den Wert über das CALL/CC-Konstrukt mit Hilfe des SET!-Konstruktes. Bei unserem CALL/CC-Konstrukt ist die „Zukunft" an die LAMBDA-Variable FORTSETZUNG gebunden, so daß wir sie beim Abarbeiten von CALL/CC an die globale Variable binden können. eval > ( D E F I N E EVAL>

*K0NTR0LLWISSEN-BEI-H*

()) ==>

(DEFINE H (LAMBDA (N) (CALL/CC (LAMBDA ( F O R T S E T Z U N G ) (SET! *K0NTR0LLWISSEN-BEI-H* ( W R I T E L N " F u n k t i o n H : " N) ( 1 + N ) ) ) ) ) ==> H Anwendung

*K0NTR0LLWISSEN-BEI-H*

FORTSETZUNG)

eval>

; ; E n t s p r i c h t der obigen ( F ( G (H F 0 0 ) ) ) ==> F u n k t i o n H: 7 Funktion G Funktion F 10

ohne

CALL/CC

eval>

*KONTROLLWISSEN-BEI-H* Der D a t e n t y p hat k e i n e aussage==> # < C 0 N T I N U A T I 0 N > fähige externe Darstellung, daher diese E r s a t z d a r s t e l l u n g analog z u r #

Die gespeicherte # kann nun erneut ausgeführt werden, d.h. wir setzten wieder bei H auf. Der Wert von H, also das Resultat der bisherigen Verarbeitung, ist als Argument angebbar. eval>

(*KONTROLLWISSEN-BEI-H* F u n k t i o n G: 8 Funktion F: 9 10

8)

==>

Die Continuation *KONTROLLWISSEN-BEI-H* kann mit einem anderen Wert als 8 appliziert werden, d.h die ausstehende Berechnung ist von der Stelle

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

229

H mit einem neuen Wert durchführbar. Wir springen quasi in die Berechnung mit einem neuen Startwert zurück. eval>

(*KONTROLLWISSEN-BEI-H* F u n k t i o n G: 13 F u n k t i o n F: 14 15

13)

==>

Der Contiunation-Mechanismus ermöglicht einerseits Sprünge aus einer Berechnung heraus, andererseits Sprünge in eine Berechnung hinein. Etwas salopp formuliert: Berechnungen sind jederzeit einfrierbar und jederzeit auftaubar. Legen wir eine Berechnung auf Eis, dann sind die schon vollzogenen Bindungen von Variablen miteinzufrieren. Erst die korrekte Behandlung dieser aktuellen Bindungen macht den Continuation-Mechanismus zu einer nützlichen Kontrollstruktur. Das einfache Heraus- und Hereinspringen in eine Folge von Anweisungen ermöglicht ein GO-TO-Konstrukt, das viele Programmiersprache bereitstellen. Davon zu unterscheiden ist der Rücksprung mit einer Aktivierung einer eingefrorenen, vormals aktuellen Umgebung. Wir verdeutlichen diese Eigenschaft des CALL/CC-Konstruktes anhand des folgenden Beispiels. Es prüft, ob in einer Liste von Zahlen eine negative Zahl vorkommt. Zur Iteration über die Liste nutzen wir das MAP-Konstrukt, wie folgt: eval>

(MAP (LAMBDA ( X ) ( I F •(1 2 -3 4 - 5 ) ==> -3 -5

(()

() ()

()

( > = 0 X)

(WRITELN

X)))

())

Dieses MAP-Konstrukt notieren wir in geschachtelte LAMBDA-Konstrukte, um Bindungswert innerhalb und außerhalb des MAP-Konstruktes zeigen zu können. eval>

eval>

( D E F I N E F00 (LAMBDA (N) (LET ( ( V A R N ) ) ( S E T ! VAR ( * 2 N ) ) (LAMBDA ( L ) (MAP (LAMBDA ( X ) ( I F ( > = 0 X) (WRITELN X ) ) ) (WRITELN " M A P - V o l 1 z u g " ) (WRITELN " 2 . S c h r i t t m i t B i n d u n g e n " " A u s s e n : " VAR " I n n e n : " L) (WRITELN " n . S c h r i t t " ) ) ) ) ) = = > F00 ( ( F 0 0 6) ' ( 1 2 - 3 4 - 5 ) ) = = > -3 -5 MAP-Vol1zug 2. S c h r i t t m i t B i n d u n g e n A u s s e n : n. S c h r i t t ()

12 I n n e n :

( 1 2 - 3

L)

4

-5)

230 eval>

II. Konstruktionen (Analyse und Synthese) ( ( F 0 0 6) ' ( 1 2 3 4 5 ) ) = = > MAP-Vol1zug 2. S c h r i t t m i t B i n d u n g e n A u s s e n : n. S c h r i t t ()

12 I n n e n :

(12

3 4 5)

Bei der ersten negativen Zahl sei die Analyse der Zahlenfolge zu beenden. Ist X negativ, dann arbeiten wir im MAP-Konstrukt die Fortsetzung des MAP-Konstruktes ab. Damit wir später von dieser Situation erneut die Abarbeitung starten können, wird sie an die globale Variable *KONSTRUKTIONS-REST* gebunden. Dazu applizieren wir das MAP-Konstrukt nicht direkt, sondern über das CALL/CC-Konstrukt. eval>

( D E F I N E F00 (LAMBDA (N) (LET ((VAR N)) ( S E T ! VAR ( * 2 N ) ) (LAMBDA ( L ) (CALL/CC (LAMBDA (FORTSETZUNG) (MAP (LAMBDA ( X ) ( I F ( > = 0 X) (BEGIN (WRITELN X) ( S E T ! *KONSTRUKT I O N S - R E S T * FORSETZUNG) ; ; DUMMY w e i l d i e S c h n i t t s t e l l e ;; e i n Argument e r f o r d e r t (FORTSETZUNG ' D U M M Y ) ) ) )

L) (WRITELN (SET!

"Vollzug")

KONSTRUKT-REST

(WRITELN

"2.

Schritt

"Aussen: (WRITELN

"n.

(DEFINE *K0NSTRUKTI0NS-REST*

eval>

((FOO 6)

2-3

4 -5)

" VAR "

Schritt")))))

eval>

'(1

NIL))) mit Bindungen

NIL)

Innen:

"

" L)

= = > FOO

= = > *KONSTRUKT I O N S - R E S T *

==>

-3 2.

Schritt

n.

Schritt

mit

Bindungen Aussen:

12 I n n e n :

( 1 2 - 3

4

-5)

() An die LAMBDA-Variable FORTSETZUNG ist die ausstehende Abarbeitung der beiden Konstrukte (WRITELN „2. Schritt mit Bindungen " "Aussen: " VAR „ Innen: " L) und (WRITELN „n. Schritt") incl. ihrer zugehörenden Umgebung gebunden, so daß VAR und L ihre bisher aktuellen Werte haben. Da diese „Zukunft" als * K O N S T R U K T I O N S - R E S T * gespeichert ist, können wir

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

231

sie erneut ausführen. Die Ausgabe von VAR und L zeigt wieder diese Werte, selbst wenn * KONSTRUKTIONS-REST* in einer entsprechend geänderten Umgebung ausgeführt wird. eval > ( CONTI NUATION? * KONSTRUKTION- REST*) ==> T eval> (*KONSTRUKTIONS-REST* 'DUMMY) ==> 2. S c h r i t t mit Bindungen Aussen: 12 Innen: ( 1 2 - 3 4 -5) n. S c h r i t t () eval> (LET ((VAR 7) (L '(A B C D))) (*K0NSTRUKTI0NS-REST* 'DUMMY)) ==> 2. S c h r i t t mit Bindungen Aussen: 12 Innen: ( 1 2 - 3 4 -5) n. S c h r i t t () Enthält die Folge keine negativen Zahlen, dann entspricht die FOO-Definition mit CALL/CC-Konstrukt der ursprünglichen Fassung. eval> ((F00 2) ' ( 1 2 3)) ==> MAP-Vol1zug 2. S c h r i t t mit Bindungen Aussen: 4 Innen: (1 2 3) n. S c h r i t t () eval> (CONTINUATION? *KONSTRUKT IONS-REST*) ==> () Der Continuation-Mechanismus erzeugt „Einheiten" im Sinne von "first-classObjekten" (vgl. Abschnitt 2.2). Sie sind daher auch als Argument einsetzbar. Wir können eine Continuation mit sich selbst als ein Argument aufrufen. Einen solchen Fall zeigt das folgende Konstrukt SUMME-M->N. Es ermittelt die Summe der Integerzahlen von der unteren Grenze M bis zur oberen Grenze N. Zum Verständnis dieser Selbstübergabe bei Continuations diskutieren wir vorab folgendes einfache Beispiele: eval> (DEFINE F00 (LIST (CALL/CC (LAMBDA (-C-) #T)))) ==> F00 eval> F00 ==> (#T) Beim Auswerten dieses CALL/CC-Konstruktes sind an die LAMBDA-Variable -C- die zu diesem Zeitpunkt noch ausstehenden Abarbeitungsschritte gebunden. Es sind die Schritte: Ausführung von LIST, Bindung von FOO an den Wert von LIST sowie Rückgabe des Symbols FOO als Wert von DEFINE. Nach dem Auswerten hat das CALL/CC-Konstrukt den Wert #T. Die Bindung von -C- ist wieder aufgelöst. Sie wurde hier nicht genutzt. Wir haben eine Situation, als wäre folgende Formulierung auszuwerten:

232

II. Konstruktionen (Analyse und Synthese)

eval> (DEFINE F00 (LIST #T)) ==> F00. In der folgenden Formulierung nutzen wir die Bindung von -C-. eval> (DEFINE F00 (LIST (CALL/CC (LAMBDA (-C-) - C - ) ) ) ) ==> F00 Der Rückgabewert des CALL/CC-Konstruktes ist der Wert von -C-, also die noch ausstehende Abarbeitungsschritte in Form einer Continuation. Mit dieser Continuation bildet LIST eine Liste und DEFINE bindet FOO an diese Liste und gibt FOO zurück. eval> FOO ==> (#) Wir greifen auf diese Continuation zurück und veranlassen die Ausführung der zunächst nicht vollzogenen Abarbeitungsschritte. eval> (CAR FOO) ==> # eval> ((CAR FOO) #T) ==> FOO eval> FOO ==> (#T) Es sei die Summe einer Integerzahlenfolge mit den Grenzwerten M und N zu bestimmen. Die Lösung bedingt die Abbildung einer Wiederholung. Eine solche Wiederholung ist als Rekursion realisierbar. Die Rekursion können wir simulieren, indem wir einer Funktion sich selbst als Argument übergeben. Diese Technik haben wir beim Fixpunktoperator Y genutzt (vgl. Abschnitt 8.1.2). Jetzt übergeben wir einer Continuation sich selbst als Argument. Anders formuliert: Die Continuation repräsentiert die Zukunft, d.h. die (noch) abzuwickelnden Schritte. Ihr Argument repräsentiert den Startwert dafür, d.h. den Wert der vollzogenen Schritte („Vergangenheit"). eval> (DEFINE SUMME-M->N (LAMBDA (M N) ;; Fal1unterscheidüngen 1 (COND ((< M N) (LET* ((SUMME 0) (VERARBEITUNG (CALL/CC (LAMBDA (-C-) - C - ) ) ) ) ;; Fal1unterscheidüngen 2 (COND ((= NM) (+ M SUMME)) (T (SET! SUMME (+ N SUMME)) (SET! N (-1+ N)) (WRITELN VERARBEITUNG " N: " N " SUMME : " SUMME (VERARBEITUNG VERARBEITUNG))))) (T (WRITELN "Von-Wert: " M " nicht groesser bis-Wert: " N)) ))) ==> SUMME-M->N

8. Abbildungsoption: Funktion mit zugeordneter Umgebung eval>

( S U M M E - M - > N 2 5) ==> # < C 0 N T I N U A T I 0 N > N : 4 SUMME # < C 0 N T I N U A T I 0 N > N : 3 SUMME # < C 0 N T I N U A T I 0 N > N : 2 SUMME 14

: : :

233

5 9 12

Zum Verstehen des Konstruktes SUMME-M->N ist die Bindung der L A M B DA-Variablen -C- bedeutsam. Ihr Wert repräsentiert die ausstehenden Abarbeitungsschritte, die das COND-Konstrukt (Fallunterscheidungen 2) definiert. Aufgrund des LET*-Konstruktes gehört die Bindung der lokalen Variablen S U M M E nicht zu den offenen Arbeitsschritten zum Zeitpunkt der Bindung von -C-. Definiert man SUMME-M->N allerdings mit LET, dann umfaßt die Continuation -C- die Bindung von S U M M E an den Wert 0. Man erhält damit das folgende, falsche Ergebnis: eval>

;; Mit LET s t a t t mit ( S U M M E - M - > N 2 5) ==> # < C 0 N T I N U A T I 0 N > N: 4 # < C 0 N T I N U A T I 0 N > N: 3 # < C 0 N T I N U A T I 0 N > N: 2 2

LET* SUMME SUMME SUMME

definiert : : :

5 4 3

8.3 Syntaxverbesserung durch Makros Das Makro ist ein Konstruktionsmittel zur Verbesserung der Syntax. Der Begriff „Makro" entstand im Rahmen der maschinennahen (Assembler-)Programmierung. Kommen Zeilen z.B. Anweisungen, Befehle oder Deklarationen in einer bestimmten Reihenfolge mehrmals vor, so ist es zweckmäßig, diese „Einheit" eindeutig und mnemotechnisch geschickt zu benennen. Im Quellcodetext wird dann an den einzelnen Stellen nur der eindeutige Bezeichner notiert. Makrobefehle verkürzen den Text wesentlich. Erst bei der Interpretation bzw. Compilation werden Makros aufgelöst, d.h. der jeweils ersetzte Text regeneriert. Diese sogenannte Makroexpansion ist eine rein textuelle Umformung. In LISP sind zwei Arten von Makros zu unterscheiden: („normale") Makros und READ-Makros. Makros expandieren in der EVAL-Phase. READ-Makros expandieren in der READ-Phase des READ-EVAL-PRINT-Zyklus. Das Hochkomma als Kurzschreibweise für das QUOTE-Konstrukt (vgl. Abschnitt 1.3) ist eines von zahlreichen eingebauten READ-Makros. In manchen LISP-Systemen (z.B. in Common LISP oder TLC-LISP) kann der Benutzer selbst READ-Makros definieren. Wir konzentrieren uns im folgenden auf die allgemein verfügbaren READ-Makros QUASIQUOTE (Kurzzeichen: ' ) , UNQUOTE (Kurzzeichen: , ) und UNQUOTE-SPLICING (Kurzzeichen: @ ). Als Beispiel für die Verbesserung der Übersichtlichkeit durch READ-Makros dient das Erweitern oder Modifizieren einer bestehenden Umgebung im Rahmen einer Kontenverwaltung.

234

II. Konstruktionen (Analyse und Synthese)

Wir definieren eine Umgebung KONTEN-MANAGEMENT mit dem THE-ENVIRONMENT-Konstrukt (vgl. Abschnitt 1.4.2; Näheres vgl. Abschnitt 8.4). eval> eval>

(DEFINE KONTEN-MANAGEMENT ((LAMBDA O ( T H E - E N V I R O N M E N T ) ) ) ) ==> K O N T E N - M A N A G E M E N T ==> # < E N V I R 0 N M E N T >

KONTEN-MANAGEMENT

Die Umgebung KONTEN-MANAGEMENT ergänzen wir um die Variablen FOO und BAZ. eval> eval> eval> eval>

( E V A L ' ( D E F I N E FOO ' ( + 1 2 ) ) K O N T E N - M A N A G E M E N T ) ==> ( E V A L ' ( D E F I N E BAZ FOO) K O N T E N - M A N A G E M E N T ) ==> BAZ ( A C C E S S FOO K O N T E N - M A N A G E M E N T ) ==> (+ 1 2) ( A C C E S S BAZ K O N T E N - M A N A G E M E N T ) ==> (+ 1 2)

FOO

Ist ein Teil des ersten Argumentes von EVAL jedoch zu evaluieren, hier (+ 1 2), dann ist mit Hilfe des LIST-Konstruktes erst ein auswertungsfahiges Argument zu konstruieren. eval>

(EVAL

eval>

(EVAL

'(LIST

'DEFINE

'BAZ

FOO)

KONTEN-MANAGEMENT) ==> ( D E F I N E BAZ (+ ( E V A L ' ( L I S T ' D E F I N E BAZ FOO) KONTEN-MANAGEMENT) K O N T E N - M A N A G E M E N T ) ==> BAZ

1

2)

oder eval> eval> eval>

(EVAL

(LIST 'DEFINE 'BAZ (EVAL 'FOO KONTEN-MANAGEMENT)) K O N T E N - M A N A G E M E N T ) ==> BAZ ( A C C E S S FOO K O N T E N - M A N A G E M E N T ) ==> ( + 1 2) ( A C C E S S BAZ K O N T E N - M A N A G E M E N T ) ==> 3

Bei einem umfangreicheren Argument wirken die dann erforderlichen vielen LIST und Q U O T E Formulierungen wenig übersichtlich. Eine syntaktische Vereinfachung bietet Q U A S I Q U O T E in Verbindung mit dem UNQUOTE-Konstrukt. Die Kurzschreibweise für das QUASIQUOTE-Konstrukt ist das Backquotezeichen '. Die Kurzschreibweise für das UNQUOTE-Konstrukt ist das Komma. eval> eval>

(EVAL

' ( D E F I N E BAZ . ( E V A L ' F O O K O N T E N - M A N A G E M E N T ) ) K O N T E N - M A N A G E M E N T ) ==> BAZ ( A C C E S S BAZ K O N T E N - M A N A G E M E N T ) ==> 3

Innerhalb des QUASIQUOTE-Konstruktes hebt ein UNQUOTE-Konstrukt die Wirkung als QUOTE-Konstrukt auf, d.h. der symbolische Ausdruck nach dem Komma ist nicht vom Q U O T E betroffen. Da es sich beim Backslash und beim Komma um Makros handelt, ist mit dem EXPAND-MACRO-Konstrukt der Zwischenschritt „Expandierter Zustand" anzeigbar.

8. Abbildungsoption: Funktion mit zugeordneter Umgebung eval>

(EXPAND-MACRO

' (DEFINE

BAZ

,(+ 1 2))

==>

235

( D E F I N E BAZ 3)

Manchmal ist das Einspleißen (engl.: splicing) eines Wertes erforderlich. Eine Liste ist als Folge ihrer Elemente, also ohne Klammern in eine andere Liste einzufügen. Dazu dient das UNQUOTE-SPLICING-Konstrukt. Es ist nur in Zusammenhang mit einem UNQUOTE-Konstrukt anwendbar. Man beachte, daß unmittelbar nach dem Komma das Zeichen „@" (spöttisch „Klammeraffe") notiert ist. eval>

;; A S C I I M m e r i c a n S t a n d a r d ( A S C I I->SYMB0L 64) ==> @

eval>

( D E F I N E BAR

'(0

12

eval>

( D E F I N E BAZ

'(5

6 ) ) ==>

eval> eval> eval>

(CONS ' + (APPEND ' ( + ,@BAR ,@BAZ) ; ; E i n QUASIQUOTE ;; des Argumentes (EXPAND-MACRO "(+

eval> ' ( + eval > ' (+

,@BAR

3 4))

Code f o r

Information

/ntergage)

= = > BAR

BAZ

BAR BAZ) = = > ( + 0 1 2 3 4 5 6 ) = = > ( + 0 1 2 3 4 5 6 ) gegen das E v a l u i e r e n von EXPAND-MACRO

,@BAZ)) = = >

(CONS

(QUOTE + )

(APPEND BAR

BAZ))

BAR BAZ) = = > ( + 0 1 2 3 4 5 6) BAR , (s BAZ) = = > ERROR . ¡Symbol @ n i c h t d e f i n i e r t i n aktueller Umgebung. A c h t u n g ! L e e r z e i c h e n z w i s c h e n Komma und " K l a m m e r a f f e n " macht d i e s e n zu e i n e m s e l b s t ä n d i g e n S y m b o l .

Mit dem QUASIQUOTE-Konstrukt drehen wir die übliche LISP-Notation um. Jetzt wird kein Ausdruck ausgewertet, der nicht besonders gekennzeichnet ist (durch das UNQUOTE-Konstrukt). Normalerweise wird jeder Ausdruck ausgewertet, es sei denn, er ist besonders (durch das QUOTE-Konstrukt) gekennzeichnet. Makros, die in der EVAL-Phase expandieren, sind in PC Scheme mit dem folgenden MACRO-Konstrukt zu definieren: eval>

(MACRO < e x p a n d i e r u n g s - v o r s c h r i f t > )

==>

mit:

: : = Name des

neuen s p e z i e l l e n

:= LAMBDA-Konstrukt mit e i n e r

Konstruktes LAMBDA-Variablen

Das MACRO-Konstrukt deklariert einen Namen als spezielles Schlüsselwort. Ist beim Evaluieren ein solches Schlüsselwort in funktionaler Stellung (erstes Ele-

236

II. Konstruktionen (Analyse und Synthese)

ment einer Liste), dann wird zunächst die Expandierungsvorschrift ausgewertet und anschließend das entstandene Resultat evaluiert. Zur Ausführung der Expandierungsvorschrift wird die LAMBDA-Variable an die gesamte Liste einschließlich des Schlüsselwortes gebunden. Mit Hilfe des Konstruktes EXPANDMACRO ist das Resultat der Expandierungsvorschrift darstellbar. eval>

(MACRO R E V E R S E - C O N S (LAMBDA ( S E X P R ) '(CONS .(LIST-REF .(LIST-REF

eval>

(EXPAND-MACRO

eval>

(DEFINE

eval> eval> eval>

F00

SEXPR SEXPR

2) 1))))

'(REVERSE-CONS

(LAMBDA

==>

A B))

REVERSE-CONS

==>

(ANFANG R E S T ) (REVERSE-CONS

(CONS

ANFANG

B A) REST)))

==>

F00

(DEFINE A 'ARBEITEN!) ==> A (DEFINE B 'HEUTE) ==> B ( F 0 0 A B) = = > ( H E U T E . A R B E I T E N )

Die Makro-Option ist einerseits ein mächtiges Konstruktionsmittel andererseits in der Praxis eine häufige Fehlerquelle. Sorgfältig zu beachten sind der Zeitpunkt und die Umgebung des Textersatzes. Bei den Normierungsbemühungen für Scheme ist die Makro-Option deshalb bisher nicht enthalten (vgl. Rees/Clinger, 1986). In PC Scheme sind Makros stets vor ihrer ersten Nennung in einer Funktion zu definieren, wie das folgende Beispiel zeigt: eval>

(DEFINE

eval>

(MACRO A D D - 2 (LAMBDA ( S E X P R ) ' ( + 2 ,@(CDR S E X P R ) ) ) )

eval>

(F00

4)

F00

(LAMBDA

==>

ERROR

(N)

...

(ADD - 2 N ) ) )

==>

ADD-2

¡Symbol

ADD-2

; nicht eval>

(DEFINE

F00

(LAMBDA

eval>

(F00

==>

6

4)

(N)

(ADD-2

==>

in

F00

aktueller

Umgebung

definiert, N)))

==>

F00

Einem Symbol als Schlüsselwort einer Makrodefinition können wir nicht unmittelbar einen anderen Wert zuweisen. eval>

(DEFINE

ADD-2

7)

==>

ERROR

...

¡Makro

als

Variable

benutzt.

Erst mit einer neuen „leeren" Makrodefinition wird das Symbol wieder allgemein verfügbar. eval> eval> eval>

(MACRO A D D - 2 ( ) ) (DEFINE ADD-2 7) ADD-2 ==> 7

==> ==>

ADD-2 ADD-2

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

237

Eine etwas umfangreichere Makrodefinition zeigt das Programmfragment 8.3.1. Das dortige DEFINE-P-Konstrukt ist in der Lage, wie ein LET-Konstrukt (vgl. Abschnitt 2.2.1), beliebig viele Symbole an Werte zu binden. eval> eval> eval> eval> eval>

(DEFINE F00 10) ==>F00 (DEFINE-P F00 (* 2 F00) BAR F00 BAZ) ==> #T F00 ==> 20 BAR ==> 10 ¡Achtung: 10 nicht 20 !!! BAZ ==> ()

Ein wesentliches Kriterium ist die richtige Umgebung, in der das einzelne DEFINE-Konstrukt evaluiert wird. Erst sind alle Werte zu ermitteln, ehe eine Zuweisung zu den angebenen Symbolen erfolgen kann. Die Lösung nutzt daher neue, nicht „interngemachte" Symbole (vgl. Abschnitt 7.4). Da das LET-Konstrukt in der Lösung eine neue Umgebung aufbaut, ist das DEFINE-Konstrukt in der vorhergehenden Umgebung auszuwerten. Diese Umgebung liefert das ENVIRONMENT-PARENT-Konstrukt (Näheres dazu vgl. Abschnitt 8.4). Die Lösung hat folgende Struktur: (LET ( ( g l ) (g2 ) . . . ) (BEGIN (EVAL (LIST 'DEFINE ' (EVAL gl (THE-ENVIRONMENT) ) ) (ENVIRONMENT-PARENT (THE-ENVIRONMENT))) T)) ;Rückgabewert Das Makro-Konstrukt DEFINE-P expandiert zu folgendem symbolischen Ausdruck: eval> (PP (EXPAND-MACRO-1 '(DEFI NE-P F00 (* 2 F00) BAR F00 BAZ))) (LET ((VARS-L '(F00 BAR BAZ)) (TEMP-SYMBOLS '(G127 G128 G129)) (G127 (* 2 F00 ) ) (G128 F00) (G129 ( ) ) ) (BEGIN (EVAL (LIST 'DEFINE 'BAZ (EVAL G129 (THE-ENVIRONMENT))) (ENVIRONMENT-PARENT (THE-ENVIRONMENT))) (EVAL (LIST 'DEFINE 'BAR (EVAL G128 (THE-ENVIRONMENT))) (ENVIRONMENT-PARENT (THE-ENVIRONMENT))) (EVAL (LIST 'DEFINE 'F00 (EVAL G127 (THE-ENVIRONMENT))) (ENVIRONMENT-PARENT (THE-ENVIRONMENT))) #T))

238

II. Konstruktionen (Analyse und Synthese)

; ; ; ; Dokument 8 . 3 . - 1 ; ; ; ; T i t e l : Makro DEFINE-P ; ; ; ; E r s t e l l t am: 2 0 . 1 0 . 8 9 ;;; ;;;

AI

zum B i n d e n von m e h r e r e n ¡ l e t z t e Änderung: -

Symbolen.

DEFINE-P b i n d e t mehrere Symbole. Die A b a r b e i t u n g e n t s p r i c h t dem L E T - K o n s t r u k t ( v g l . A b s c h n i t t 2 . 2 . 1 ) .

(MACRO D E F I N E - P (LAMBDA ( S E X P R ) (LETREC ((GET-VARS (LAMBDA ( L ) (COND ( ( N U L L ? L) N I L ) (T (CONS (CAR L) ( G E T - V A R S (CDDR L ) ) ) ) ) ) ) (GET-ARGS (LAMBDA ( L ) (COND ( ( N U L L ? (CDR L ) ) N I L ) (T (CONS (CADR L) ( G E T - A R G S (CDDR L ) ) ) ) ) ) ) (MAKE-SYMBOLS (LAMBDA ( L ) (COND ( ( N U L L ? L) N I L ) (T (CONS (GENSYM) (MAKE-SYMBOLS (CDR L ) ) ) ) ) ) ) (MAKE - L E T - L I S T E (LAMBDA (TEMP-SYM ARGS) (COND ( ( N U L L ? T E M P - S Y M ) N I L ) (T (CONS ( L I S T (CAR T E M P - S Y M ) (CAR A R G S ) ) ( M A K E - L E T - L I S T E (CDR T E M P - S Y M ) (CDR A R G S ) ) ) ) ) ) ) ( V A R S - L ( G E T - V A R S (CDR S E X P R ) ) ) ( A R G S - L (GET-ARGS (CDR S E X P R ) ) ) ( T E M P - S Y M B O L S (MAKE-SYMBOLS V A R S - L ) ) ( M - L (CONS ( L I S T ' V A R S - L ( L I S T 'QUOTE V A R S - L ) ) (CONS ( L I S T ' T E M P - S Y M B O L S ( L I S T 'QUOTE T E M P - S Y M B O L S ) ) ( M A K E - L E T - L I S T E TEMP-SYMBOLS A R G S - L ) ) ) ) ) '(LET

,M-L ,(DO ( ( V A R S V A R S - L (CDR V A R S ) ) ( V A L U E S TEMP-SYMBOLS (CDR V A L U E S ) ) (AKKU N I L ) ) ( ( N U L L ? VARS) (APPEND (CONS ' B E G I N AKKU) ( L I S T T ) ) ) ( S E T ! AKKU (CONS ' ( E V A L (LIST 'DEFINE ' , ( C A R VARS) (EVAL .(CAR VALUES) (THE-ENVIRONMENT))) (ENVIRONMENT-PARENT (THE-ENVIRONMENT))) AKKU)))) ) ) ) ==> DEFINE-P

Programmfragment

8 . 3 . 1 Makro

DEFINE-P

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

239

8.4 Lokaler Namensraum (Pakete) Eine Umgebung stellt Symbol-Wert-Assoziationen bereit (vgl. Abschnitt 1.4.2). Sie ist als ein lokaler Namensraum betrachtbar. Der gleiche Name, d.h. das gleiche Symbol, kann in unterschiedlichen Umgebungen zu verschiedenen Werten führen. Die Umgebung ist daher ein zweckmäßiges Konstruktionsmittel, um Namenskonflikten vorzubeugen. Werden fremde Konstrukte importiert oder arbeiten mehrere Programmierer gemeinsam an einer Problemlösung, dann besteht die Gefahr, daß für verschiedene Konstrukte die gleichen Namen verwendet werden. Das Einbetten jeder Teillösung in eine eigene Umgebung verhindert das Überschreiben einer Symbol-Wert-Assoziation. Die Umgebung übernimmt eine Abschottungsaufgabe. Sie „verpackt" eine (Teil)Lösung zu einem „Paket". Ein Paketname bezeichnet somit eine Sammlung von Symbol-Wert-Assoziationen. Der Paketname ist ein Abstraktionsmittel (vgl. Abschnitt 4). Er ermöglicht, diese Sammlung als eine „Einheit" zu benutzen. Pakete sind mit Paketen kombinierbar. Sie können aus schon existierenden Paketen konstruiert werden kann. Dazu kann ein Paket mit anderen Paketen „kommunizieren". Diese Kommunikation bedingt, daß Pakete über gemeinsame Namen verfügen. Wir können drei Arten von Namen unterscheiden: o Importnamen, Dies sind Namen, die in anderen Paketen definert sind und im vorliegenden Paket genutzt werden. o Exportnamen und Dies sind Namen, die, falls Bedarf besteht, von anderen Paketen genutzt werden können. o Interne Namen Dies sind Namen, nur für den internen Gebrauch im vorliegenden Paket. Die Import- und Exportnamen bilden die Schnittstelle des Paketes. Sie sind die Grundlage um die Modularisierung einer komplexen Aufgabe zu spezifizieren (Näheres vgl. Abschnitt 12.2). Umgebungen können in einer Hierarchie angeordnet werden. Entsprechend dieser Struktur sind ihre Namen verfügbar. Nicht zugreifbar sind Namen für Umgebungen auf höherer Ebene, während umgekehrt der Zugriff von tieferen Ebenen entlang der Hierarchie erfolgen kann. Umgebungen bilden damit sogenannte Blockstrukturen, wie sie erstmals in ALGOL (Akronym für ALGOrithmic Language) realisiert wurden. Anhand des Beispiels Kontenverwaltung erörtern wir das „Verpacken" von Symbolen.

240

II. Konstruktionen (Analyse und Synthese)

Beispiel: Kontenverwaltung Die bisherige Kontenverwaltung (vgl. Abschnitt 1.4.2) umfaßt jetzt zusätzlich das Anlegen, Modifizieren und Löschen von Konten. Auf ein angelegtes Konto können Ein- und Auszahlungen gebuchen werden, wobei der Kontostand fortgeschrieben und die Mittelverfügbarkeit im Fall einer Abbuchung kontrolliert wird. Diese Kontenverwaltung nutzt den Verbund aus: o Konstruktoren: MAKE-KONTO-STRUKTUR und MAKE-KONTO, o Selektoren: NAME etc.,

GET-EINZAHLUNGEN,

GET-AUSZAHLUNGEN,

GET-

o Prädikaten: KONTO?, BETRAG?, ADRESSE? etc. und o Modifikatoren: SET-EINZAHLUNGEN!, ADD-EINZAHLUNG!, LOESCHE- KONTO!, AENDERE-KONTO-IDENTIFIZIERUNG! etc. Damit diese Konstrukte nicht in der allgemeinen Benutzerumgebung (in PC Scheme USER-INITIAL-ENVIRONMENT) zugreifbar sind, definieren wir sie in der eigenen Umgebung KONTO-MANAGEMENT. Als Symbole in der Umgebung KONTO-MANAGEMENT sind sie „versteckt", d.h. auf sie kann nicht direkt aus der hierarchisch höheren Umgebung USER-INITIAL-ENVIRONMENT zugegriffen werden. e v a l > MAKE-KONTO = = > ERROR

...

¡Symbol

an k e i n e n Wert

gebunden

Die einzelnen Konten grenzen wir ebenfalls in einer eigenen Umgebung, genannt KONTEN-DATENBANK, gegen andere Umgebungen ab. Alle Konten haben die Attribute: TYP, IDENTIFIZIERUNG, NAME-INHABER, ADRESSE-INHABER, EINZAHLUNGEN, AUSZAHLUNGEN und KONTOSTAND. Für jedes Konto sind die Attribut-Wert-Bindungen festzuhalten. Diese realisieren wir wieder mit der Definition einer eigenen Umgebungen. Ist eine Operation, die in der Umgebung KONTEN-MANAGEMENT gebunden ist, anzuwenden auf ein Konto (Operand), gebunden in der Umgebung KONTEN-DATENBANK, und dessen Werte, gebunden in der Umgebung, die dieses Konto darstellt, dann ist für das Evaluieren die explizite Angabe der einzelnen Umgebung erforderlich. Auf eine explizite Umgebungsnennung können wir in zwei Fällen verzichten: o Das Symbol ist in der aktuellen Umgebung gebunden, und es wird dieser Wert benötigt. o Das Symbol ist in hierarchisch höheren Umgebungen gebunden, und es wird der Wert benötigt, der auf dem Weg von der aktuellen Umgebung zur Hierarchie-Spitze (top level) als erster für das Symbol angetroffen wird. Bild 8.4-1 zeigt ein Beispiel einer Umgebungshierarchie. Ist dort z.B. die Umgebung U 4 die aktuelle Umgebung, dann sind direkt alle Symbole zugreifbar in

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

241

den Umgebungen U 4 , U 2 , und U . Nicht zugreifbar sind dann die Symbole von U 5 und U 3 sind. Bild 8.4-2 zeigt die „Abschirmung", d.h. die lokalen Namensräume, für die Kontenverwaltung. Die Umgebungen KONTEN-DATENBANK und KONTEN-MANAGEMENT definieren wir mit Hilfe des eingebauten Konstruktes THE-ENVIRONMENT, wie folgt: eval>

eval>

( D E F I N E KONTEN - DATENBANK (LET ((TYP 'OPERANDEN)) (THE-ENVIRONMENT)))

==>

KONTEN - DATENBANK

(DEFINE KONTEN-MANAGEMENT (LET ((TYP 'OPERATIONEN) (MAKE-KONTO-STRUKTUR (LAMBDA () (LET ((TYP 'KONTO) ( I D E N T I F I Z I E R U N G 0) (NAME-INHABER NIL) (ADRESSE-INHABER NIL) (EINZAHLUNGEN NIL) (AUSZAHLUNGEN NIL) (KONTOSTAND 0)) (THE-ENVIRONMENT))))) ( T H E - E N V I R O N M E N T ) ) ) ==> K O N T E N - M A N A G E M E N T

Die Umgebung KONTEN-MANAGEMENT verpackt die Symbole T Y P und MAKE-KONTO-STRUKTUR mit ihren Werten. eval>

(ENVIRONMENT-BINDINGS ((TYP . OPERATIONEN)

K O N T E N - M A N A G E M E N T ) ==> (MAKE-KONTO-STRUKTUR .

#))

242

II. Konstruktionen (Analyse und Synthese)

-USER-GLOBAL-ENVIRONMENTr — U S E R - I N I T I A L - ENVIRONMENTr—KONTEN - D A T E N B A N K -

c c

A001

A002-

—KONTEN-MANAGEMENT( ( M A K E - K O N T O .. . ) ( D E S C R I B E - K O N T O ...) ( K O N T O ? ... ) ( G E T - K O N T O ... )

Legende: USER-GLOBAL-ENVIRONMENT ::= G l o b a l e S y s t e m u m g e b u n g . Sie e n t h ä l t die " e i n g e b a u t e n " Konstrukte; z.B.: C(CAR . # < P R O C E D U R E > ) (CDR . # < P R O C E D U R E ) ...) USER-INITIAL-ENVIRONMENT ::= B e n u t z e r u m g e b u n g für die a l l g e m e i n g ü l t i g e n ( g l o b a l e n ) , selbstdefinierten Konstrukte; z.B.: ((KONTEN-DATENBANK . #) ( K O N T E N - M A N A G E M E N T . # < E N V I R O N M E N T > ) ...) KONTEN-DATENBANK ::= U m g e b u n g zur A b b i l d u n g der K o n t e n ; z . B . : ((A001 . # < E N V I R O N M E N T > ) (A002 . # < E N V I R O N M E N T > ) A001 ::= U m g e b u n g zur A b b i l d u n g der W e r t e e i n e s ( ( N A M E - I N H A B E R . "Otto") ( A D R E S S E - I N H A B E R . " B e r l i n " ) ...)

Kontos;

z.B.:

KONTEN-MANAGEMENT ::= U m g e b u n g zur A b b i l d u n g der O p e r a t i o n e n ; z . B . : ((MAKE-KONTO.#) (KONTO? . #) Bild 8 . 4 - 2 .

Kontenverwaltung

abgeschirmt

durch

...)

...)

Umgebungen

Eine schon bestehende Umgebung ist durch das DEFINE-Konstrukt erweiterbar, wenn es in dieser Umgebung ausgewertet wird.

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

243

e v a l > (EVAL ' ( D E F I N E F00 'BAR) K O N T E N - M A N A G E M E N T ) = = > F00 eval > ( E N V I R O N M E N T - B I N D I N G S K O N T E N - M A N A G E M E N T ) = = > ((FOO . B A R ) ( T Y P . O P E R A T I O N E N ) (MAKE-KONTO-STRUKTUR . #)) Die Modifikation der Bindung eines bestehenden Symbols erfordert das Evaluieren des SET'.-Konstruktes in der entsprechenden Umgebung. e v a l > (EVAL '(SET! FOO 'BAZ) K O N T E N - M A N A G E M E N T ) = = > BAZ eval> (ENVIRONMENT-BINDINGS KONTEN-MANAGEMENT) ==> ((FOO . B A Z ) ( T Y P . O P E R A T I O N E N ) (MAKE-KONTO-STRUKTUR . #)) Eine Symbol-Wert-Assoziation wird aus der Umgebung mit dem UNBINDKonstrukt entfernt. Das UNBIND-Konstrukt erfordert stets die explizite Angabe der Umgebung. e v a l > ( U N B I N D '+ ) = = > ERROR ... ; 2 A r g u m e n t e e r f o r d e r l i c h e v a l > ( U N B I N D 'FOO K O N T E N - M A N A G E M E N T ) = = > # < E N V I R 0 N M E N T > eval> (ENVIRONMENT-BINDINGS KONTEN-MANAGEMENT) ==> ((TYP . O P E R A T I O N E N ) ( M A K E - K O N T O - S T R U K T U R . # < P R O C E D U R E > ) )

Unter Verwendung der Konstruke QUASIQUOTE und UNQUOTE (vgl. Abschnitt 8.3) erweitern wir die definierte Umgebung KONTEN-MANAGEMENT um das MAKE-KONTO-Konstrukt. MAKE-KONTO hat in KONTENMANAGEMENT eine Funktion mit der LAMBDA-Variablen K_ IDENT als Wert. Diese Funktion enthält in ihrem Funktionskörper die lokalen Funktionen MAKE-OBJEKT und SET-IDENT!. Die Anwendung der Funktion MAKE-OBJEKT erweitert die Umgebung KONTEN-DATENBANK um das Konto IDENT. Dabei ist der Wert von IDENT die Umgebung, die durch MAKE-KONTO-STRUKTUR erzeugt wird. Der Wert von MAKE-OBJEKT ist der Wert von IDENT. e v a l > (EVAL ' ( D E F I N E M A K E - K O N T O ( L A M B D A ( K _ I D E N T ) (LET ( ( M A K E - O B J E K T ( L A M B D A (IDENT) (EVAL ' ( D E F I N E .IDENT ,(MAKE-KONTO-STRUKTUR)) KONTEN-DATENBANK) ;; D a m i t d e r R ü c k g a b e w e r t d e r ;; I D E N T - W e r t i s t . (EVAL IDENT K O N T E N - D A T E N B A N K ) ) ) ( S E T - I D E N T ! ( L A M B D A (KONTO IDENT) (EVAL '(SET! I D E N T I F I Z I E R U N G ',K_IDENT) KONTO)))) (SET-IDENT! (MAKE-OBJEKT K_IDENT) K_IDENT)))) KONTEN-MANAGEMENT) ==> MAKE-KONTO

244

1

II. Konstruktionen (Analyse und Synthese)

X^A

mallOl Kennt VI EO_LE I C M T OEMANT: H e r r n ^Foo, ) n H Hll E f ? IN " D I E S E R E T A Q l

Da MAKE-KONTO-STRUKTUR ein Symbol in KONTEN-MANAGEMENT ist, kann es ohne explizite Umgebungsangabe evaluiert werden, wenn die aktuelle Umgebung durch die definierte Hierarchie auf KONTEN-MANAGEMENT zugreifen kann. Mit der Definition von MAKE-OBJEKT als LAMBDA-Konstrukt wird eine Umgebung an diese Funktion gebunden. Dies geschieht innerhalb der Funktion MAKE-KONTO. Deren hierarchisch höhere Umgebung übernimmt die Rolle einer „Elternschaft". Die Umgebung der Eltern ist mit dem ENVIRONMENT-PARENT-Konstrukt zugreifbar, wie das folgende Beispiel skizziert. Dieses M-K-Konstrukt orientiert sich an der Struktur von MAKEKONTO. eval> (DEFINE M-K (LAMBDA (K_IDENT) (LET ((M-K-0 (LAMBDA (IDENT) (WRITELN "Fuehre Nebeneffekte aus!") (THE-ENVIRONMENT)))) (M-K-0 'A001)))) ==> M-K eval> (ENVIRONMENT-BINDINGS (M-K 'ABC)) ==> Fuehre Nebeneffekte aus! ((IDENT . A001)) eval> (ENVIRONMENT-BINDINGS (ENVIRONMENT-PARENT (M-K 'ABC))) ==> Fuehre Nebeneffekte aus! ((K_IDENT . ABC)) Entsprechend unserem MAKE-KONTO-Konstrukt erweitern wir KONTENMANAGEMENT um ein DESCRIBE-KONTO-Konstrukt. Ein Konto erhalten wir durch Zugriff mittels ACCESS in der Umgebung KONTEN-DATEN-

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

245

BANK. Das COPY-Konstrukt dient als Schutz vor dem destruktiven SORT!Konstrukt (vgl. Abschnitt 5.1). eval> (EVAL '(DEFINE D E S C R I B E - K O N T O (LAMBDA (K_I DENT) (NEWLINE) (WRITELN " Inhalt des Kontos: " K_I DENT "

" )

(PP (SORT! (COPY ( E N V I R O N M E N T - B I N D I N G S (EVAL '(ACCESS ,K_IDENT KONTEN - D A T E N B A N K ) ) ) ) ) ) (NEWLINE) *THE-NON-PRINTING-OBJECT*)) KONTEN-MANAGEMENT) ==> DESCRIBE-KONTO Beim Benutzen der Kontenverwaltung ist USER-INITIAL-ENVIRONMENT die aktuelle Umgebung. Um das MAKE-KONTO-Konstrukt aus dieser Umgebung aufrufen zu können, definieren wir ein gleichnamiges Konstrukt in der USER-INITIAL-ENVIRONMENT. Dieses MAKE-KONTO-Konstrukt legt das Konto an und gibt es mit DESCRIBE-KONTO auf dem Bildschirm aus. eval> (DEFINE M A K E - K O N T O (LAMBDA (K_IDENT) (EVAL '(MAKE-KONTO ',K_IDENT) K O N T E N - M A N A G E M E N T ) (EVAL '(DESCRIBE-KONTO ',K_IDENT) KONTEN - M A N A G E M E N T ) ) ) ==> M A K E - K O N T O eval> (MAKE-KONTO 'A001) = = > Inhalt des Kontos: A001 ((ADRESSE-INHABER) (AUSZAHLUNGEN) (EINZAHLUNGEN) (IDENTIFIZIERUNG . A001) (KONTOSTAND . 0) (NAME-INHABER) (TYP . KONTO)) Bei mehreren zu definierenden Operationen ist die bisherige Formulierung: (EVAL '(DEFINE FOO ... ) KONTEN-MANAGEMENT) umständlich. Wir definieren eine Syntaxverbesserung in Form eines Makros (vgl. Abschnitt 8.3). eval> (MACRO D E F I N E - O P E R A T I O N (LAMBDA (SEXPR) '(EVAL \ ( C 0 N S 'DEFINE (CDR SEXPR)) KONTEN-MANAGEMENT))) ==> DEFINE-OPERATION eval>

(EXPAND-MACRO '(DEFINE-OPERATION FOO (LAMBDA (X) "... ==> (EVAL (QUOTE (DEFINE FOO (LAMBDA (X) "... KONTEN-MANAGEMENT)

..."))) ...")))

II. Konstruktionen (Analyse und Synthese)

246

eval>

(DEFINE-OPERATION DESCRIBE-KONTO-STRUKTUR (LAMBDA ( ) (NEWLINE) (WRITELN " Kontostruktur ") (PP ( E N V I R O N M E N T - B I N D I N G S (MAKE-KONTO-STRUKTUR))) (NEWLINE) *THE-NON-PRINTING-OBJECT*)) ==> DESCRIBE-KONTO-STRUKTUR

eval>

(DEFINE-OPERATION DESCRIBE-KONTEN (LAMBDA ( ) (NEWLINE) (WRITELN " Angelegte Konten ") ( P P ( S O R T ! (MAP ( L A M B D A ( X ) ( C A R X ) ) ( E N V I R O N M E N T - B I N D I N G S KONTEN - D A T E N B A N K ) ) ) ) (NEWLINE) *THE-NON-PRINTING-OBJECT*)) ==> DESCRIBE-KONTEN

eval>

( D E F I N E - O P E R A T I O N D E S C R I B E - O P E R A T I ON EN (LAMBDA ( ) (NEWLINE ) (WRITELN " O p e r a t i o n e n der K o n t e n v e r w a l t u n g ") ( P P ( S O R T ! (MAP ( L A M B D A ( X ) ( C A R X ) ) (ENVIRONMENT-BINDINGS KONTEN-MANAGEMENT)))) (NEWLINE) * T H E - N 0 N - P R I N T I N G - O B J E C T * ) ) = = > D E S C R I B E - O P E R A T I ON EN

Um diese Operationen aus der USER-INITIAL-ENVIRONMENT heraus aufrufen zu können, müßten entsprechend der obigen Definition für MAKE-KONTO dort Symbole definiert werden. Mit Hilfe eines Makros erleichtern wir uns solche Definitionen. Ein SEND-Makro übernimmt die Operation mit ihren jeweiligen Argumenten und erzeugt einen Aufruf: (EVAL KONTEN-MANAGEMENT). eval>

eval>

(MACRO S E N D (LAMBDA ( S E X P R ) '(EVAL ' , ( C D R SEXPR)

KONTEN-MANAGEMENT)))

(SEND DESCRIBE-KONTO ' A 0 0 1 ) I n h a l t des K o n t o s : A001 ((ADRESSE-INHABER) (AUSZAHLUNGEN) (EINZAHLUNGEN) (IDENTIFIZIERUNG . A001) (KONTOSTAND . 0) (NAME-INHABER) (TYP . KONTO))

==>

==>

SEND

247

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

;;;; ;;;; ;;;; :;;:

Dokument 8 . 4 - 1 T i t e l : VerwaltungvonKontenaufdenenEin-und gebucht werden. E r s t e l l t : 17.11.89 ;1etzte Aenderung : -

;;;;

Syntaxhilfen

;;;

Auszahlungen

(Makros)

DEFINE-OPERATION

und

;; ;; ;; ;

KMoAnKsEt-rKuOkNt To Or e - SnT R U K T U R

;;;;

Praedi kate

SEND

(siehe

obigen

und M A K E - K O N T O

Text)

(siehe

obigen

Text)

( D E F I N E - O P E R A T I O N KONTO? (LAMBDA ( X ) ( L E T ( ( W E R T ( E V A L " ( A C C E S S ,X K O N T E N - D A T E N B A N K ) ) ) ) (AND ( E N V I R O N M E N T ? WERT) ( E Q ? ' K O N T O ( A C C E S S TYP W E R T ) ) ) ) ) ) ( D E F I N E - O P E R A T I O N BETRAG? (LAMBDA ( X ) (AND (NUMBER?

X)

(>=

X

0))))

( D E F I N E - O P E R A T I O N BUCHUNGSDATUM? ( L A M B D A ( X ) (AND ( N U M B E R ? X ) ( > =

X

890101))))

( D E F I N E - O P E R A T I O N NAME? (LAMBDA ( X ) ( S T R I N G ? X ) ) ) (DEFINE-OPERATION ADRESSE? (LAMBDA ( X ) ( S T R I N G ? X ) ) ) ;;:;

Selektoren

( D E F I N E - O P E R A T I O N GET-KONTO (LAMBDA ( K _ I D E N T ) (COND ( ( K O N T O ? K _ I D E N T ) ( E V A L K _ I D E N T K O N T E N - D A T E N B A N K ) ) (T (ERROR " K e i n K o n t o : " K _ I D E N T ) ) ) ) ) (DEFINE-OPERATION GET-NAME-INHABER (LAMBDA ( K _ I D E N T ) ( A C C E S S NAME-INHABER (DEFINE-OPERATION GET-ADRESSE-INHABER (LAMBDA ( K _ I D E N T ) ( A C C E S S A D R E S S E - I N H A B E R (GET-KONTO

(GET-KONTO

K_IDENT))))

K_IDENT))))

(DEFINE-OPERATION GET-EINZAHLUNGEN (LAMBDA ( K _ I D E N T ) ( A C C E S S EINZAHLUNGEN

(GET-KONTO

K_IDENT))))

( D E F I N E - O P E R A T I O N GET-AUSZAHLUNGEN (LAMBDA ( K _ I D E N T ) ( A C C E S S AUSZAHLUNGEN

(GET-KONTO

K_IDENT))))

( D E F I N E - O P E R A T I O N GET-KONTOSTAND ( L A M B D A ( K _ I D E N T ) ( A C C E S S KONTOSTAND

(GET-KONTO

; ; ; ; Modi f i k a t o r e n (DEFINE-OPERATION SET-IDENTIFIZIERUNG! (LAMBDA ( K _ I D E N T I _ N E U ) (EVAL ' ( S E T ! I D E N T I F I Z I E R U N G ',I_NEU) (GET-KONTO K _ I D E N T ) ) ) )

K_IDENT))))

248

II. Konstruktionen (Analyse und Synthese)

( D E F I N E - O P E R A T I O N SET - NAME - 1 N H A B E R ! (LAMBDA ( K _ I D E N T N_NEU) (EVAL ' ( S E T ! NAME-INHABER \ N _ N E U )

(GET-KONTO

( D E F I NE-OPERAT ION S E T - A D R E S S E - I N H A B E R ! (LAMBDA ( K _ I D E N T A_NEU) (EVAL ' ( S E T ! ADRESSE-INHABER \ A _ N E U )

K_IDENT))))

(GET-KONTO

K_IDENT))))

(DEFINE-OPERATION SET-EINZAHLUNGEN! ( L A M B D A ( K_ I DENT E_ N EU ) (EVAL ' ( S E T ! EINZAHLUNGEN \ E _ N E U )

(GET-KONTO

K_IDENT))))

( D E F I N E - O P E R A T I O N SET-AUSZAHLUNGEN ! (LAMBDA ( K _ I D E N T A_NEU) ( E V A L ' ( S E T ! AUSZAHLUNGEN \ A _ N E U )

(GET-KONTO

K_IDENT))))

( D E F I NE-OPERAT ION SET-KONTOSTAND ! ( L A M B D A ( K_ I DENT K_ N E U ) ( E V A L ' ( S E T ! KONTOSTAND ' , K _ N E U )

(GET-KONTO

K_IDENT))))

(DEFINE-OPERATION ADD-EINZAHLUNG! (LAMBDA ( K _ I D E N T E.DATUM BETRAG) (COND ( ( A N D ( K O N T O ? K _ I D E N T ) (BUCHUNGSDATUM? E _ D A T U M ) (BETRAG? BETRAG)) (SET-EINZAHLUNGEN! K_IDENT (CONS ( L I S T E_DATUM B E T R A G ) (GET-EINZAHLUNGEN K _ I D E N T ) ) ) (SET-KONTOSTAND! K_IDENT ( + BETRAG ( G E T - K O N T O S T A N D K _ I D E N T ) ) ) ) ( T (ERROR " K e i n e E i n z a h l u n g h i n z u g e f u e g t ! " K _ I D E N T E_DATUM B E T R A G ) ) ) ) ) (DE F I NE-OPERAT ION ADD-AUSZAHLUNG! ( L A M B D A ( K _ I D E N T A_DATUM B E T R A G ) (COND ( ( A N D ( K O N T O ? K _ I D E N T ) (BUCHUNGSDATUM? A _ D A T U M ) (BETRAG? BETRAG)) (LET ((SALDO (GET-KONTOSTAND K _ I D E N T ) ) ) (COND ( ( < = BETRAG S A L D O ) (SET-AUSZAHLUNGEN! K_IDENT (CONS ( L I S T A_DATUM B E T R A G ) (GET-AUSZAHLUNGEN K _ I D E N T ) ) ) (SET-KONTOSTAND! K_IDENT ( - SALDO B E T R A G ) ) ) ( T (ERROR "Keine Deckung f u e r : " BETRAG))))) ( T (ERROR " K e i n e A u s z a h l u n g m o e g l i c h ! " K _ I D E N T A_DATUM B E T R A G ) ) ) ) ) ( D E F I N E - O P E RAT I O N N E U E R - I N H A B E R ! (LAMBDA ( K _ I D E N T I _ N E U ) (COND ( ( A N D ( K O N T O ? K _ I D E N T ) (NAME? I _ N E U ) ) (SET-NAME-INHABER! K_IDENT I _ N E U ) ) ( T (ERROR " K e i n e N a m e n s a e n d e r u n g v o l l z o g e n : K_IDENT I_NEU) ) ) ) )

"

249

8. Abbildungsoption: Funktion mit zugeordneter Umgebung (DEFINE-OPERATION NEUE-ADRESSE! (LAMBDA ( K _ I D E N T A_NEU) (COND ( ( A N D ( K O N T O ? K _ I D E N T ) (ADRESSE? A_NEU)) (SET-ADRESSE-INHABER! K_IDENT A_NEU)) (T ( E R R O R " K e i n e A d r e s s e n a e n d e r u n g v o l l z o g e n : K_ I DENT A_NEU ) ) ) ) )

"

( D E F I N E - O P E R A T I O N LOESCHE-KONTO! (LAMBDA ( K . I D E N T ) (COND ( ( K O N T O ? K _ I D E N T ) (UNBIND K_IDENT KONTEN-DATENBANK)) (T ( E R R O R " K e i n K o n t o : " K _ I D E N T ) ) ) ) ) ( D E F I N E - O P E R A T I O N A E N D E R E - KONTO - I D E N T I F I Z I E R U N G ! (LAMBDA ( K _ I D E N T _ A L T K _ I D E N T _ N E U ) (COND ( ( K O N T O ? K _ I D E N T _ N E U ) (ERROR "Neue Kontonummer i s t b e l e g t : " K_ I D E N T _ N E U ) ) ((KONTO? K_IDENT_ALT) (EVAL ' ( D E F I N E ,K_IDENT_NEU (SEND GET-KONTO ',K_IDENT_ALT)) KONTEN-DATENBANK) ( S E T - I D E N T I F I Z I E R U N G ! K_IDENT_NEU K_IDENT_NEU) (LOESCHE-KONTO! K_IDENT_ALT)) (T ( E R R O R " A l t e K o n t o n u m m e r e x i s t i e r t n i c h t : " K_ I D E N T _ A L T ) ) ) ) ) Programmfragment

8.4-1.

Verwaltung von Konten (Abbildungsbasis: Umgebungen)

Anhand einiger Beispielen verdeutlichen wir die Kontenverwaltung: eval> eval> eval> eval> eval> eval> eval> eval> eval> eval> eval>

(SEND MAKE-KONTO ' A O O l ) = = > A 0 0 1 (SEND MAKE-KONTO ' A 0 0 2 ) = = > A002 (SEND MAKE-KONTO ' A 0 0 3 ) = = > A003 (SEND NEUER-INHABER! 'AOOl "H. B o n i n " ) ==> "H. B o n i n " (SEND NEUE-ADRESSE! 'AOOl " V o l g e r s h a l l 1 D - 2 1 2 0 L u e n e b u r g " ) ==> " V o l g e r s h a l l 1 D-2120 Lueneburg" (SEND ADD-EINZAHLUNG! 'AOOl 891116 1 2 0 . 1 0 ) ==> 1 2 0 . 1 (SEND ADD-EINZAHLUNG! 'AOOl 891117 80.20) ==> 200.3 (SEND ADD-EINZAHLUNG! 'AOOl 891118 1 0 0 . 7 0 ) ==> 301. (SEND ADD-AUSZAHLUNG! 'AOOl 8 9 1 1 1 8 2 0 0 . 0 0 ) = = > 101. (SEND AENDERE-KONTO-1DENTIFI ZIERUNG! 'AOOl 'A007) ==> # (SEND DESCRIBE-KONTO 'A007 ) ==>

I n h a l t des K o n t o s : A007 ((ADRESSE-INHABER . " V o l g e r s h a l l (AUSZAHLUNGEN ( 8 9 1 1 1 8 2 0 0 . ) ) (EINZAHLUNGEN (891118 100.7) (891117 80.2) (891116 120.1)) (IDENTIFIZIERUNG . A007) (KONTOSTAND . 1 0 1 . ) (NAME-INHABER . "H. B o n i n " ) (TYP . KONTO))

1 D-2120

Lueneburg")

250 eval>

II. Konstruktionen (Analyse und Synthese) (SEND

DESCRIBE- OPERATIONEN)

==>

Operationen der Kontenverwaltung (ADD-AUSZAHLUNG! ADD-EINZAHLUNG! ADRESSE? AENDERE-KONTO-IDENTIFIZIERUNG! BETRAG? BÜCHUNGSDATUM? DESCRIBE-KONTEN DESCRIBE-KONTO DESCRIBE-KONTO-STRUKTUR DESCRIBE-OPERATIONEN GET-ADRESSE-INHABER GET-AUSZAHLUNGEN GET-EINZAHLUNGEN GET-KONTO GET-KONTOSTAND GET-NAME-INHABER KONTO? LOESCHE-KONTO! MAKE-KONTO MAKE-KONTO-STRUKTUR NAME? NEUE-ADRESSE! NEUER-INHABER! SET-ADRESSE-INHABER! SET-AUSZAHLUNGEN! SET-EINZAHLUNGEN! SET-IDENTIFIZIERUNG! SET-KONTOSTAND! SET-NAME-INHABER! TYP)

Die Kontenverwaltung zeigt, daß wir bei dem Verbund von Konstruktor, Selektor, Prädikat und Mutator für jedes Attribut eines Kontos entsprechende Konstrukte definieren müssen. Etwas salopp formuliert: Es wimmelt bald von GET--, SET-- und ?-Definitionen. Es bietet sich daher an, diese Konstrukte unmittelbar vom LISP-System definieren zu lassen. Eine solche Möglichkeit bietet das DEFINE-STRUCTURE-Konstrukt (vgl. Abschnitt 9).

8.5 Übersetzen von Funktionen Die Zeit, die ein Rechner benötigt, um eine LISP-Funktion auszuführen, ist häufig zu lang. Eine Steigerung der Effizienz bedingt eine Reduktion des Aufwandes für den Rechner. Der Ausführungsaufwand ist reduzierbar, indem der Rechner nicht die Interpretation der Funktion ausführt, sondern ihr compiliertes Äqui-

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

251

valent. Ein Übersetzer (engl.: Compiler) transformiert das in LISP formulierte Konstrukt in eine maschinennähere Form, so daß deren Ausführung denselben Effekt hat wie die Ausführung des ursprünglichen Konstruktes. Zu fordern ist von übersetzten Konstrukten, daß sie beliebig mit zu interpretierenden Konstrukten kombinierbar sind. Diese Anforderung an den Compiler steht in Konkurrenz zur Anforderung, möglichst effizienten Code zu erzeugen. Beispielsweise sind Referenzen zu Variablen abzubilden, die der Interpreter kennt, damit ein interpretiertes Konstrukt darauf Bezug nehmen kann. Zusätzlich ist bei die Fehlersuche hilfreich, wenn compilierte Konstrukte ein Stück zurückübersetzt (engl.: discompiliert) werden können. Erst ihre Rückübersetzung macht sie wieder durchschaubar. Zur Zeitmessung definieren wir ein Makro mit dem RUNTIME-Konstrukt (vgl. PC Scheme Manual). Das RUNTIME-Konstrukt gibt die Uhrzeit als Integerzahl zurück. eval>

(MACRO Z E I T M E S S U N G (LAMBDA ( S E X P R ) 'C LET ( ( A N F A N G ( R U N T I M E ) ) (ENDE 0 ) ) ,(CADR SEXPR) ( S E T ! ENDE ( R U N T I M E ) ) ;; F a l l s M i t t e r n a c h t d u r c h l a u f e n (COND ( ( < ENDE A N F A N G ) ( / ( + ( - ( * 24 60 60 1 0 0 ) ANFANG) ENDE) (T

(/

(-

100))

ENDE A N F A N G )

wird

100)))))) =->

ZEITMESSUNG

Zur Beurteilung der Effizienz hat Richard P. Gabriel Funktionen vorgeschlagen, die in der LISP-Welt allgemein Verwendung finden (vgl. Gabriel, 1985). Einige seiner rekursiven Meß-Funktionen basieren auf einem Vorschlag von Takeuchi. Ihm zu Ehren hat die folgende Funktion die Kurzbezeichnung TAK: eval>

eval>

( D E F I N E TAK ( L A M B D A (X Y Z ) ( I F (NOT ( < Y X ) ) Z (TAK (TAK ( - 1 + X) (TAK ( - 1 + Y) (TAK ( - 1 + Z) (TAK

18 12 6 )

Y Z) Z X) X Y)))))

==>

TAK

==> 7

Eine weitere Meß-Funktion von R.P. Gabriel generiert die ungeraden Zahlen in einem Intervall. Sie kann in Scheme, wie folgt, definiert werden:

252

II. Konstruktionen (Analyse und Synthese)

eval> (OEFINE GENERATOR-ZAHLEN-LISTE (LAMBDA (N) (DO ( ( I N (-1+ I ) ) (LISTE () (CONS I LISTE))) ((= I 0) LISTE)))) ==> GENERATOR-ZAHLEN-LISTE eval> (DEFINE FILTER-UNGERADE-POSITION (LAMBDA (L) (COND ((NULL? L) ( ) ) (T (CONS (CAR L) (FI LTER-UNGERADE-POSITION (CDDR L ) ) ) ) ) ) ) ==> FI LT ER-UNGERADE-POSITION eval> (FILTER-UNGERADE-POSITION (GENERATOR-ZAHLEN-LISTE 5000)) = = > ( 1 3 5 7 . . .

4999)

Unsere rechenintensive Funktion UMDREHEN (vgl. Abschnitt 3.4) ist ebenfalls geeignet für einen Leistungsvergleich. eva1> (DEFINE UMDREHEN (LAMBDA (X) (COND ((NULL? X) NIL) ((NULL? (CDR X)) X) (T (CONS (CAR (UMDREHEN (CDR X))) (UMDREHEN (CONS (CAR X) (UMDREHEN (CDR (UMDREHEN (CDR X ) ) ) ) ) ) ) ) ) ) ) ==> UMDREHEN Aufgrund der Abhängigkeit vom jeweiligen Rechner ist im folgenden bewußt auf eine Zeitangabe verzichtet worden. In PC Scheme (Version 3.03 für MS-DOS) bringt das Compilieren der Funktion UMDREHEN keine Verbesserung. In TLC-LISP (Version 1.51 für MS-DOS) konnte ein Verbesserungsfaktor von 1:4 gemessen werden. eval> (ZEITMESSUNG (UMDREHEN ' ( 0 1 2 3 4 5 6 7 8 9 ) ) ) ==> ? eval> (C0MPILE UMDREHEN) ==> (PCS-C0DE-BL0CK 1 4 (#) ( 1 4 0 59)) eval> (ZEITMESSUNG (UMDREHEN ' ( 0 1 2 3 4 5 6 7 8 9 ) ) ) ==> ?

8.6 Zusammenfassung: Closure, Continuation, Makros und Pakete Die Verknüpfung der Funktion mit ihrer Definitionsumgebung (Closure-Konzept) ermöglicht problemloses Konstruieren mit Funktionen höherer Ordnung. Eine Funktion kann an ihre eigene LAMBDA-Variable gebunden werden. Auf diese Weise entstehen selbstbezügliche Funktionen (ohne DEFINE- oder LETREC-Konstrukt). Das CALL/CC-Konstrukt bindet an die LAMBDA-Variable seiner applizierten Funktion, die noch nicht vollzogene Abarbeitung als sogenannte Continuation („Fortsetzung"). Da eine Continuation die vollzogenen Bindungen mit umfaßt, können Berechnung abgebrochen und später (auch mehrfach) wieder aufge-

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

253

nommen werden. Das CALL/CC-Konstrukt ist eine Konstrollstruktur, die Sprünge in die „Zukunft" und „Vergangenheit" einer Berechnung ermöglicht. Die Umgebung einer Funktion ist als ein lokaler Namensraum betrachtbar. Die hierarchische Anordnung von Umgebungen ist eine Technik zur Vermeidung von Namenskonflikten. Makros dienen zur Syntaxverbesserung. Zu unterscheiden sind „normale" Makros von READ-Makros. READ-Makros sind z.B das QUASIQUOTE (Zeichen: ' ), das QUOTE (Zeichen: ' ), das UNQUOTE (Zeichen: , ) und das UNQUOTE-SPLICING (Zeichen: @ ). Sie expandieren in der READ-Phase des READ-EVAL-PRINT-Zyklus. „Normale" Makros expandieren in der EVAL-Phase. Sie können in PC Scheme mit dem Konstrukt MACRO definiert werden. Einerseits ist das Makro ein mächtiges Konstruktionsmittel, andererseits verursacht das Makro im Programmieralltag häufig Fehler, weil der Expansionszeitpunkt und die Expansionsumgebung nicht beachtet werden. Charakteristische ; ; ; ; Funktion eval> (DEFINE eval> (DEFINE

Beispiele für Abschnitt 8: und Umgebung ( l e x i k a l i s c h e Bindung) ZAEHLER 5) ==> ZAEHLER F00 (LAMBDA () (SET! ZAEHLER (1+ ZAEHLER))))

eval> (LET ((ZAEHLER 0)) (F00) ZAEHLER) ==> 0 eval> ZAEHLER ==> 6

==> F00

eval> (DEFINE F00 (LAMBDA (FKT X Y) (LET ((+ (LAMBDA (X Y) (APPLY + (LIST X Y ) ) ) ) ) (LIST (+ (FKT X) (FKT Y)) (+ (FKT (FKT X)) (FKT (FKT Y ) ) ) ) ) ) ) ==> F00 eval> (DEFINE BAZ (LAMBDA (Z) (+ Z Z))) ==> BAZ eval> (F00 BAZ 1 2) ==> (6 12) eval> (LET ((FKT (LAMBDA (FUN X) (COND ((ZERO? X) (FUN)) (T (LAMBDA () X ) ) ) ) ) ) (FKT (FKT NIL 6) 0)) ==> 6 ;;;; ;;; ;;; ;;;

Klassisches LISP-Sprung-Paar: CATCH und THROW h i e r r e a l i s i e r t mit der allgemeinen K o n t r o l l s t r u k t u r CALL-WITH-CURRENT-CONTINUATION, kurz: CALL/CC CATCH s e t z t eine Marke; THROW s p r i n g t zur Marke.

eval> (DEFINE *CATCHER-C0NTINUATI0NS* ( ) ) ==> *CATCHER-CONTI NUAT IONS*

II. Konstruktionen (Analyse und Synthese)

254

eval>

(MACRO

CATCH

(LAMBDA

(SEXPR)

(LET ((-C-

(GENSYM)))

'(CALL/CC

(LAMBDA

( ,-C-)

(SET!

*CATCHER-CONTINUATIONS* (CONS (LIST

,(CADR SEXPR)

,-C-)

*CATCHER-CONTINUATIONS*)) (BEGINO ,(CONS

'BEGIN

(SET!

(CDR ))))) ==> eval>

(DEFINE THROW (LETREC

(LAMBDA

SEXPR))

*CATCHER-CONTINUATIONS*)))

CATCH

(MARKE

((MEMBER-RESTLISTE (COND

(CDDR

*CATCHER-CONTINUATIONS*

((NULL?

SEXPR) (LAMBDA

(KEY

C(EQ? KEY

(CAAR L)) L)

(T ( M E M B E R - R E S T L I S T E (CONTINUATIONS

L)

L) NIL) KEY

(CDR

L))))))

(MEMBER-RESTLISTE MARKE *CATCHER-CONTINUATIONS*))

(GET-C-

(LAMBDA

(COND ((NULL?

() (CADR (CAR

CONTINUATIONS)))))

CONTINUATIONS)

(ERROR "Keine C A T C H - M a r k e : (T (SET!

(CDR

CONTINUATIONS))

((GET-C-) SEXPR)))))) ==> eval>

(BEGIN

(+ 1 (CATCH

(CATCH

"1.

Schritt")

'BAR (WRITELN (THROW (CATCH

"2.

Schritt")

'FOO (+ 2 8)) 'BAZ (WRITELN (THROW 5)))

2.

11

Schritt

THROW

' FOO (WRITELN

1. Schritt

" MARKE))

*CATCHER-CONTINUATIONS*

"3.

Schritt")

'BAR 20))) ==>

8. Abbildungsoption: Funktion mit zugeordneter Umgebung

;;;; eval>

Sich

selbst

(DEFINE

(SEXPR)

(LIST

SEXPR

SEXPR))

(LAMBDA

(SEXPR)

(LIST

SEXPR

SEXPR))))

F00 = = >

eval>

(EVAL

(#

(EVAL

==> ;;;;

Konstrukt (MACRO

(EVAL

zur

F00) ) ) )

#)

Berechnung

der

Länge

einer

Liste

(SEXPR) ,(CADR

SEXPR)

(LAMBDA

(X)

(,(CADDR (DEFINE

(F

(LAMBDA (F

X)))))

==>

DE F I N E - F U N KT I ON

G) (X)

(G X ) ) ) ) )

(DEFINE

SEXPR)

COMPOSE

(LAMBDA

==>

COMPOSE

CONDITIONAL

(LAMBDA

(P

(LAMBDA

F G) (X)

eval>

(DEFINE

F-ADD

eval>

(DEFINE

ALPHA

(LAMBDA (MAP

(IF

(P

(LAMBDA

X)

(F

(X)

X)

(APPLY

(G X ) ) ) ) ) + X)))

==>

==>

CONDITIONAL

F-ADD

(F)

(LAMBDA

eval>

F00

DEFINE-FUNKTION

"(DEFINE

eval>

==>

#)

(EVAL

(#

(LAMBDA

eval>

Konstrukt

F00

((LAMBDA

eval>

eval>

reproduzierendes

255

(X) F X))))

(DEFINE-FUNKTION (CONDITIONAL

==>

ALPHA

F-LENGTH

NULL? (LAMBDA

(X)

(COMPOSE

0)

F-ADD

(ALPHA

(LAMBDA

(X) ==>

eval>

(F-LENGTH

'(A

B C D )

==>

4

1))))) F-LENGTH

256

II. Konstruktionen (Analyse und Synthese)

;;;

Das

;;;

entspricht

eval>

expandierte

(DEFINE

Makro

DEFINE-FUNKTION

folgendenem

Konstrukt:

F-LENGTH

(LAMBDA

(X)

((CONDITIONAL NULL? (LAMBDA

(X)

0) (COMPOSE X))) Ersetzt

==>

man d i e

F-ADD,

das

F-LENGTH-Konstrukt:

(DEFINE

(ALPHA

Konstrukte

und eval>

dann

F-ADD

(LAMBDA

(X)

1))))

F-LENGTH

ergibt

CONDITIONAL,

sich

folgende

COMPOSE,

Definition

ALPHA für

F-LENGTH

(LAMBDA

(X)

(((LAMBDA

(P

F G)

(LAMBDA

(X)

(IF

X)

(P

(F

X)

(G

X))))

(X)

1)

X))))

NULL? (LAMBDA

(X)

0) ((LAMBDA

(F

(LAMBDA (F

(G

(LAMBDA (APPLY (MAP

;;;; eval>

==>

Konstruktion (DEFINE (LET

X))))

(X)

(LAMBDA X)))

G) (X)

+

X))

(X) (LAMBDA F-LENGTH

einer

Umgebung,

die

sich

selbst

enthält,

MEINE-UMGEBUNG ((SELBST (X

NIL)

13))

(THE-ENVIRONMENT))) eval>

(ENVIRONMENT-BINDINGS

eval>

(EVAL

eval>

(ENVIRONMENT-BINDINGS

eval>

(ACCESS

eval>

(SET!

==>

MEINE-UMGEBUNG

MEINE-UMGEBUNG) ==>

'(DEFINE

Y 6)

MEINE-UMGEBUNG)

(ACCESS

MEINE-UMGEBUNG) SELBST

(X

.

13))

(X

.

12))

MEINE-UMGEBUNG) ==>

SELBST

((SELBST)

==> Y

((Y ==>

. 6)

(SELBST)

NIL

MEINE-UMGEBUNG)

MEINE-UMGEBUNG) ==>

NIL

9. Generierte Zugriffskonstrukte

eval>

(ENVIRONMENT-BINDINGS ==>

eval> ;;;;

257

(ACCESS

((Y

X (ACCESS

MEINE-UMGEBUNG)

. 6)

(SELBST

SELBST

. #)

MEINE-UMGEBUNG))

==>

(X

.

13))

13

Makro/Funktions-Gegenüberstellung

eval>

(DEFINE

F00 6 )

eval>

(DEFINE

CALL-BY-VALUE-DEMO

(LAMBDA

6

(X)

(SET! (-

==>

X (*

X

X FOO)))

X)) ==>

eval>

(CALL-BY-VALUE-DEMO

eval>

FOO = = >

CALL-BY-VALUE-DEMO FOO) = = >

30

6

e v a l X MACRO C A L L - B Y - R E F E R E N C E - D E M O (LAMBDA

(SEXPR)

'(BEGIN (SET! (-

,(CADR

,(CADR

eval>

(DEFINE

eval>

(EXPAND-MACRO

BAZ

6)

SEXPR)

SEXPR) ==>

(BEGIN

(CALL-BY-REFERENCE-DEMO

eval>

BAZ = = >

eval>

(CALL-BY-REFERENCE-DEMO FOO = = >

==>

SEXPR)

,

(CADR

SEXPR)))

CALL-BY-REFERENCE-DEMO

6

eval>

eva1>

,(CADR

'(CALL-BY-REFERENCE-DEMO ==>

==>

(*

FOO))))

(SET! BAZ)

BAZ ==>

(*

BAZ))

BAZ

BAZ))

(-

BAZ

FOO))

30

36 0

;Name d e r

FOO)

freien

Variablen

benutzt

!

36

9. Generierte Zugriffskonstrukte Dieser Abschnitt behandelt das automatische Generieren von Zugriffskonstrukten. Für die Liste, die A-Liste oder die P-Liste, konnten wir die primitiven, eingebauten Konstruktoren, Selektoren, Prädikate und Mutatoren nutzen, um damit unseren anwendungsspezifischen Verbund zu konstruieren. War auf ein bestimmtes Feld einer A-Liste zuzugreifen z.B. auf das Feld NAME-DES-KONTOINHABERS, dann konnte mit dem ASSQ-Konstrukt eine entsprechenden Selektor, wie folgt, definiert werden (vgl. Abschnitt 5.2).

258

II. Konstruktionen (Analyse und Synthese)

eval> (DEFINE NAME-DES-KONTOINHABERS (LAMBDA (A_LISTE) (ASSQ 'NAME-DES-KONTO INHABE RS A.LISTE))) ==> NAME-DES-KONTOINHABERS Neben den Selektoren sind die Prädikate und Mutatoren ebenfalls aus den eingebauten Grundfunktionen selbst zu konstruieren. Solche Definitionen können direkt vom LISP-System generiert werden. Zum Zeitpunkt der Definition einer Datenstruktur sind unter Angabe einiger Steuerungsparameter die Konstrukte des Verbundes generierbar. Dazu dient in LISP-Sytemen das DEFINE-STRUCTURE-Konstrukt (Abschnitt 9.2). Vorab betrachten wir das Konzept der „passiven" Daten primär aus der Sicht „aktiver" Zugriffskonstrukte. Erörtert wird das Konzept abstrakter Datentypen (Abschnitt 9.1). Wesentlicher Vorteil ist die Rückgriffsmöglichkeit auf existierende Datentypen bei der Definition neuer Datentypen. Es werden Eigenschaften von existierenden Datentypen auf neue „vererbt". Wir zeigen die einfache statischen Vererbung (Abschnitt 9.3). Die multiple dynamische Vererbung wird im Zusammenhang mit Klassen-Instanz-Konzepten behandelt (Abschnitt 10.2).

9.1 Abschirmung von Weiten Definieren wir ein Konstrukt, dann bemühen wir uns mit den gewählten Symbolen und Strukturen eine gedankliche Verknüpfung zwischen realen und formalen „Einheiten" (Objekten) herzustellen. Entsprechend dem jeweiligen Denkrahmen (Paradigma, vgl. Abschnitt 4) und den Möglichkeiten seiner Umsetzung betonen wir eine „passive" (daten-/wert-bezogene) oder „aktive" (operations-bezogene) Sicht (vgl. Abschnitt 2). Lösen wir eine kleine Programmieraufgabe wie das Verwalten einigen Postanschriften, dann werden wir im imperativgeprägten Denkrahmen frühzeitig die benötigten Speicherplätze benennen und ordnen. Wir definieren eine passive Datenstruktur. Eine solche Datenstruktur zeigt Bild 9.1-1. Sie ist in COBOL, einer der Programmiersprache für das imperativgeprägte Paradigma, formuliert. Wir haben früh Angaben über die Wertbereiche der einzelnen Felder bereitgestellt. Aufgrund der PICTURE-Angabe ist die Variable ZUNAME maximal 30 Zeichen lang, während die POSTLEITZAHL eine Zahl mit 4 Ziffern ist. Der Wertebereich eines Feldes ist hier mit der PICTURE-Klausel durch eine Längenangabe und einen Verweis auf einen eingebauten Datentyp definiert. Mit den Namen dieser Datenstruktur werden Schritt für Schritt benötigte Operationen durchdacht und spezifiziert. Eine Operation DRUCKE-ADRESSAUFKLEBER enthält dann beispielsweise eine Formulierung: „BILDEDRUCKZEILE aus TITEL, VORNAME und ZUNAME. Falls die Zeilenlänge gößer als 25 Zeichen ist, dann nimm vom Feld TITEL die ersten drei Zei-

9. Generierte Zugriffskonstrukte

259

01 ADRESSE. 05 NAME. 10 ZUNAME 10 VORNAME 10 T I T E L 0 5 WOHNORT. 10 S T R A S S E 10 P O S T L E I T Z A H L 10 ORT 05 KOMMUNIKATION. 10 T E L E F O N 10 T E L E F A X 10 E - M A I L 05 S O N S T I G E S . 10 STOCKWERK 10 POSTFACH

PICTURE PICTURE PICTURE

X(30 ). X(30 ). X(10).

PICTURE X(20 ). PICTURE 9 ( 4 ) . PICTURE X(30 ). PICTURE PICTURE PICTURE

X(15). X(15). X ( 15 ) .

PICTURE PICTURE

99. 9(5).

Legende: 01

. . . 10

PICTURE PICTURE Bild

: : = Gruppennummern

zur S t r u k t u r i e r u n g

X(30)

::=

Feld

f ü r 30 a l p h a n u m e r i s c h e

9(4)

::=

Feld

für eine

9 . 1 - 1.

Zahl

mit 4

Zeichen

Ziffern.

COBOL-Datendefinition

chen ...." Jetzt wird erkannt, daß man eine Operation KURZ-TITEL, die vom Feld TITEL die ersten drei Zeichen selektiert, benötigt. Im Kontext „passive" Datenstruktur bietet sich eine Änderung der TITEL-Definition, wie folgt, an: 10

TITEL. 15 K U R Z T I T E L 15 R E S T T I T E L

PICTURE PICTURE

X(3). X(7).

Mit dieser modifizierten TITEL-Definition kann man TITEL oder KURZTITEL drucken. Als Alternative wäre die Definition einer Operation KURZTITEL möglich, die im COBOL-Programm auf einer MOVE-Operation (assign Statement) beruhen würde. Im funktionsgeprägten Denkrahmen benennen wir frühzeitig Operation. Ihr schrittweises Durchdenken und Spezifizieren führt schließlich ebenfalls zu den abzubildenden „passiven" Einheiten (Daten). Wir hätten beispielsweise eine Funktion ADRESS-AUFKLEBER mit einem Argument ADRESSE definiert. Beim Konstruieren des Nebeneffektes WRITELN benötigen wir die Operationen GET-TITEL und GET-KURZTITEL. Auch jetzt können wir wieder für GET-KURZTITEL eine operationale Sicht umsetzten, d.h. in LISP ein LAMBDA-Konstrukt wählen. Dieses enthält die Operationen GET-TITEL und eine Verkürzungsoperation. Diese ist abhängig vom Typ des Wertes, den GETTITEL liefert. Ist es ein String, dann käme zum Verkürzen das SUBSTRING-

260

II. Konstruktionen (Analyse und Synthese)

Konstrukt in Betracht (vgl. Abschnitt 7.2). Ist es eine Symbol, wäre ein EXPLODE-Konstrukt notwendig (vgl. Abschnitt 7.3). Im imperativgeprägten Denkrahmen wurden frühzeitig die Wertebereiche der Felder beschrieben. Wir haben in den Kategorien wie Länge und primitiven Beschreibungstyp der Wertebereichsspezifikation gedacht. Holzschnittartig formuliert: Die Speicherplatzkonditionen prägen diese Denkwelt. Ausgangspunkt für die Problemdurchdringung ist die Frage: Wie werden die anwendungsspezifischen Daten repräsentiert? Im funktionsgeprägten Denkrahmen kannten wir zum Zeitpunkt der Operation ADRESS-AUFKLEBER weder die Länge des Feldes noch mußte schon der Wertebereich mit eingebauten bzw. bekannten Datentypen präzisiert werden. Erst mit der Definition von GET-KURZTITEL sind Angaben über den Wertebereich entsprechend zu präzisieren. Mit einem Denken in der Kategorie Zugriffskonstrukt verschieben wir eine solche, implementationsabhängige Beschreibung. Wir können zunächst davon abstrahieren, ob die Daten z.B. vom Typ STRING, VECTOR oder SYMBOL sind. Wir durchdenken und spezifizieren unsere Konstruktion in der Kategorie Zugriffskonstrukte. Ausgangspunkt für die Problemdurchdringung ist die Frage: Welche anwendungsspezifischen Operationen sind mit Daten notwendig? Beide Ansätze sind voneinander abhängig. Immer dann, wenn ein Problem wenig durchschaut wird, ist ein Wechselspiel zwischen beiden Ansätzen zur Korrektur der ersten Lösungsentwürfen erforderlich. Änderungen der „passiven" Datenstruktur werden durch Änderungen der „aktiven" Konstrukte (Operationen) hervorgerufen und umgekehrt. Die Definition von Zugriffskonstrukten bildet dabei eine Schnittstelle zwischen der Implementations- und der Verwendungs-Perspektive. Mit den Zugriffskonstrukten versuchen wir die Implementations-Anforderungen (Wertebeschreibungen in den Kategorien der eingebauten Konstrukte) von den Verwendungs-Anforderungen (anwendungsspezifischen Operationen) abzuschirmen. In LISP kann diese Schnittstelle „verheimlichen", ob nur auf die Wertzelle eines Symbols zugegriffen oder ein LAMBDA-Konstrukt appliziert wird. Im ersten Fall haben wir es mit dem Zugriff auf ein „passives" Datum zu tun. Im zweiten Fall geht es um eine Funktionsanwendung, d.h. um ein „aktives" Datum. Beide Arten von Zugriffskonstrukten sind mit der Datenstruktur-Definition automatisch generierbar. Das DEFINE-STRUCTURE-Konstrukt erzeugt „passive" Zugriffskonstrukte (Abschnitt 9.2). Das Generieren von „aktiven" Zugriffskonstrukten („aktiven Slots") ist Bestandteil des Klassen-Instanz-Konzeptes. Wir behandeln es daher nach SCOOPS (Abschnitt 10.3.). Zum Verständnis des DEFINE-STRUCTURE-Konstruktes betrachten wir erneut unsere Konten Verwaltung (Abschnitt 8.4). Dabei konstruieren wir die neue Struktur VERWAHRKONTO aus der definierten Struktur KONTO. Verdeut-

261

9. Generierte Zugriffskonstrukte

licht wird damit der „Vererbungsmechanismus" des DEFINE-STRUCTUREKonstruktes (vgl. Abschnitt 9.3).

9.2 DEFINE-STRUCTURE-Konstrukt Mit dem DEFINE-STRUCTURE-Konstrukt (auch DEFSTRUCT genannt, z.B. in Common LISP) definieren wir eine (Datensatz-)Struktur aus Feldern (engl.: slots) mit gegebenenfalls voreingestellten Werten (engl.: default values) gemeinsam mit ihren Selektoren, Prädikaten und Mutatoren. Das DEFINE-STRUCTURE-Konstrukt ermöglicht den Zugriff auf bereits definierte Struturen. Neue Strukturen lassen sich aus bestehenden zusammenbauen. Vorteilhaft ist dabei, daß jeweils die Operationen für das Zugreifen und Ändern der Feldwerte unmittelbar vom LISP-System bereitgestellt werden. Sie müssen nicht erst selbst definiert werden. Das DEFINE-STRUCTURE-Konstrukt hat ohne Vererbungsoption die folgende Syntax. Die Vererbungsoption behandeln wir anschließend (vgl. Abschnit 9.3). eval>

(DEFINE-STRUCTURE

{}) ==> < s t r u c t u r e - n a m e >

mit:



::= Ein Symbol, das die Struktur

benennt.

::= ( | < d e f a u l t - v a l u e > ) ::= Ein Symbol, das das Feld

benennt.

::= Ein symbolischer A u s d r u c k , dessen Evaluierung den v o r e i n g e s t e l l t e n Wert für das Feld ergibt.

Als Nebeneffekt werden bei der Applikation des DEFINE-STRUCTUREKonstruktes folgende Operatoren generiert: o Ein Konstruktor: MAKE-, o ein Prädikat:

?,

o für jedes Feld einen Selektor: -

und

o für jedes Feld einen Mutator, d.h. die Möglichkeit seinen Wert mit Hilfe des SET!-Konstruktes zu ändern. (SET!

(-

)

;

/Hinweis: DEFINE-STRUCTURE-Makro. In PC Scheme ist das DEFINESTRUCTURE-Konstrukt als Makro implementiert. Seine „interne" Arbeitswei-

262

II. Konstruktionen (Analyse und Synthese)

se zeigt der expandierte Wert dieses Makros. Es basiert auf den Abbildungsoptionen Vektor und Eigenschaftsliste. eval> (EXPAND-MACRO ' ( D E F I N E - S T R U C T U R E F00 (A 1) (B 2))) = = > (BEGIN (PUTPROP 'F00 '((|#!STRUCTURE| . F00)) '%TAG) (PUTPROP 'FOO * ((B 2 . 2) (A 1 . 1)) '7.SL0T-VALUES) ( D E F I N E - I N T E G R A B L E FOO-B (LAMBDA (OBJ) ( V E C T O R - R E F O B J 2 ) ) ) ( D E F I N E - I N T E G R A B L E FOO-A (LAMBDA (OBJ) ( V E C T O R - R E F O B J 1))) (DEFINE F00? (LAMBDA (OBJ) ( % S T R U C T U R E - P R E D I C A T E O B J '(!#1 S T R U C T U R E I . F00)))) (DEFINE M A K E - F 0 0 (LAMBDA %DS0002% (LET ((%DS0001% (MAKE-VECTOR 3 '()))) (VECTOR-SET! %DS0001% 0 (GETPROP 'F00 '%TAG)) (VECTOR-SET! %DS0001% 1 1) (VECTOR-SET! %DS0001% 2 2) (IF (NULL? % D S 0 0 0 2 % ) %DS0001% ( % M A K E - S T R U C T U R E 'FOO 'MAKE-FOO %DS0001% %DS0002%))))) •FOO) Das %STRUCTURE-PREDICATE-Konstrukt ist eine LISP-Funktion, gebunden in USER-GLOBAL-ENVIRONMENT. Das DEFINE-INTEGRABLEKonstrukt ist ein Makro, das zu folgendem Ausdruck expandiert: eval> (EXPAND-MACRO

' ( D E F I N E - I N T E G R A B L E FOO-A (LAMBDA (OBJ) ( V E C T O R - R E F O B J 1))) = = >

(BEGIN (PUTPROP 'FOO-A (CONS ' D E F I N E - I N T E G R A B L E '(LAMBDA (OBJ) ( V E C T O R - R E F O B J 1))) 'PCS*PRIMOP-HANDLER) 'FOO-A)

Die obige Eigenschaft PCS*PRIMOP-HANDLER wirkt bei der Auswertung wie eine Bindung von FOO-A an (LAMBDA (OBJ) (VECTOR-REF OBJ 1)); vgl. Abschnitt 5.3, Hinweis PCS*PRIMOP-HANDLER)J Für unsere Kontenverwaltung (vgl. Abschnit 8.4) formulieren wir folgende Struktur: eval> ( D E F I N E - S T R U C T U R E KONTO ( I D E N T I F I Z I E R U N G 0) (NAME-INHABER NIL) ( A D R E S S E - I N H A B E R NIL) (EINZAHLUNGEN N I L ) (AUSZAHLUNGEN N I L ) (KONTOSTAND 0)) = = > KONTO

9. Generierte Zugriffskonstrukte

263

Konstruktor:

Mit Hilfe des Konstruktors MAKE-KONTO legen wir die beiden Konten A001 und A002 an. Die generierte MAKE-KONTO-Funktion kann die voreingestellten Werte überschreiben. Dazu sind und als Argumente anzugeben. eval> (DEFINE A001 (MAKE-KONTO)) ==> A001 eval> (DEFINE A002 (MAKE-KONTO •IDENTIFIZIERUNG 'A002 'NAME-INHABER "H. Bonin" 'ADRESSE-INHABER "D-2120 Lueneburg")) ==> A002 Das MAKE-KONTO-Konstrukt erzeugt in Scheme einen Vektor. Das erste Vektorfeld speichert entsprechend einer A-Liste den Namen der Struktur. eval> A001 ==> #(((!#!STRUCTURE¡ . KONTO)) 0 O O O () 0) eval> A002 ==> #(((!#!STRUCTURE¡ . KONTO)) A002 "H. Bonin" "D-2120 Lueneburg" O () 0) Prädikat:

Das generierte Prädikat KONTO? erkennt Objekte, die mit dem MAKEKONTO-Konstrukt erzeugt wurden. eval> (KONTO? A001) ==> #T eval> (DEFINE F00 (VECTOR 'IDENTIFIZIERUNG 'NAME -1NHABER)) ==> F00 eval> (KONTO? F00) ==> () eval> (KONTO? #(((ÜSTRUCTURE¡ . KONTO)) A002 O () 0)) ==> #T ¡Achtung! Umgehen des MAKE-Konstruktes Selektor:

Für den Zugriff auf die einzelnen Felder hat das DEFINE-STRUCTUREKonstrukt Selektoren generiert. Ihr Name setzt sich aus dem Präfix Strukturnamen, gefolgt von einem Bindestrich und dem Feldnamen, zusammen. eval> (KONTO-ADRESSE-INHABER A002) ==> "D-2120 Lueneburg" eval> (KONTO-KONTOSTAND A001) ==> 0 Diese generierten Selektoren benutzen wir zum Definieren der Funktion DESCRIBE-KONTO. Sie stellt die aktuellen Werte eines Kontos dar. eval> (DEFINE DESCRIBE - KONTO (LAMBDA (K_IDENT) (COND ((KONTO? K_IDENT) (NEWLINE) (WRITELN " Inhalt des Kontos: " (KONTO-IDENTIFIZIERUNG K_IDENT)

264

II. Konstruktionen (Analyse und Synthese) (WRITELN "Name-Inhaber: ( K O N T O - N A M E - I N H A B E R K_IDENT)) (WRITELN "Adresse-Inhaber: " ( K O N T O - A D R E S S E - I N H A B E R K_IDENT)) (WRITELN "Einzahlungen: ") (PP ( K O N T O - E I N Z A H L U N G E N K_IDENT)) (NEWLINE) (WRITELN "Auszahlungen: ") (PP ( K O N T O - A U S Z A H L U N G E N K_IDENT) ) (NEWLINE) (WRITELN "Kontostand: ( K O N T O - K O N T O S T A N D K_IDENT)) *THE-NON-PRINTING-OBJECT*) (T (ERROR "Kein Konto: " K _ I D E N T ) ) ) ) ) ==> D E S C R I B E - K O N T O

Die folgenden Konstrukte zum Buchen von Ein- und Auszahlungen unterscheiden sich gegenüber der Lösung in Abschnitt 8.4 nur durch die generierten Selektoren und die zugehörenden Mutatoren auf der Basis des SET!-Konstruktes. Sie bedürfen daher keiner weiteren Erläuterung. eval> (DEFINE BETRAG? (LAMBDA (X) (AND (NUMBER? X) (>= X 0)))) = = > BETRAG? eva1> (DEFINE B U C H U N G S D A T U M ? (LAMBDA (X) (AND (NUMBER? X) (>= X 8 9 0 1 0 1 ) ) ) ) ==> B U C H U N G S D A T U M ? eval> (DEFINE ADD - EINZAHLUNGi (LAMBDA (K_ID E N T E_DATUM BETRAG) (COND ((AND (KONTO? K_IDENT) ( B U C H U N G S D A T U M ? E.DATUM) (BETRAG? BETRAG)) (SET! ( K O N T O - E I N Z A H L U N G E N K_IDENT) (CONS (LIST E.DATUM BETRAG) ( K O N T O - E I N Z A H L U N G E N K_IDENT))) (SET! ( K O N T O - K O N T O S T A N D K_IDENT) (+ BETRAG ( K 0 N T 0 - K 0 N T 0 S T A N D K_IDENT))) ( K 0 N T 0 - K 0 N T 0 S T A N D K_IDENT)) (T (ERROR "Keine Einzahlung h i n z u g e f u e g t ! " K_IDENT E.DATUM B E T R A G ) ) ) ) ) = = > A D D - E I N Z A H L U N G ! eval> (DEFINE A D D - A U S Z A H L U N G ! (LAMBDA (K_IDENT A_DATUM BETRAG) (COND ((AND (KONTO? K_IDENT) (BUCHUNGSDATUM? A_DATUM) (BETRAG? BETRAG)) (COND (( A D D - A U S Z A H L U N G !

9. Generierte Zugriffskonstrukte eval>

265

(DEFINE A E N D E R E - K O N T O - 1 D E N T I F I Z I E R U N G ! (LAMBDA (K_IDENT_ALT K_IDENT_NEU) (COND ((KONTO? (EVAL '(ACCESS , K_ID E N T_ N E U USER-INITIAL-ENVIRONMENT))) (ERROR "Neue Kontonummer ist belegt: " K_IOENT_NEU)) ((KONTO? (EVAL '(ACCESS ,K_IDENT_ALT USER-INITIAL-ENVIRONMENT))) (EVAL "(DEFINE .K_IDENT_NEU (EVAL ,K_IDENT_ALT))) (EVAL '(SET! ( K O N T O - I D E N T I F I Z I E R U N G ,K_IDENT_NEU) ' , K_ I DENT_NEU )) (EVAL '(UNBIND ',K_IDENT_ALT USER-INITIAL-ENVIRONMENT))) (T (ERROR "Alte Kontonummer e x i s t i e r t nicht: " K_IDENT_ALT))))) ==> A E N D E R E - K O N T O - I D E N T I F I Z I E R U N G

Wir nehmen an, daß neben den Konten der bisherigen Kontenstruktur andere Konten mit den beiden zusätzlichen Eigenschaften BUCHUNGSGRUND und BEARBEITER benötigt werden. Ein solches erweitertes Konto sei z.B. ein Verwahrkonto, d.h. ein Konto auf dem dubiose Vorgänge zwischengebucht werden. Zur Definition der Struktur VERWAHRKONTO sind nicht alle Felder (slots) neu zu beschreiben. Die Struktur VERWAHRKONTO kann auf der definierten Struktur KONTO aufbauen. Oder aus der entgegengesetzten Blickrichtung formuliert: Die Struktur VERWAHRKONTO erbt die Felder der Struktur KONTO.

9.3 Einfache statische Vererbung Vererbung ist die Übertragung von körperlichen und seelischen Merkmalen der Eltern auf die Nachkommen. Angelehnt an diese biologische Betrachtung wäre für LISP-Systeme die „Genetik" von der "Klassifikation" zu unterscheiden. Genetische Vererbung führt nicht zur Weitergabe jeder Eigenschaft eines Objektes. Holzschnittartig formulierte: „Wenn die Mutter eine schmale Nase hat, kann die Tochter eine dicke haben." Die Klassifikation entspricht der Wiederanwendbarkeit einer Beschreibung beim Nachkommen (zur mathematischen Betrachtung der Vererbung vgl. z.B. Touretzky, 1986). Außerdem ist mit dem Begriff Vererbung noch ein Modus "letzter Wille/Testament" assoziierbar. Dieser steht für (fast) völlig freies Weitergeben. Der Mechanismus des DEFINE-STRUCTURE-Konstruktes vererbt alle Felder mit ihren voreingestellten Werten. Er hat folgende Syntax: eval>

(DEFINE-STRUCTURE (

(INCLUDE < p a r e n t - n a m e > ) ) { KLASSE-I eval> (KLASSE-I-SLOT-3 F00) ==> OTTO ¡Achtung! FunKtioniertin PC Scheme ; Version 3.03 n i c h t .

Für das Propagieren einer solchen Änderung bedarf es der dynamischen Vererbung. Zunächst erörtern wir den Aspekt „multiple Vererbung" (Abschnitt 10.1) und dann den Aspekt „dynamische Vererbung" (Abschnitt 10.2).

9.4 Zusammenfassung: Zugriffskonstrukte als Schnittstelle Das DEFINE-STRUCTURE-Konstrukt generiert den Verbund von Konstruktor, Selektor, Prädikat und Mutator. Der Konstruktor MAKE- bildet ein Objekt mit den Feldern (engl.: slots) und initialisiert diese mit voreingestellten Werten (engl.: default values). Ein generierter Selektor besteht aus dem Strukturnamen und dem Feldnamen, verbunden durch einen Bindestrich. Das Prädikat hat als Namen den Strukturnamen ergänzt um ein Fragezeichen am Namensende. Ein Feldinhalt einer mit MAKE- gebildeten Struktur ist mit dem SET!-Konstrukt modifizierbar. Das erste Argument des SET!-Konstruktes ist die Applikation des entsprechenden Sclcktors Das DEFINE-STRUCTURE-Konstrukt ermöglicht mit der INCLUDE-Angabe eine einfache statische Vererbung. Eine Struktur kann auf einer vorher definierten Struktur aufbauen.

II. Konstruktionen (Analyse und Synthese)

270

Charakteristisches ;;;; ;;;; ;;;;

Beispiel für Abschnitt 9:

E i n f a c h e r Bestimmungs-Baum ( e n g l . : d i s c r i m i n a t i o n t r e e ) ( N ä h e r e s z u r K o n s t r u k t i o n von "discrimination nets" v g l . z.B. Charniak u . a . , 1987)

eval>

(DEFINE-STRUCTURE

BAUM-BLATT

eval>

(DEFINE-STRUCTURE

KNOTEN T E S T

eval>

(DEFINE (COND

BESTIMMUNG

(LAMBOA

((BAUM-BLATT? ((KNOTEN?

WERT) JA

(SEXPR

BAUM)

==>

NEIN)

BAUM-BLATT ==>

KNOTEN

BAUM)

(BAUM-BLATT-WERT

BAUM))

BAUM)

(BESTIMMUNG SEXPR (NAECHSTER-KNOTEN ((KNOTEN-TEST

BAUM)

SEXPR)

BAUM))) (T eval>

(DEFINE (IF

NIL))))

==>

NAECHSTER-KNOTEN

(LAMBDA

(TEST-ERGEBNIS

BAUM)

TEST-ERGEBNIS (KNOTEN-JA

BAUM)

(KNOTEN-NEIN eval>

BESTIMMUNG

(DEFINE

BAUM))))

==>

NAECHSTER-KNOTEN

*LISP-TYPEN*

(MAKE-KNOTEN 'TEST •JA

VECTOR?

(MAKE-BAUM-BLATT

'WERT

"Vektor")

'NEIN (MAKE-KNOTEN 'TEST 'JA

PAIR?

(MAKE-BAUM-BLATT

'WERT

"Paar")

'NEIN (MAKE-KNOTEN 'TEST 'JA

SYMBOL?

(MAKE-BAUM-BLATT

'WERT

"Symbol")

'NEIN (MAKE-KNOTEN 'TEST 'JA

NUMBER?

(MAKE-BAUM-BLATT

'WERT

"Zahl")

'NEIN (MAKE-BAUM-BLATT

eval>

(BESTIMMUNG

))))

==

'FOO

* L I SP-TY PEN*)

'WERT

"Unbekannt"))

*LISP-TYPEN* ==>

"Symbol"

10. Klasse-Instanz-Modell

271

10. Klasse-Instanz-Modell Die Realisierung von Klassen-Instanz-Modellen hat in LISP eine lange Tradition. Als charakteristische Vertreter gelten die beiden Systeme „Common LOOPS" und „Flavors" (vgl. Bonin, 1989): o CommonLOOPS (Common LISP Object-Oriented Programming System, vgl. Bobrow u.a., 1986) CommonLOOPS, implementiert in Common LISP, realisiert die Objektorientierung mit generischen Funktionen, so daß normale LISP-Funktionen und Methoden syntaktisch einheitlich gehandhabt werden können. Meta-Objekte sind eine charakteristische CommonLOOPS-Eigenschaft. o (New) Flavors (vgl. Weinreb/Moon, 1981; Moon, 1986) Das Flavor-System wurde ursprünglich für die LISP-Maschine entwickelt (vgl. Anhang B). Im Mittelpunkt steht ein „Mixen" von Teilkomponenten durch multiple Vererbung. Dieser „mixin style" begründet den außergewöhnlichen Namen Flavor. Durch das Mischen von verschiedenen Flavors erhält man einen neuen Flavor. Dabei schmeckt man beim Zusammenmischen die einzelnen Komponenten bis hin zur Systemwurzel „Vanilla-flavor". Selbst kleine PC-orientierte LISP-Implementationen ermöglichen den Umgang mit Klassen und Instanzen. Ohne großen Aufwand kann ein LISP-System für eine objektgepräget Programmierung erweitert werden (vgl. z.B. DiPrimo/Christaller, 1983; Beckstein, 1985, Brewka u.a., 1987; Kessler, 1988 oder FISCH, 1988). Entstanden ist daher eine Vielfalt von LISP-Erweiterungen. Sie weisen wesentliche Unterschiede in ihren Konstrukten und in ihrer Terminologie auf. Begriffe wie Objekt, Klasse, Instanz, Methode oder Funktion werden unterschiedlich gehandhabt. Diese Sprach- und Konstrukte-Vielfalt war Veranlassung einen leistungsfähigen Standard zu schaffen. Die amerikanischen Standardisierungsbemühungen haben zum „Common LISP Object System", kurz CLOS, geführt (vgl. ANSI X3J13; Bobrow/Moon u.a., 1988). In Bild 10-1 sind die CLOS-Bausteine genannt. Die CLOS-Charakteristika zeigt Bild 10-2. Mit dem DEFINE-STRUCTURE-Konstrukt ist eine „Klasse" (Struktur) auf der Basis einer existierenden Klasse definierbar (vgl. Abschnitt 9.3). Vererbt werden dabei die „slots" einer Klasse. Ein wesentliches Leistungsmerkmal eines Klassen-Instanz-Modells ist die multiple Vererbung, d.h. die Möglichkeit neue Klassen durch Rückgriff auf die Eigenschaften von mehreren Klassen zu definieren (vgl. Bild 10-2). Ein weiteres Beurteilungskriterium ist der Zeitpunkt der Vererbung. Hierbei ist zwischen statischer und dynamischer Vererbung zu unterscheiden. Die multiple Vererbung verdeutlichen wir anhand der Erweiterung unsere Kontenverwaltung (vgl. Abschnitte 8.4 und 9.2). Die Klasse VERWAHRKONTO erbt zusätzlich zu den Eigenschaften der Klasse KONTO die Eigenschaften einer Klasse OFFENER-VORGANG (vgl. Bild 10-3). Die Klasse OFFENER-

II. Konstruktionen (Analyse und Synthese)

272

Instanz

]

>

Eine K l a s s e b e s t i m m t die Struktur und das V e r h a l t e n einer M e n g e v o n O b j e k t e n , d i e Instanzen genannt werden.

Generi sehe F u n k t i on Methode

]

>

Eine M e t h o d e d e f i n i e r t die klassenspezifische Operation einer generischen Funktion ("Gattungsfunktion")

Kl a s s e

Bild

10-1.

Common

-,

"Common- LI SP -Object - Objekttypen -



> Deklarative

System

-

>

10-2.

(generische Funktionen)

Vererbung

Object

Bild

-Baus te i ne

> Gattungsfunktionen > Multiple

LISP

-System"

Methodenkombination

Meta-Objekt-Protokol1 "Common-LISP-Object-System"-Charakter!stika

V O R G A N G definiert z.B. für die Klasse V E R W A H R K O N T O das bisher nicht vorhandene Feld A K T E N Z E I C H E N .

10.1 Scheme Object-Oriented Programming System (SCOOPS) Im folgenden nutzen wir SCOOPS-Konstrukte von PC Scheme, um damit objektgeprägte Konstruktionen allgemein zu erläutern. Scheme Object-Oriented Programming System (kurz: SCOOPS) ist ein kleines Flavor-System. Seine multiple Vererbung basiert auf einer "mixin"-Option im Definitions-Konstrukt der Klasse. Instanzen werden analog zum DEFINE-STRUCTURE-Konstrukt mit einem MAKE-Konstrukt erzeugt. Der Zugriff auf die Slots einer Instanz, d.h. auf ihren lokalen Wert, erfolgt mit Hilfe des SEND-Konstruktes. Die von SCOOPS generierten Selektoren und Mutatoren sind an das jeweilige Objekt (Instanz oder Klasse) im Sinne einer Nachricht zu "senden". In der funktionsgeprägten Terminologie formuliert: Das SEND-Konstrukt hat als Argumente das Objekt, den Operationsnamen und gegebenenfalls Argumente für die Operation. Weil wir einen Selektor oder Mutator mit dem SEND-Konstrukt an ein Objekt „übermittelt", betrachten wir diese in der ob-

10. Klasse-Instanz-Modell

Legende: A > B

Bild

::= Vererbungsrichtung; (Anders f o r m u l i e r t :

O

::=

Klasse



::=

Instanz

10-3.

273

A v e r e r b t an B. B e r b t von A)

Beispiel einer multiplen Vererbung: VERWAHRKONTO e r b t von KONTO und OFFENER-VORGANG

jektgeprägten Terminologie als eine "Nachricht" (engl.: message), die das Objekt empfängt und verarbeitet. Aus der Kommunikationsicht (Nachrichtenübermittlung) können wir formuliern: Empfängt ein Objekt eine Nachricht, dann interpretiert es die Nachricht, d.h. es ermittelt die zu applizierenden (Teil-)Operationen und führt diese aus. Eine Nachricht kann mehrere (Teil-)Operationen betreffen. Wir sprechen in diesem Kontext eher von Methoden (engl.: methods) statt von Operationen oder Funktionen. Eine Methode ist eine Funktion, die als Reaktion auf eine zugehörende Nachricht ausgeführt wird. Sie ist einer Klasse zugeordnet. Die Vererbung bezieht sich daher sowohl auf die lokalen Zustandsbeschreibungen („slots") wie auf die generierten Selektions-/Mutations-Funktionen und die selbst definierten Funktionen („methods"). /Hinweis: SCOOPS.FSL. Die SCOOPS-Konstrukte sind in der Systemdatei SCOOPS.FSL gespeichert. Diese Datei ist vorab zu laden, damit die SCOOPS-

274

II. Konstruktionen (Analyse und Synthese)

Konstrukte in der USER-INITIAL-ENVIRONMENT zugreifbarsind. eval> (LOAD"SCOOPS.FSL")==>OK ] Das Klasse-Instanz-Modell o DEFINE-CLASS o MAKE-INSTANCE o DEFINE-METHOD und o SEND

basiert auf den folgenden Konstrukten: ;Definiert die Slots ;Kreiert lokale Zustände ;Definiert eine eigene Methode ¡"Versendet" eine Nachricht ; (engl.: message handling).

/Hinweis: Eine Modifikation des implementierten Vererbungsmechanismus ist in SCOOPS nicht vorgesehen. Das mächtige CLOS ermöglicht eine weitreichende Beeinflussung über die deklarative Methodenkombination und über das Meta-Objekt-Protokoll (vgl. z.B. Keene, 1989).7 Zunächst betrachten wir die Syntax dieser SCOOPS-Konstrukte. Anschließend definieren wir damit unsere Kontenverwaltung an. eval>

(DEFINE-CLASS {(INSTVARS < s l o t > { < s l o t > } )} {(CLASSVARS < s l o t > { < s l o t > } )} {(MIXINS { < c o m p o n e n t - c l a s s > } )} {(OPTIONS { < g e t t a b l e - v a r i a b l e s > } {) {} )} — >

mit:



: := E i n S y m b o l , d a s d i e Klasse benennt.

(zu

definierende)

::= | ( < i n i t - v a l u e > ) I ( (ACTIVE < i n i t - v a l u e >

)) : : = Ein Symbol, benennt.

d a s den S l o t

(das

Feld)

::=

Ein s y m b o l i s c h e r Ausdruck, dessen Eval u i e r u n g den v o r e i n g e s t e l l t e n Wert ( I n i t i a l i s i e r u n g s w e r t ) f ü r den S l o t ergi bt.

::=

Eine F u n k t i o n mit e i n e r LAMBDA-Variab l e n . W i r d m i t dem g e n e r i e r t e n S e l e k t o r G ET - < s 1 o t - n a m e > a u f den S l o t z u g e g r i f f e n , dann w i r d d i e L A M B D A - V a r i a b l e an den a k t u e l l e n Wert von < s l o t - n a m e > g e bunden und m i t d i e s e r B i n d u n g < g e t f u n c t i o n > a u s g e f ü h r t . Der Wert von < g e t - f u n c t i o n > i s t dann d e r R ü c k g a b e wert f ü r GET-.

275

10. Klasse-Instanz-Modell



::= E i n e F u n k t i o n m i t e i n e r L A M B D A - V a r i a blen. Wird mit dem g e n e r i e r t e n Mutator S E T - < s l o t - n a m e > ein < n e u e r - w e r t > für den Slot gesendet, dann wird die L A M B D A - V a r i a b l e an < n e u e r - w e r t > g e b u n den und mit d i e s e r Bindung a u s g e f ü h r t . Der W e r t v o n < s e t - f u n c t i o n > ist d a n n d e r n e u e W e r t von < s l o t - n a m e > . ::= E i n

Symbol,

das

eine

::= G E T T A B L E - V A R I A B L E S (GETTABLE-VARIABLES



eval>

Klasse

benennt.

|

(I

)

SETTABLE-VARIABLES I (SETTABLE-VARIABLES I 1 ) INITTABLE-VARIABLES I (INITTABLE-VARIABLES 11 ) ::= N a m e d e r e r z e u g t e n K l a s s e , an d e n als Wert die Repräsentation Vektor gebunden ist.

(MAKE-INSTANCE { }

) = >

mit:



eval > ( D E F I N E - M E T H O D

:= V g l .

DEFINE-CLASS

:= E i n e V a r i a b l e , d e r e n W e r t e i n g ü l t i g e r < s l o t - n a m e > ist. Diesem Slot wird der danach folgende Initialisierungswert zugewiesen, falls seine Initialisierungsoption definiert wurde. := V g l .

DEFINE-C LASS

:= E i n e I n s t a n z , repräsentiert

die durch wird.

eine

Umgebung

( ) ({}) ) — >

mit:

::= s i e h e

DEFINE-C LASS

::= Ein S y m b o l , d a s e i n e M e t h o d e Funktion) bezeichnet.

(LISP-

276

II. Konstruktionen (Analyse und Synthese)

eval>

(SEND

::= L A M B D A - V a r i a b l e ::= S y m b o l i s c h e r 1.4.1)

(vgl.

Ausdruck

{}

Abschnitt (vgl.

—=>

1.4.1)

Abschnitt

mit:



::= s i e h e

MAKE-INSTANCE

::= D e r N a m e e i n e r wird

zugreifbaren Methode. nicht evaluiert.

::= S y m b o l i s c h e r A u s d r u c k , d e r a u s g e w e r t e t wird. Die e n t s p r e c h e n d e L A M B D A - V a r i a b l e d e r zu a p p l i z i e r e n d e n M e t h o d e w i r d an d i e s e n W e r t g e b u n d e n . ( H i n w e i s : Es s i n d in S C O O P S d e r z e i t m a x i m a l 8 A r g u m e n t e

möglich.)

::= R ü c k g a b e w e r t

der

applizierten

Methode.

Für unsere Kontenverwaltung definieren wir als Ausgangsbasis zunächst die Klasse KONTO (vgl. auch Bild 10-3). Entsprechend der DEFINE-STRUCTURE-Lösung (vgl. Abschnitt 9.2) beschreibt die Klasse KONTO die Slots (Felder): IDENTIFIZIERUNG, NAME-INHABER, ADRESSE-INHABER; EINZAHLUNGEN, AUSZAHLUNGEN und KONTOSTAND. Jedem dieser Slots ist ein Initialisierungswert zugeordnet. Wir wollen ausschließen, daß über das SEND-Konstrukt mit Hilfe der generierten Mutatoren bestimmte Slots, hier: EINZAHLUNGEN, AUSZAHLUNGEN und KONTOSTAND, direkt verändert werden können. Daher ist das automatische Generieren dieser Mutatoren zu verhindern. Das DEFINE-CLASSKonstrukt bietet diese Möglichkeit. Im Rahmen der OPTIONS-Angabe ist festlegbar, ob Selektoren und/oder Mutatoren für einen bestimmten Slot zu generieren sind. Enthält die GETTABLE-VARIABLES- bzw. die SETTABLEVARIABLES-Angabe keine Slots, dann gilt sie für alle Slots. Ist ein Slot genannt, dann werden nur für genannte Slots die Methoden generiert. Ähnliches gilt für die INITTABLE-VARIABLES-Angabe. Die Option INITTABLES-VARIABLES generiert keine Methoden, sondern spezifiziert die Möglichkeit, beim Kreieren einer Instanz den voreingestellten Wert des Slots zu überschreiben. Mit der expliziten Nennung von Slots ist die Initialisierung beim MAKE-INSTANCE-Konstrukt nur für diese Slots möglich. eva1>

(DEFINE -CLASS KONTO ( I N S T V A R S ( I D E N T I F I Z I E R U N G 0) (NAME-INHABER NIL) ( A D R E S S E - I N H A B E R NIL) (EINZAHLUNGEN NIL)

10. Klasse-Instanz-Modell

277

(AUSZAHLUNGEN NIL) (KONTOSTAND 0)) (OPTIONS GETTABLE-VARIABLES (SETTABLE- VARIABLES IDENTIFIZIERUNG NAME-INHABER ADRESSE-INHABER) INITTABLE-VARIABLES)) ==> KONTO Konstruktor. Für das Kreieren eines Kontos ist das eingebaute Konstrukt MAKE-INSTANCE der Konstruktor im engeren Sinne. Da das MAKE-INSTANCE-Konstrukt vom DEFINE-CLASS-Konstrukt abhängig ist, verteilt sich die originäre Konstruktor-Aufgabe (Definition und Implementation) auf beide Konstrukte. Als Kontenbeispiele konstruieren wir A001 und A002, wie folgt: eval> (DEFINE A001 (MAKE-INSTANCE KONTO •IDENTIFIZIERUNG 'AOOl)) ==> A001 eval> (DEFINE A002 (MAKE-INSTANCE KONTO 'IDENTIFIZIERUNG 'A002 'NAME-INHABER "Hinrich Bonin")) ==> A002 Prädikat: Benötigt wird das Prädikat KONTO?. Unverständlicherweise generiert SCOOPS kein Prädikat, das festellen kann, ob ein Objekt vom Typ einer bestimmten Klasse ist. Um dieses Prädikat selbst zu definieren, wäre ein Prädikat CLASS? hilfreich. Das Prädikat CLASS? prüft z.B., ob dem MAKE-INSTANCE-Konstrukt ein korrektes Argument übergeben wird. Auch dieses Prädikat CLASS? ist in SCOOPS nicht eingebaut. Hilfsweise definieren wir ein eigenes Prädikat. Dieses Prädikat CLASS? muß auf die primitivere LISP-Repräsentation für die Klasse zurückgreifen. Da SCOOPS die Klasse als Vektor abbildet, basiert unser Prädikat CLASS? auf dem Prädikat VECTOR?. Da nicht jeder Vektor eine SCOOPS-Klasse repräsentiert, ist das Kennzeichen des KlassenVektors auszuwerten. Es steht im ersten Vektorfeld und hat den Wert :#!CLASS:. eval> (DEFINE CLASS? (LAMBDA (OBJEKT) (AND (VECTOR? OBJEKT) (EQ? *|#!CLASS| (VECTOR-REF OBJEKT 0 ) ) ) ) ) ==> CLASS? eval> (CLASS? KONTO) ==> #T Entsprechend dem Erkennungskonstrukt für den Typ Klasse ist ein Konstrukt zum Erkennen des Typs Instanz erforderlich. Auch hier müssen wir zur Definition einer Hilfslösung auf die primitivere LISP-Repräsentation zurückgreifen. Die Instanz ist in SCOOPS als Umgebung repräsentiert. Unser Prädikat IN-

II. Konstruktionen (Analyse und Synthese)

278

STANCE? basiert daher auf dem eingebauten Prädikat ENVIRONMENT?. Zur Erkennung einer Umgebung, die eine Instanz abbildet, dient das von SCOOPS verwendete Symbol % * I N S T V A R S * % . eval> eval>

( E N V I R O N M E N T ? A 0 0 1 ) = = > #T (ENVIRONMENT-BINDINGS A001) ==> ( ( I D E N T I F I Z I E R U N G . A 0 0 1 ) (NAME - 1 N H A B E R ) (ADRESSE-INHABER) (EINZAHLUNGEN) (AUSZAHLUNGEN) (KONTOSTAND) (%*INSTVARSn . -))

eval>

(DEFINE INSTANCE? (LAMBDA ( O B J E K T ) (AND ( E N V I R O N M E N T ? O B J E K T ) (NOT ( E Q ? '#!UNASSIGNED (ASSOC % * I N S T V A R S * %

eval> eval>

(INSTANCE? (INSTANCE?

OBJEKT)))))) ==>

INSTANCE?

A 0 0 1 ) = = > #T KONTO) = = > ( )

SCOOPS verfügt über einen Selektor CLASS-OF-OBJECT, der die Klasse einer Instanz als Rückgabewert hat. Das CLASS-OF-OBJECT-Konstrukt bedingt ein Argument vom Typ Instance. Ein Argument vom Typ Umgebung führt zum Fehler, so daß wir es nicht zur Konstruktion des Prädikates INSTANCE? verwenden können. eval> eval> eval> eval>

( C L A S S - O F - O B J E C T A 0 0 1 ) = = > KONTO ( D E F I N E F00 (LET ( ( X 1 ) ) ( T H E - E N V I R O N M E N T ) ) ) ( E N V I R O N M E N T ? F 0 0 ) = = > #T ( C L A S S - O F - O B J E C T F 0 0 ) = = > ERROR . . .

==>

F00

Mit den (hier ersatzweise selbstdefinierten) Prädikaten C L A S S ? und INSTANCE? ist das anwendungsspezifische Prädikat KONTO? konstruierbar. Eine Instanz ist vom Typ KONTO, wenn sie durch die Klasse KONTO entstanden ist. Anders formuliert, wenn sie Mitglied der Klasse KONTO ist. eval>

( D E F I N E KONTO? ( L A M B O A ( O B J E K T ) ( M E M B E R - O F - C L A S S ? KONTO O B J E K T ) ) )

==>

KONTO?

Das entsprechende MEMBER-OF-CLASS?-Konstrukt definieren wir mit Hilfe des eingebauten Selektors CLASS-OF-OBJECT. Ist ein Objekt vom Typ Klasse, dann ist in SCOOPS der Name der Klasse im 2. Vektorfeld gespeichert. Auch hier benötigen wir die Details der eigentlich zu verbergenden internen Vektorstruktur. eval>

(DEFINE MEMBER-OF-CLASS? (LAMBDA ( K L A S S E I N S T A N Z ) (AND ( C L A S S ? K L A S S E ) (INSTANCE? INSTANZ) ( E Q ? (VECTOR-REF KLASSE 1) (CLASS-OF-OBJECT INSTANZ))))) ==>

MEMBER-OF-CLASS?

279

10. Klasse-Instanz-Modell eval> eval> eval>

(KONTO? (KONTO? (CLASS?

A 0 0 1 ) = = > #T KONTO) ==> ( ) K O N T O ) ==> #T

Für unsere Kontenverwaltung benötigen wir wie im Fall der DEFINE-STRUCTURE-Lösung (vgl. Abschnitt 9.1) wieder die Prädikate B E T R A G ? und BUCHUNGSDATUM?. eval>

( D E F I N E BETRAG? (AND (NUMBER?

(LAMBDA (X) X) (>= X 0 ) ) ) )

eval>

( D E F I N E BUCHUNGSDATUM? (AND (NUMBER? X) (>=

==>

(LAMBDA ( X ) X 890101))))

BETRAG? ==>

BUCHUNGSDATUM?

Selektor. Aufgrund der optionalen Angabe GETTABLE-VARIABLES im DEFINECLASS-Konstrukt sind die gewünschten Selektoren für die einzelnen Slots generiert worden. Da für unsere Klasse KONTO nicht die Option „aktive Instanzvariable" genutzt wurde, greift der generierte Selektor direkt auf den Slotwert zu. Im Fall einer „aktiven Instanzvariablen" würde die zugehörende Zugriffsfunktion mit dem Slotwert appliziert, und ihr Rückgabewert wäre das Selektionsergebnis (vgl. z.B. Programmfragment 10.2-1). Die Namen der generierten Selektoren sind mit dem SEND-Konstrukt als Nachricht an die Instanzen zu „senden", wie die folgenden Beispiele zeigen: eval> eval> eval> eval>

(SEND (SEND (SEND (SEND

A001 A001 A002 A002

G E T - 1 D E N T I F I Z I E R U N G ) ==> A001 G E T - K 0 N T 0 S T A N D ) ==> 0 KONTOSTAND) ==> ERROR . . . ¡Undefinierte G E T - N A M E - I N H A B E R ) ==> " H i n r i c h B o n i n "

Methode

/Exkurs: "messagepassing". Im strengen Sinne des Nachrichtenversands verursacht das Senden einer Nachricht an jemanden ein (potentiell) asynchrones Anworten auf diese Nachricht. Beispiel: Der Sachbearbeiter sendet seinem Chef eine Nachricht. Wann er die Antwort erhält, bestimmt in der Regel der Chef. Zwischenzeitlich kann der Sachbearbeiter andere Aufgaben erledigen. Die SEND-Syntax suggeriert, daß ein solches „asynchrones" Antwortenverhalten vorliegt. In objekt-orientierten LISP-Erweiterungen bedeutet Nachrichtenversand das Heraussuchen einer Funktion und ihre Abarbeitung. Der Sender ist nicht in der Lage, zwischenzeitlich etwas anderes zu bearbeiten. Er wartet auf die Antwort. Senden einer Nachricht führt „nur" zur Applikation einer LISPFunktionJ Da in SCOOPS die Instanz eine Umgebung ist, in der die einzelnen Slot-Namen gebunden sind, kann innerhalb dieser Umgebung ohne den SEND-Mechanismus direkt auf die Slots zugegriffen werden. eval>

; ; Durchbrechen der S 1 o t - A b s c h i r m u n g ( E V A L N A M E - I N H A B E R A002) ==> " H i n r i c h

Bonin"

280

II. Konstruktionen (Analyse und Synthese)

Das Klasse-Instanz-Modell hat als ein wesentliches Merkmal die Abschirmung („Einkapselung") der internen Zustände ihrer Objekte (Klassen und Instanzen) gegen Zugriffe von außen, die nicht über die vorgesehene Schnittstelle laufen. /Exkurs: Einkapselung. Der gewünschte Objektzustand ist entscheidend für die Objektdefinition und für die Objektverwendung. Die Einkapselung (engl.: encapsulation) hat deshalb nur die Implementation zu verbergen und nicht den internen Objektzustand. Die Zusammenfassung aller Implementations-Informationen zu einem Objekt (Programmstück) und deren Abschirmung gegen direkte Zugriffe von anderen Programmteilen aus sichert die Modifizierbarkeit eines Objektes. Neben- und Fernwirkungen einer Objektänderung sind dadurch begrenzbar. Wäre jedoch eine Optimierung der Effizienz gefordert, dann hätte eine Streuung der Informationen über die Implementations-Details an viele Programmstellen Vorteile. Umwege über die Schnittstelle, d.h. hier über den Nachrichten Versandmechanismus, wären einsparbar.7 Ein Durchbrechen der Abschirmung der Implementationsdetails der Slots ist daher unbedingt zu vermeiden. Der Verweis auf die interne Repräsentation (Implementationsdetail) und damit auf die Umgehungsmöglichkeit der Schnittstelle dient hier ausschließlich zum Verstehen. Deutlich wird damit, warum wir bei der Definition von Methoden auf die Instanzvariablen ohne SEND-Mechanismus und ohne generierte GET-Selektoren direkt zugreifen können (vgl. die folgenden Beispiele für das DEFINE-METHOD-Konstrukt). Mutator. Wie Selektoren werden, abhängig von der optionalen SETTABLE-VARIABLES-Angabe, auch Mutatoren generiert, die über den SEND-Mechanismus anwendbar sind. eval>

eval>

(SEND A002 SET-ADRESSE-INHABER " F u h r e n k a m p 2a D - 2 8 5 8 S c h i f f d o r f " ) = = > " F u h r e n k a m p 2a D - 2 8 5 8 (SEND A002 SET-KONTOSTAND = = > ERROR . . . ¡ M u t a t o r

1000) SET-KONTOSTAND

nicht

Schiffdorf" definiert

!

Für unsere Konto Verwaltung werden die Mutatoren ADD-EINZAHLUNG! und ADD-AUSZAHLUNG! benötigt (vgl. Abschnitt 9.2). Wir definieren sie als Methoden, die der Klasse KONTO zugeordnet sind. Bevor eine Methode definiert werden kann, ist ihre Klasse mit DEFINE-CLASS zu definieren. Diese Beschränkung ist angebracht, da das System einerseits für die Methode einen zweckmäßigen Speicherplatz benötigt, um sie später wiederzufinden, und andererseits sollten der Methode aus Effizienzsgründen die Variablen bekannt sein, auf die sie direkt zugreifen kann (zumindest beim Compilieren). Bei der Definition der Methoden ADD-EINZAHLUNG! und ADD-AUSZAHLUNG! können wir direkt globale LISP-Konstrukte wie BETRAG? und

10. Klasse-Instanz-Modell

281

B U C H U N G S D A T U M ? verwenden; verfügbar sind alle Symbole der USER-INITIAL-ENVIRONMENT und der USER-GLOBAL-ENVIRONMENT. Zusätzlich sind diejenigen Instanz-Variablen direkt zugreifbar, die der Instanz gehören, auf die wir die Methode anwenden. Senden wir einer Instanz eine Nachricht, die unsere Methode zur Anwendung bringt, dann wird die Methode wie eine LISPFunktion in der Umgebung, die die Instanz repräsentiert, ausgeführt. Daher sind die Instanz-Variablen bei der Methodendefinition direkt verfügbar. eval>

(DEFINE-METHOD (COND

eval>

(KONTO A D D - E I N Z A H L U N G ! ) (E.DATUM BETRAG) ( ( A N D (BUCHUNGSDATUM? E.DATUM) (BETRAG? BETRAG)) ( S E T ! E I N Z A H L U N G E N (CONS ( L I S T E_DATUM B E T R A G ) EINZAHLUNGEN)) ( S E T ! KONTOSTAND ( + B E T R A G K O N T O S T A N D ) ) KONTOSTAND) ¡ P r o b l e m R ü c k g a b e w e r t ! ; Siehe Hinweis. (T ( E R R O R " K e i n e E i n z a h l u n g h i n z u g e f u e g t ! " E-DATUM B E T R A G ) ) ) ) = = > ADD-EINZAHLUNG!

(DEFINE-METHOD (COND

(KONTO A D D - A U S Z A H L U N G ! ) (A_DATUM B E T R A G ) ( ( A N D (BUCHUNGSDATUM? A_DATUM) (BETRAG? BETRAG)) (COND ( ( < = B E T R A G K O N T O S T A N D ) ( S E T ! AUSZAHLUNGEN (CONS ( L I S T A_DATUM B E T R A G ) AUSZAHLUNGEN)) ( S E T ! KONTOSTAND ( - KONTOSTAND B E T R A G ) ) KONTOSTAND) ¡ P r o b l e m R ü c k g a b e w e r t ! ; Siehe Hinweis. (T ( E R R O R " K e i n e D e c k u n g f u e r : " BETRAG)))) (T ( E R R O R " K e i n e A u s z a h l u n g m o e g l i c h ! " A_DATUM B E T R A G ) ) ) ) = = > A D D - A U S Z A H L U N G !

/Hinweis: Rückgabewert. Wird die Methode ADD-EINZAHLUNG! bzw. A D D - A U S Z A H L U N G ! angewendet, dann ist der hier definierte Rückgabewert der Wert des Slots K O N T O S T A N D . Falls wir Methoden wie Funktionen schachteln wollten, wäre es zweckmäßig, nicht den K O N T O S T A N D , sondern die ganze Instanz zurückzugeben. Notwendig wäre dazu die Kenntnis des Objektes, an das die Nachricht gesendet wurde. (SEND ' (SEND (SEND < o b j e c t > ) )

)

) eval> eval> eval>

(SEND (SEND (SEND

A002 A002 A002

ADD-EINZAHLUNG! ADD-AUSZAHLU-NG! GET-K0NT0STAND)

] 890713 500.00) 890801 400.00) = = > 100

==> ==>

500. 100.

282

II. Konstruktionen (Analyse und Synthese)

Zum Zeitpunkt des Kreierens der Instanz A002 (Anwendung von MAKE-INSTANCE) waren die Methoden ADD-EINZAHLUNG! und ADD-AUSZAHLUNG! noch nicht definiert. Wir können sie trotzdem auf A002 anwenden. Wenn das nachträgliche Modifizieren einer Klasse auf die von ihr abhängigen Objekte unmittelbar vererbt wird, dann sprechen wir von dynamischer Vererbung. Als abhängige Objekte gelten alle Klassen und Instanzen, die im Vererbungsgraph auf der modifizierten Klasse aufbauen; oder anders formuliert: alle Objekte die Nachfolgeknoten im Vererbungsgraphen sind. In SCOOPS bezieht sich die dynamische Vererbung auf die Methoden und nicht auf die Instanzvariablen bestehender Instanzen (vgl. Abschnitt 10.2). /Hinweis: In CLOS ist die Vererbung von Modifikationen, insbesondere der Instanzvariablen, anwendungsabhängig spezifizierbar. Z.B. kann auf den „alten" Wert einer Instanzvariablen im Rahmen der Vererbung der Modifikation zugegriffen werden (vgl. z.B. Keene, 1989)./ Wir ändern eine Kontonummer, indem das Konto als ein Objekt in der USERINITIAL-ENVIRONMENT eine neue Bindung erhält. Die neue Kontonummer wird an das Objekt gebunden, und die Bindung der alten Kontonummer wird aufgelöst. Vorab prüfen wir, ob die neue Kontonummer schon als Konto existiert und die alte Kontonummer wirklich an ein Objekt vom Typ KONTO gebunden ist. eva1> (DEFINE-METH00 (KONTO AENDERE-KONTO-IDENTIFIZIERUNG!) (K_IDENT_ALT K_IDENT_NEU) (COND ((KONTO? (EVAL '(ACCESS ,K_IDENT_NEU USER-INITIAL-ENVIRONMENT))) (ERROR "Neue Kontonummer i s t b e l e g t : " K_IDENT_NEU)) ((KONTO? (EVAL '(ACCESS ,K_IDENT_ALT USER-INITIAL-ENVIRONMENT))) (EVAL '(DEFINE ,K_IDENT_NEU (EVAL ,K_IDENT_ALT)) USER-INITIAL-ENVIRONMENT) (EVAL '(SEND ,K_IDENT_NEU SET-IDENTIFIZIERUNG ' , K_ I DENT_NEU ) USER-INITIAL-ENVIRONMENT) (EVAL '(UNBIND ',K_IDENT_ALT USER-INITIAL-ENVIRONMENT) USER-INITIAL-ENVIRONMENT)) (T (ERROR "Alte Kontonummer e x i s t i e r t n i c h t : " K_ I DENT_ALT)))) ==> AENDERE-KONTO-IDENTIFIZIERUNG!

10. Klasse-Instanz-Modell

283

eval>

(SEND A 0 0 1 AENDERE-KONTO- I D E N T I F I Z I E R U N G ! ' A O O l ' A 0 0 2 ) = = > [ERROR e n c o u n t e r e d ! ] Neue Kontonummer i s t b e l e g t : A002

eval>

(SEND AOOl A E N D E R E - K O N T O - I D E N T I F I Z I E R U N G ! ' A 0 0 8 ' A 0 Ü 9 ) = = > [ERROR e n c o u n t e r e d ! ] A l t e Kontonummer e x i s t i e r t n i c h t : A008

eval>

(SEND AOOl A E N D E R E - K O N T O - I D E N T I F I Z I E R U N G ! ' A 0 0 2 ' A 0 0 7 ) = = > # ( S E N D A007 GET-KONTOSTAND) = = > 1 0 0 .

eval>

Wie das obige Beispiel zeigt, können wir mit dem SEND-Mechanismus die Methode AENDERE-KONTO-IDENTIFIZIERUNG! an eine beliebige Instanz der Klasse KONTO senden. Auf welche Instanz sich die Änderung der Kontonummer bezieht, wird erst durch die weiteren Argumente definiert. Dies ist keine zweckmäßige Lösung. Wir hätten gern in der Methodendefinition die Möglichkeit genutzt, das Objekt abzufragen, an das die Nachricht gesendet wird, die zur Anwendung der Methode führt. Benötigt wird ein Konstrukt SELF, dessen Wert das Objekt ist, an das die Nachricht gesendet wurde. Dann hätten wir uns die LAMBDA-Variable K_ IDENT_ ALT in der Methodendefinition ersparen können. Ein solches SELF-Konstrukt stellt SCOOPS nicht zur Verfügung. Als Lösung bietet sich hier das Formulieren eines eigenen Makros SENDWITH-OBJECT an, so daß die doppelte Nennung der alten Kontonummer entfällt. Diese Lösung entspricht einer Funktion, die wir an ihre LAMBDA-Variablen binden (vgl. z.B. Fixpunktoperator in Abschnitt 8.1.2). eval>

eval>

(MACRO S E N D - W I T H - O B J E C T (LAMBDA ( S E X P R ) ' ( S E N D , ( L I S T - R E F SEXPR 1) . ( L I S T - R E F SEXPR 2) ' . ( L I S T - R E F SEXPR 1) , @ ( L I S T - R E F SEXPR 3)

¡das Objekt ;die Nachricht ¡wieder das Objekt ¡ R e s t der Angabe )) ==> SEND-WITH-OBJECT

; ; Da SEND a u c h e i n M a k r o i s t , i n t e r e s s i e r t ; ; h i e r n u r d i e E x p a n d i e r u n g a u f 1. S t u f e . (EX PAND-MACRO-1 '(SEND-WITH-OBJECT

A007 AENDERE-KONTO-IDENTIFIZIERUNG!

==> eval>

(SEND A007 A E N D E R E - K O N T O - I D E N T I F I Z I E R U N G !

'A100)) 'AlOO)

(SEND-WITH-OBJECT A007 A E N D E R E - K O N T O - I D E N T I F I Z I E R U N G !

'A100) ==>

eval>

'A007

(SEND AlOO GET-KONTOSTAND)

==>

100

#

284

II. Konstruktionen (Analyse und Synthese)

10.2 Multiple dynamische Vererbung Aufbauend auf den Klassen K O N T O und O F F E N E R - V O R G A N G definieren wir die Klasse V E R W A H R K O N T O (vgl. Bild 10-3). Die Klasse VERW A H R K O N T O „hängt" unter diesen beiden Klassen. Sie ist eine Unterklasse, auch Subklasse genannt, der Klasse K O N T O und der Klasse OFFENERV O R G A N G . Umgekehrt betrachtet: K O N T O und O F F E N E R - V O R G A N G sind in Bezug auf die Klasse V E R W A H R K O N T O Superklassen. Wir können die Klasse V E R W A H R K O N T O als eine Spezialisierung ihrer Superklassen betrachten. Eine Superklasse stellt dann eine Zusammenfassung der gemeinsamen Eigenschaften (Slots und Methoden) ihrer Subklassen dar. Die Zusammenfassung von gemeinsamen Eigenschaften zu einer Klasse bezeichnet man als Faktorisierung. eval>

( D E F I N E - C L A S S VERWAHRKONTO ( M I X I N S KONTO O F F E N E R - V O R G A N G ) ( I N S T V A R S (BUCHUNGSGRUND N I L ) (BEARBEITER "Krause")) (OPTIONS GETTABLE-VARIABLES SETTABLE-VARIABLES INITTABLE-VARIABLES)) ==>

VERWAHRKONTO

Mit dem in SCOOPS eingebauten DESCRIBE-Konstrukt ist das Ergebnis der definierten Klasse V E R W A H R K O N T O anzeigbar: eval> (DESCRIBE V E R W A H R K O N T O ) = = > CLASS

DESCRIPTION

NAME C L A S S VARS INSTANCE VARS METHODS MIXINS CLASS COMPILED CLASS INHERITED () ; Rückgabewert

VERWAHRKONTO ( ) (BUCHUNGSGRUND B E A R B E I T E R ) (SET-BUCHUNGSGRUND S E T - B E A R B E I T E R GET-BUCHUNGSGRUND G E T - B E A R B E I T E R ) (KONTO O F F E N E R - V O R G A N G ) ( ) () des D E S C R I B E - K o n s t r u k t e s

Als sogenannte M I X I N S ist erst die Klasse K O N T O und dann die Klasse OFFEN E R - V O R G A N G genannt. Die Reihenfolge der M I X I N S ist bedeutsam. Sie bestimmt die Priorität bei der Vererbung. Kommen namensgleiche Slots oder Methoden in K O N T O und in O F F E N E R - V O R G A N G vor, dann ist das Erbe der erstgenannten Klasse (hier K O N T O ) für Instanzen der Klasse V E R W A H R K O N T O dominant. Anders formuliert: Die Slots bzw. Methoden der zuerstgenannten Klasse überschatten die namensgleichen Slots bzw. Methoden der nachfolgenden genannten Klassen (vgl. Bild 10.2-1). SCOOPS

speichert die beiden Eigenschaften C L A S S C O M P I L E D und

285

10. Klasse-Instanz-Modell

CLASS INHERITED für eine Klasse. Eine Klasse kann entweder direkt mit dem COMPILE-CLASS-Konstrukt oder indirekt durch Anwendung des MAKEINSTANCE-Konstruktes compiliert werden. Bei dieser "Compilierung" einer Klasse werden die Slots und Methoden ihrer MIXINS vererbt. Die in den „compilierten" MIXINS genannten Klassen werden als vererbt gekennzeichnet, d.h. ihr CLASS INHERITED weist den Wert # T aus. eval>

(COMPILE-CLASS

)

==>

Die MIXINS der Klasse VERWAHRKONTO sind die Klassen KONTO und OFFENER-VORGANG, wobei bisher nur die Klasse KONTO definiert und durch das Kreieren ihre Instanz A001 implizit compiliert wurde. Die Definition der Klasse OFFENER-VORGANG ist noch nicht vollzogen. Bis dies erfolgt, bezeichnen wir sie als eine „unentschiedene" Klasse (engl.: pending class). Eine Instanz der Klasse VERWAHRKONTO zu konstruieren, bedingt vorab die Klassendefinition OFFENER-VORGANG. eval > eval>

( D E F I N E V 0 0 1 (MAKE - 1 N S T A N C E VERWAHRKONTO)) = = > ERROR . . . ¡ K l a s s e O F F E N E R - V O R G A N G n i c h t

definiert

( D E F I N E - C L A S S OFFENER-VORGANG (INSTVARS (AKTENZEICHEN N I L ) (ABSENDER N I L ) (BISHERIGE-BEARBEITUNG NIL) (BEARBEITER "Fischer")) ( C L A S S V A R S (ANZAHL 0 ) ) (0PTI0NS GETTABLE-VARIABLES SETTABLE-VARIABLES I N I T T A B L E - V A R I A B L E S ) ) = = > OFFENER-VORGANG

Die Klasse OFFENER-VORGANG enthält die Klassenvariable ANZAHL. Sie ist eine gemeinsame Variable (engl.: shared slot) aller ihrer Instanzen. In diesem Kontext ist eine Instanzvariable eine lokale Variabe (engl.: local slot). Als Kontenbeispiele konstruieren wir die beiden Verwahrkonten V001 und V002 und wenden die obigen Methoden darauf an. eval>

(DEFINE

eval>

(DEFINE

eval> eval> eval> eval> eval>

V001

(MAKE-INSTANCE

VERWAHRKONTO))

==>

V 0 0 2 ( M A K E - 1 N S T A N C E VERWAHRKONTO 'IDENTIFIZIERUNG 'V002 'NAME-INHABER "Franz Otto D i c k e r " ' A D R E S S E - I N H A B E R " D o r f s t r a s s e 10 D - 1 0 0 0 )) = = > V002

V001

Berlin

(SEND V002 ADD-EINZAHLUNG! 891230 1 0 0 . 1 1 ) = = > 1 0 0 . 1 1 (SEND V002 ADD-EINZAHLUNG! 8 9 1 2 3 1 0 . 8 9 ) ==> 101. (SEND V002 ADD-AUSZAHLUNG! 900102 5 1 . 0 0 ) ==> 50. (SEND V002 ADD-AUSZAHLUNG! 9 0 0 1 0 3 7 0 . 0 0 ) = = > [ERROR e n c o u n t e r e d ! ] K e i n e Deckung f u e r : 70. (SEND-WITH-OBJECT V002 AENDERE-KONTO-IDENTIFIZIERUNG! ' V 0 0 3 ) ==> V002

2"

286

II. Konstruktionen (Analyse und Synthese)

e v a l > (SEND V003 GET-ANZAHL) = = > 0 e v a l > (SEND V 0 0 1 S E T - A N Z A H L 1 2 ) = = > 12 e v a l > (SEND V003 GET-ANZAHL) = = > 12

¡Shared

slot

!

eval>

(SEND V003 SET-BISHERIGE-BEARBEITUNG

eval>

(SEND V003 SET-BISHERIGE-BEARBEITUNG (LIST "3. Lieferung" (SEND V003 G E T - B I S H E R I G E - B E A R B E I T U N G ) ) ) ==> ( " 3 . L i e f e r u n g " " T e l e f o n i s c h e

eval>

(DESCRIBE

INSTANCE

Instance

V003)

"Telefonische Anfrage") ==> " T e l e f o n i s c h e A n f r a g e "

==>

DESCRIPTION

of C l a s s

Class Variables ANZAHL : 12

VERWAHRKONTO

:

Instance Variables : BISHERIGE-BEARBEITUNG : (3. Lieferung T e l e f o n i s c h e ABSENDER : ( ) AKTENZEICHEN : ( ) KONTOSTAND : 5 0 . AUSZAHLUNGEN : ( ( 9 0 0 1 0 2 5 1 . ) ) EINZAHLUNGEN : ( ( 8 9 1 2 3 1 0 . 8 9 ) ( 8 9 1 2 3 0 1 0 0 . 1 1 ) ) A D R E S S E - I N H A B E R : D o r f s t r a s s e 10 D - 1 0 0 0 B e r l i n 2 NAME-INHABER : F r a n z O t t o D i c k e r I D E N T I F I Z I E R U N G : V003 BUCHUNGSGRUND : ( ) BEARBEITER : Krause () ; Rückgabewert des D E S C R I B E - K o n s t r u k t e s eval>

(DESCRIBE

CLASS

KONTO)

Anfrage)

==>

DESCRIPTION

NAME C L A S S VARS INSTANCE VARS METHODS

Anfrage")

: KONTO : () : ( I D E N T I F I Z I E R U N G NAME-INHABER A D R E S S E - I N H A B E R EINZAHLUNGEN AUSZAHLUNGEN KONTOSTAND) : (AENDERE-KONTO - 1 D E N T I F I Z I E R U N G ! ADD-AUSZAHLUNG! A D D - E I N Z A H L U N G ! S E T - 1 DENT I F I Z I E R U N G S E T - N A M E - I N H A B E R S E T - A D R E S S E -1NHABER G E T - I D E N T I F I Z I E R U N G GET-NAME-INHABER G E T - A D R E S S E - I N H A B E R GET-EINZAHLUNGEN GET-AUSZAHLUNGEN GET-KONTOSTAND)

287

10. Klasse-Instanz-Modell MIXINS C L A S S COMPILED CLASS INHERITED () eval> cplO

(DESCRIBE

CLASS

: () : #T : #T

VERWAHRKONTO)

==>

DESCRIPTION

NAME C L A S S VARS INSTANCE VARS

METHODS

MIXINS C L A S S COMPILED CLASS INHERITED ()

VERWAHRKONTO (ANZAHL) ( B I S H E R I G E - B E A R B E I T U N G ABSENDER AKTENZEICHEN KONTOSTAND AUSZAHLUNGEN EINZAHLUNGEN A D R E S S E - I N H A B E R NAME-INHABER I D E N T I F I Z I E R U N G BUCHUNGSGRUND B E A R B E I T E R ) ( G E T - B I S H E R I G E - B E A R B E I T U N G GET-ABSENDER G E T - A K T E N Z E I C H E N GET-ANZAHL SET-BISHERIGE-BEARBEITUNG SET-ABSENDER S E T - A K T E N Z E I C H E N S E T - A N Z A H L GET-KONTOSTAND GET-AUSZAHLUNGEN GET-EINZAHLUNGEN GET-ADRESSE-INHABER GET-NAME-INHABER GET-IDENTIFIZIERUNG SET-ADRESSE-INHABER S E T - N A M E - I N H A B E R S E T - I D E N T I F I ZIERUNG ADD-EINZAHLUNG! ADD-AUSZAHLUNG! A E N D E R E - K O N T O - I D E N T I F I ZIERUNG! SET-BUCHUNGSGRUND S E T - B E A R B E I T E R GET-BUCHUNGSGRUND G E T - B E A R B E I T E R ) (KONTO OFFENER-VORGANG) #T #T

e v a l > ( D E S C R I B E OFFENER-VORGANG) CLASS DESCRIPTION

NAME C L A S S VARS INSTANCE VARS METHODS

MIXINS C L A S S COMPILED CLASS INHERITED ()

==>

OFFENER-VORGANG (ANZAHL) ( A K T E N Z E I C H E N ABSENDER BISHERIGE-BEARBEITUNG BEARBEITER) (SET-ANZAHL SET-AKTENZEICHEN SET-ABSENDER SET-BISHERIGE-BEARBEITUNG SET-BEARBEITER GET-ANZAHL G E T - A K T E N Z E I C H E N GET-ABSENDER GET-BISHERIGE-BEARBEITUNG GET-BEARBEITER) () () #T

288

II. Konstruktionen (Analyse und Synthese)

Zur Vertiefung der multiplen Vererbung im Klassen-Instanz-Modell betrachten wir den Vererbungsgraphen gemäß Bild 10.2-1. Das Programmfragment 10.2-1 bildet diesen Vererbungsgraphen in SCOOPS-Konstrukten ab. Entsprechend Weinreb/Moon, die mit ihrem Flavor-System die multiple Vererbung in LISP bekannt gemacht haben, bezeichnen wir die Klassen(-Knoten) im Vererbungsgraphen als Flavors. Für einige Flavors definieren wir namensgleiche Slots (Klassen- und Instanzvariablen) und namensgleiche Methoden. Wird eine Instanz gebildet, dann stellt sich die Frage, welche Slots mit welchen Inhalten und welchen Methoden sie erbt. Bei gleichnamigen Slots bzw. Methoden ist eine Prioritätenregelung notwendig. Zur Bildung einer solchen Prioritätsreihenfolge sind die Flavors in eine Sequenz einzuordnen, d.h. der gerichtete azyklische Vererbungsgraph (engl.: directed acyclic graph, kurz DAG) ist zu linearisieren. In der CLOS-Terminologie formuliert: Es geht um die „class precedence list". Die multiple Vererbung erspart das Duplizieren von Informationen. Gegenüber einem einfachen Vererbungsbaum vermeiden wir identische Knoten und deren konsistente Pflege. Bilden wir den Vererbungsgraph von Bild 10.2-1 mit den Mitteln der einfachen, d.h. nicht multiplen, Vererbung ab, dann ist in dem Vererbungsbaum Flavor F3 doppelt auszuweisen (vgl. Bild 10.2-2). /Exkurs: Klassentypen. Wenn eine Klasse keine eigenen Instanzen aufweist, also nur Abstraktionsaufgaben hat (in Bild 10.2-1 die Flavors Fl, F2 und F4), dann spricht man im SmallTalk-Jargon von einer „abstrakten Superklasse" (vgl. Goldberg/Robson, 1983). Werden Klassen durch Objekte (LISP-Konstrukte) repräsentiert, die selbst Instanzen von sind, dann ist eine MetaKlasse, gesehen aus der Perspektive einer Instanz./ ; ; ; ; Dokument 10.2-1 ; ; ; ; T i t e l : Programmfragment zur Abbildung des Vererbungsgraphen ; ;;; gemäß Bild 10.2-1. ; ; ; ; E r s t e l l t am 14.12.89 ¡ l e t z t e Änderung: eval> (DEFINE *DATE* 891214) ==> *DATE* eva1> (DEFINE-CLASS Fl (INSTVARS (LS-A (ACTIVE ' ( F l 0) (LAMBDA (X) (CAR X)) (LAMBDA (X) (LIST X *DATE*))))) (CLASSVARS (CS ' F l ) ) (0PTI0NS GETTABLE-VARIABLES SETTABLE-VARIABLES INITTABLE-VARIABLES)) ==> Fl eva1> (DEFINE-CLASS F2 (INSTVARS (LS-B (ACTIVE '(F2 0) (LAMBDA (X) (CAR X)) (LAMBDA (X) (LIST X *DATE*))))) (MIXINS Fl) (0PTI0NS GETTABLE- VARIABLES SETTABLE-VARIABLES INITTABLE-VARIABLES)) ==> F2

289

10. Klasse-Instanz-Modell

Legende:

Fl, F2, F3, F4, F5 F3-I1, F5-I2 LS-A, LS-B, LS-C, LS-D CS

Flavor (Klasse) Instanz Local Slot ( I n s t a n z v a r i a b l e ) Shared (Class) Slot (Klassenvari able) Ml ::= Methode —> ::= Vererbungsrichtung [ i ] ::= Abstraktionsebene; 1 = 1 , 2 , 3 , 4

Bild 1 0 . 2 - 1 .

Multiple gleichen

::= ::= ::= ::=

Vererbung: Slots und

Flavor-Graph mit namens - gl ei chen

namensMethoden

eva1> (DEFINE-CLASS F3 (INSTVARS (LS-A (ACTIVE '(F3 0) (LAMBDA (X) (CAR X)) (LAMBDA (X) (LIST X *DATE*))))) (CLASSVARS (CS 'F3)) (MIXINS Fl) (OPTIONS GETTABLE-VARIABLES SETTABLE-VARIABLES IN ITTABLE-VARIABLES)) ==> F3

290

II. Konstruktionen (Analyse und Synthese)

Legende:

Vgl. Bild

Bild

10.2-2.

10.2-1. Einfache Vererbung führt zur Duplizierung Informationen; hier F3' und F3'' gleicher Flavor-Graph in Bild 10.2-1.

eval> (DEFINE-CLASS F4 (INSTVARS (LS-C (ACTIVE ' ( F4 0) (LAMBDA (X) (CAR X)) (LAMBDA (X) (LIST X *DATE*))))) (MIXINS F2 F3) (OPTIONS GETTABLE-VARIABLES SETTABLE-VARIABLES INITTABLE-VARIABLES)) ==> F4 eval> (DEFINE-CLASS F5 (INSTVARS (LS-D (ACTIVE ' ( F 5 (LAMBDA (X) (LAMBDA (X) (MIXINS F4) (OPTIONS GETTABLE-VARIABLES SETTABLE-VARIABLES INITTABLE-VARIABLES) eval> eval> eval> eval> eval>

(COMPILE-CLASS (COMPILE-CLASS (COMPILE-CLASS (COMPILE-CLASS (COMPILE-CLASS

Fl) F2) F3) F4) F5)

==> ==> ==> ==> ==>

Fl F2 F3 F4 F5

0) (CAR X)) (LIST X *DATE*)))))

) ==> F5.

gemäß

10. Klasse-Instanz-Modell

291

eval > ( D E F I N E F 3 - I 1 (MAKE - 1 N S T A N C E F3 'LS-A 'F3-I1)) = = > F 3 - I 1 e v a l > ( D E F I N E F5-I2 ( M A K E - I N S T A N C E F5 ' L S - B 'F5-12)) = = > F5-I2 e v a l > ( D E F I N E - M E T H O D (Fl M l ) () (WRITELN "---Fl " LS-A)) = = > Ml e v a l > ( D E F I N E - M E T H O D (F2 M l ) () ( W R I T E L N " — F2 " LS-A)) = = > Ml e v a l > ( D E F I N E - M E T H O D (F3 M l ) () ( W R I T E L N " — F3 " LS-A)) = = > Ml Programmfragment 10.2-1.

Vererbungsgraph gemäß Bild 10.2-1 - f o r m u l i e r t in S C O O P S - K o n s t r u k t e n -

Wir betrachten zunächst die Instanz F3-I1 des obigen Vererbungsgraphen. Die Instanz F3-I1 erbt von ihrem („Heimat-")Flavor F3 die Slots L S - A und CS. Beim Kreieren der Instanz F3-I1 mit dem MAKE-INSTANCE-Konstrukt erhielt der Slot L S - A den Wert F3-I1. Hier ist es der Name der Instanz, damit wir den Vererbungsmechanismus leichter verfolgen können. Das D E F I N E - C L A S S Konstrukt für Flavor F3 gibt für den Slot C S den Initialisierungswert F3 an. Für die Instanz F3-I1 hat C S diesen voreingestellten Wert F3. eval> (DESCRIBE F3-I1) = = > INSTANCE

DESCRIPTION

I n s t a n c e o f C 1 a s s F3 Class Variables : CS : F3 Instance Variables : LS-A : F 3 - I 1 () Als Komponenten-Flavor weist die MIXINS-Angabe von F3 den Flavor F l aus. Der Flavor F l hat in Bezug auf F3 die namensgleichen Slots L S - A und CS. Diese Fl-Slots werden von den namensgleichen F3-Slots „überschattet". Zur Bestimmung „Wer überschattet (überschreibt) wen?" ist der Vererbungsgraph zu durchlaufen. Startpunkt ist die jeweilige Instanz. Maßgeblich ist der (Durchsuchungs-)Algorithmus. In S C O O P S ist der Tiefensuche-Algorithmus implementiert. Diesen Suchalgorithmus haben wir in Abschnitt 5.3 erörtert und programmiert. Wird z.B. für die Instanz F5-I2 die Methode M l von F2 oder von F3 angewendet? Anders formuliert: Wird erst der linke F2-Zweig oder der rechte F3-Zweig des Graphen abgesucht? Die Entscheidung wird üblicherweise durch die vordefinierte „Von-

292

II. Konstruktionen (Analyse und Synthese)

links-nach-recht-Abarbeitungsfolge" in den MIXINS festgelegt. In CLOS-Terminologie ist es die „local precedence order"-Liste der Klassen-Definition. Durchläuft man den obigen Vererbungsgraphen, von der Instanz F3-I1 ausgehend, entsprechend dem Tiefensuche-Algorithmus entlang der definierten Vererbung, also entgegen der Pfeilrichtung, dann erhält man die Flavorfolge: F3 Fl. Gehen wir von der Instanz F5-I1 aus, so erhalten wir die Flavorfolge: F5 F4 F2 Fl F3 Fl. Slots bzw. Methoden werden entsprechend dieser Flavorfolge, die eine Linearisierung des Graphen darstellt, vererbt. Die Flavorfolge wird von links nach rechts abgearbeitet. Ist ein Slot bzw. eine Methode schon vererbt worden, dann wird sein erneutes Vorkommen nicht vererbt. Das erneute Vorkommen gilt als "überschattet". Um bei dieser Untersuchung schon „expandierte" Flavors nicht nochmals zu prüfen, werden sie vorab aus der Flavorfolge gestrichen. Es entsteht eine bereinigte Prioritätsfolge (vgl. Tab. 10.2-1). In SCOOPS wird diejenige Flavornennung gestrichen, die als Wiederholung vorkommt. Die bereinigte Prioritätenfolge bilden die erstmaligen Nennungen. Dies entspricht dem klassischen Flavor-Konzept. Instanz

Fl a v o r f ol ge

F3-I1

F3

Fl

F3-I5

F5

F4

Tab.

1 0 . 2 - 1 . Priorität Beispiel

Bereinigte F3

F2

Fl F3 Fl

Prioritätenfolge

Fl

F5 F4 F2 Fl F3 ¡Achtung! ; Fl h a t V o r r a n g

bei der Vererbung des Graphen gemäß

vor

F3

in SCOOPS am Bild 10.2-1.

Entsprechend dieser Prioritätsfolge erbt die Instanz F5-I2 ihren Slot LS-A von dem Flavor Fl und nicht vom Flavor F3. eval>

(DESCRIBE

INSTANCE

Instance

F5-I2)

DESCRIPTION

of C l a s s

Class Variables CS : Fl

F5

:

Instance Variables LS-C : (F4 0)

:

==>

293

10. Klasse-Instanz-Modell

()

LS-A LS-B LS-D

: (Fl 0) : F5-I2 : (F5 0)

Diese Regelung der Priorität spiegelt nicht das gewünschte Zusammenwirken von Spezialisierung und Faktorisierung wieder. Wir betrachten Flavor F2 und Flavor F3 jeweils als Verfeinerung der in Fl formulierten Eigenschaften. F3und F2-Eigenschaften sind eine Spezialisierung bzw. Ergänzung von Fl-Eigenschaften. Ein namensgleicher Slot bzw. eine namensgleiche Methode in F2 oder F3 sollte für die nachfolgenden Knoten Vorrang vor der Fl-Angabe haben. Wie unsere Instanz F5-I2 zeigt, ist dies bei der obigen Regelung der Priorität nicht der Fall. Die Instanz F5-I2 erhält im Slot LS-A nicht den spezielleren F3-Wert sondern den allgemeinen Fl-Wert. Dieser Nachteil des klassischen Flavor-Konzeptes ist leicht behebbar. Zu ändern ist der Bereinigungsvorgang der Flavorfolge. Entsprechend „New Flavors" ist nicht ein zweites Vorkommen einer Flavornennung zu streichen, sondern die erste Flavornennung. Maßgeblich ist damit das letzte Vorkommen in der Flavorfolge (vgl. Tab. 10.2-2).

Instanz

Flavorfolge

Bereinigte

Prioritätenfolge

F3-I1

F3 Fl

F3 Fl

F3-I5

F5 F4 F2 Fl F3 Fl

F5 F4 F2 F3 Fl ;Achtung! ; F3 hat V o r r a n g

Tab.10.2-2.

Priorität bei der Vererbung "New Flavors" am Beispiel des gemäß Bild 10. 2-1

vor

Fl

entsprechend Graphen

10.3 Aktiver Slot Im Klassen-Instanz-Modell werden Slots nicht nur als passive Speicher für die lokale oder gemeinsame Zustandsabbildung betrachtet. Einerseits können wir aufgrund der LISP-spezifischen Gleichheit von Daten- und Programm-Repräsentation (vgl. Abschnitt 1.3) auch eine LISP-Funktion in einem Slot speichern, so daß Slots eine aktive Rolle haben können. Sie sind dann ähnlich wie Methoden einsetzbar. Andererseits können Selektor- und Mutator-Funktionen direkt Slots zugeordnet werden. Wie das Programmfragment 10.2-1 zeigt, lassen sich solche „aktiven" Slots in SCOOPS definieren. Senden wir der Instanz F3-I1 die Nachricht SET-LS-A, dann ist ihr Slot

294

II. Konstruktionen (Analyse und Synthese)

LS-A, geerbt vom Flavor F3, zu verändern. Da der Slot LS-A als aktiver Slot definiert ist, wird seine Mutator-Funktion appliziert (vgl. -Beschreibung in der Syntaxbeschreibung des DEFINE-CLASS-Konstruktes, Abschnitt 10.1). Diese Mutator-Funktion ist für LS-A als eine anonyme Funktion, wie folgt, definiert: (LAMBDA (X) (LIST X *DATE*)) Die LAMBDA-Variable X wird an den Änderungswert gebunden. Mit dieser Bindung wird die Mutator-Funktion ausgeführt. Ihr Wert ist dann der neue Wert für den Slot LS-A. eval>

(SEND

F3-I1

SET-LS-A

"1.

Aenderung") = = > ("1. A e n d e r u n g "

891214)

Neben dem Mutator ist für den Slot eine Selektor-Funktion definiert. Sie besteht aus dem folgenden LAMBDA-Konstrukt: (LAMBDA (X) (CAR X)) Wenn wir auf den Wert des Slots mit Hilfe der Nachricht GET-LS-A zugreifen, dann wird die LAMBDA-Variable X an den Slotwert gebunden, und wir erhalten den Wert der LAMBDA-Konstrukt-Anwendung. Die generierten Selektoren und Mutatoren sind durch aktive Slots beeinflußbar. Fest verankert ist in SCOOPS nur der Mechanismus aus der Nachricht, den richtigen Selektor bzw. Mutator herauszufinden. Was diese generierte Methode konkret bewirkt, ist anwendungsspezifisch durch die Option "aktiver" Slot formulierbar. eval> eval>

(SEND (SEND

F3-I1 F5-I2

G E T - L S - A ) = = > "1. A e n d e r u n g " SET-LS-B "Alles klar!") ==> ("Alles klar!"

891214)

10.4 Modifikation des Vererbungsgraphen In unserem Flavor-System (Programmfragment 10.2-1) betrachten wir die Vererbung der selbstdefinierten Methode M l . Die Methode Ml ist so formuliert, daß wir erkennen können, für welchen Flavor sie definiert wurde. Ihr WRITELN-Konstrukt schreibt den jeweiligen Flavor-Namen. Gemäß dem klassischen Flavor-Konzept konstruiert SCOOPS für die Instanz F5-I2 die bereinigte Prioritätsfolge: F5 F4 F2 Fl F3 (vgl. Tab 10.2-1). Die Methode Ml kommt danach für F5-I2 zuerst in F2 vor; der Slot LS-A zuerst in Fl. eval> (SEND F5-I2 Ml) ==> — F2 (Fl 0) () ; R ü c k g a b e w e r t d e s W R I T E L N - K o n s t r u k t e s eval>

(SEND

eval> — F2

(SEND

()

F5-I2

SET-LS-A

"Vanilla schmeckt") ==> ("Vanilla

F5-I2 Ml) = = > (Vanilla schmeckt

891214)

schmeckt"

891214)

10. Klasse-Instanz-Modell

295

Löschen wir die Methode Ml des Flavors F2, dann muß im Fall der dynamischen Vererbung die nächste Methode Ml entsprechend der Prioritätsfolge angewendet werden. Ein dynamisches Vererbungssystem hat laufend den Vererbungsgraphen zu überwachen und gegebenenfalls neu zu linearisieren, um die Änderungen zu berücksichtigen. Es gilt, alle Änderungen auf die betroffenen Knoten im Vererbungsgraphen zu übertragen („propagieren"). Bei einem statischen System, welches das Ergebnis der einmaligen Abarbeitung des Vererbungsgraphen fest für die Instanzen implementiert, führt das Löschen der Methode Ml nicht mehr zur Anwendung einer vormals überschatteten Methode Ml. Objektorientierte Erweiterungen sind im Regelfall pragmatische Lösungen zwischen dem völligen Einfrieren des Vererbungsgraphen (extrem statisches Vererbungskonzept) und dem permanenten Überwachen und sofortigen Propagieren der Änderungen (sehr dynamisches Vererbungskonzept). Sie versuchen einen Kompromiß zwischen den Vor- und Nachteilen der beiden Konzepte zu finden. Wesentliche Vorteile des statischen Vererbungskonzeptes sind der geringere Abarbeitungsaufwand für eine Nachricht (CPU- und Speicherplatz-Bedarf) und die bessere Durchschaubarkeit des Systems. Haben wir ein Objekt verstanden, dann können wir darauf bauen, daß es so bleibt, da Änderungen seine erneute Konstruktion bedingen. Das Systemverhalten ist im Vergleich zur dynamischen Vererbung leichter nachvollziehbar und einfacher prognostizierbar. Wir müssen nicht den gesamten Kontext eines Objektes laufend überwachen, um sein Verhalten zu verstehen. Vorteil des dynamischen Vererbungskonzepts ist die große Flexibilität. Eine Abarbeitung einer simplen Nachricht kann die bestehenden Vererbungsregeln modifizieren. Jede Veränderung ist im gesamten System sofort bekannt und wird, falls erforderlich, berücksichtigt. Modifikationen können vom Benutzer jederzeit eingebracht werden, ohne daß quasi eine Neukonstruktion die Konsequenz ist. In SCOOPS ist eine dynamische Methodenvererbung realisiert. Neue Slots oder das Löschen von Slots erfordern die erneute Anwendung des DEFINECLASS- und des MAKE-INSTANCE-Konstruktes. Damit orientiert sich der SCOOPS-Kompromiß stark am statischen Vererbungskonzept. eval > (DELETE-METHOD (F2 Ml)) ==> Ml eval> (SEND F5-I2 Ml) ==> ---Fl (Vanilla schmeckt 891214) eval> (DELETE-METHOD (Fl Ml)) ==> Ml eva1> (SEND F5-I2 Ml) ==> — F3 (Vanilla schmeckt 891214) eval> (DELETE-METHOD (F3 Ml)) ==> Ml eval> (SEND F5-I2 Ml) ==> ERROR . . . ;Methode Ml wurde gelöscht

296

II. Konstruktionen (Analyse und Synthese)

Hier sei der Vollständigkeit halber noch erwähnt, daß SCOOPS weitere Konstrukte bereitstellt. Zum Beispiel sind mit den Konstrukten METHODS und ALL-METHODS die eigenen und die eigenen plus geerbten Methoden eines Flavors selektierbar: eval> eval>

(METHODS F 5 ) = = > ( G E T - L S - D S E T - L S - D S E T - L S - D G E T - L S - D ) (ALL-METHODS F5) = = > (Ml S E T - L S - C GET-LS-C GET-CS SET-CS SET-LS-A GET-LS-A GET-LS-B SET-LS-B GET-LS-D SET-LS-D SET-LS-D GET-LS-D) ;Diese Doppelnennung i s t offensicht; l i e h e i n SCOOPS-Fehl er

Die folgenden Beispiele verdeutlichen weiter SCOOPS-Konstrukte. eval> eval> eval> eval> eval> eval> eval> eval> eva1> eval>

eval>

( M I X I N S F4) ==> (F2 F3) (CLASSVARS F5) ==> ( ) ( A L L - C L A S S V A R S F5) ==> (CS) ( E Q ? F5 ( N A M E - > C L A S S ' F 5 ) ) = = > # T ( N A M E - > C L A S S ( C A R ' ( F l F2 F3 F4 F 5 ) ) ) ==> #( ... ) V e k t o r , der d i e K l a s s e Fl d a r s t e l l t . Direkte Wertzuweisung einer ( S E T C V F2 CS " O t t o " ) ==> "Otto" "Class l/ariable" (GETCV F2 C S ) D i r e k t e r Z u g r i f f auf eine ==> "Otto" "Class l/ariable" ( S E N D - I F - H A N D L E S F 5 - I 2 G E T - L S - C ) = = > F4 ( S E N D - I F - H A N D L E S F 5 - I S S E T - L S - A "Emma") = = > ("Emma" 8 9 1 2 1 4 ) ( S E N D - I F - H A N D L E S F 5 - I 2 SET-NO-SLOT (WRITELN ( + 1 2 ) ) ) ==> () ¡Methode unbekannt, daher () ; a l s Rückgabewert (DESCRIBE

CLASS

F2)

==>

DESCRIPTION

NAME C L A S S VARS I N S T A N C E VARS METHODS MIXINS CLASS COMPILED CLASS INHERITED ( )

F2 (CS) (LS-A LS-B) (Ml GET-CS SET-CS S E T - L S - A GET-LS-A GET-LS-B SET-LS-B SET-LS-B GET-LS-B) (Fl) #T #T

Bei komplexen Aufgaben verstehen wir die Konstruktion erst nach Definition einer Menge von anwendungsspezifischen Objekten (Klasse, Instanzen) und Operationen (Methoden). Erst nach ihrer Definition fallen uns Ähnlichkeiten auf. Zur Bewertung einer objektorientierten Entwicklungsumgebung gehört daher die Faktorisierungsoption. Können überhaupt, und wenn ja mit welchem

10. Klasse-Instanz-Modell

I.

Modifikation

der

297

Knoten

(Klassen/Flavors)

des

Graphen

o Hinzufügen eines neuen Knotens o Löschen eines existierenden Knotens o Umbenennen eines existierenden Knotens II.

Modifikation

der

Kanten

des

Graphen

o Knoten X als S u p e r k l a s s e ("mixin") eines Knotens Y definieren. o Knoten X aus den "mixins" eines Knotens Y e n t f e r n e n , o P r i o r i t ä t ( R e i h e n f o l g e ) in d e n " m i x i n s " einer Klasse ändern. III. M o d i f i k a t i o n

des

Knoteninhaltes

(Klasse/Flavors)

o Änderungen, die Methoden betreffen - Ändern der Definition einer e x i s t i e r e n d e n - Hinzufügen einer neuen Methode - Löschen einer existierenden Methode - Umbenennen einer existierenden1Methode - Ändern des Types einer M e t h o d e 1 ^

Methode

o Ä n d e r u n g e n , die Slots betreffen - Hinzufügen eines neuen Slots - Löschen eines existierenden Slots - Umbenennen eines existierenden Slots - Ändern des v o r e i n g e s t e l l t e n Wertes eines Slots - Ändern des Typs eines Slots * W e c h s e l v o m "local S l o t " ( I n s t a n z v a r i a b l e ) zum "shared Slot" ( K l a s s e n v a r i a b l e ) * Wechsel vom "shared Slot" ( K l a s s e n v a r i a b l e ) z u m "local S l o t " ( I n s t a n z v a r i a b l e ) 1)

Flavor-Systeme unterscheiden z.B. die M e t h o d e n t y p e n : "before d a e m o n " , "primary m e t h o d " , " a f t e r d a e m o n " und "whopper" (Näheres dazu vgl. z.B. Bonin, 1989 und die dort genannte Literatur)

Tab.

1 0 . 4 Modifikationen halten beim

von Knoten, Kanten Vererbungsgraphen

und

Knotenin-

Aufwand aus schon existierenden „speziellen" Klassen „abstraktere" Klassen nachträglich definiert werden? Welchen Aufwand bereitet die Suche nach potentiellen Kandidaten (Slots und Methoden) für die Faktorisierung? Das Komplement zur Faktorisierung ist die Spezialisierung. Zunächst werden Objekte auf einem hohen Abstraktionsniveau definiert und Schritt für Schritt durch Definition von Subklassen und ihren zugeordneten Methoden verfeinert. Nach der Analyse von Objekten mit allgemeinen Eigenschaften ergänzt, bzw. modifiziert man diese Objekte, indem man sich auf die spezielleren Eigenschaften in Form von Subklassen-Definitionen konzentriert. Selten können die Ab-

298

II. Konstruktionen (Analyse und Synthese)

straktionsebenen ohne Korrekturbedarf durch einen konsequenten Spezialisierungsansatz (engl.: top down approach) oder umgekehrt durch einen konsequenten Faktorisierungsansatz (engl.: bottom up approach) geschaffen werden. In der Praxis findet für eine objektgeprägte Konstruktion ein Wechsel zwischen Faktorisierungs- und Spezialisierungs-Schritten statt. Das inkrementelle Entstehen von Klassen, Instanzen und Methoden ist ein charakteristisches Kennzeichen der objektgeprägten Vorgehensweise. Die Möglichkeiten einer effizienten und transparenten Fortschreibung eines Vererbungsgraphen sind daher leistungsbestimmend für eine solche Software-Entwicklungsumgebung. Die Tabelle 10.4-1 nennt die gewünschten Modifikationoptionen.

10.5 Zusammenfassung: Spezialisierung und Faktorisierung Eine Klasse, manchmal auch Flavor genannt, definiert die Struktur und Eigenschaften einer Menge von Objekten, den Instanzen. Eine klassenspezifische Operation heißt Methode. Sie wird über eine Nachricht aktiviert. Bei der multiplen Vererbung kann eine Instanz die Struktur und Eigenschaften (Slots und Methoden) mehrerer Klassen übernehmen („erben"). Bei namensgleichen Slots und Methoden bedarf es einer Prioritätsregelung. Die Priorität wird mit Hilfe des Vererbungsgraphen ermittelt. Bei der dynamischen Vererbung werden Veränderungen des Vererbungsgraphen permanent überwacht und sofort berücksichtigt („propagiert"). Eine statische Vererbung wertet den Vererbungsgraphen nur zum Konstruktionszeitpunkt einer Instanz aus. Eine Modifikation des Graphen erfordert dann eine Neukonstruktion der Objekte. Das von PC Scheme bereitgestellte „Scheme Object-Oriented Programming System (kurz: SCOOPS)" vererbt Methoden dynamisch und Slots statisch. Dabei sind in SCOOPS Slots nicht nur passive Speicher für die lokale Zustandsabbildung der Instanzen. Sie sind als aktive Slots mit Selektor- und Mutator-Funktionen definierbar. Ein Aspekt zur Beurteilung einer objekt-orientierten Software-Entwicklungsumgebung sind die Optionen zur Spezialisierung und Faktorisierung. Sind „spezielle" Eigenschaften, im Sinne von Subklassen, auf der Basis „abstrakter" Klassen jeder Zeit definierbar? Das Komplement zu dieser Spezialisierung ist die Faktorisierung. Hier geht es um die Möglichkeit, jederzeit Eigenschaften aus existierenden Subklassen zu „abstrakteren" Klassen zusammenfassen zu können. Charakteristisches

Beispiel für Abschnitt

10:

Im folgenden ist mit den SCOOPS-Konstrukten eine Magazinverwaltung skizziert. Der Vererbungsgraphen ist in Bild 10.5-1 dargestellt. Das Programmfragment 10.5-1 zeigt anhand dreier Beispiele das Versenden von anwendungsspezifischen Nachrichten.

10. Klasse-Instanz-Modell

299

; ; Dokument 1 0 . 5 - 1 ; ; T i t e l : P r o g r a m m f r a g m e n t z u r A b b i l d u n g des ;; gemäß B i l d 1 0 . 5 - 1 . AI E x e m p l a r i s c h s i n d d i e Methoden: A I . .1 { E i n l a g e r n } und A I . .2 { E n t n e h m e n ! d e f i n i e r t . El Eingesetzt sind die SCOOPS-Konstrukte E l , .1 D E F I N E - C L A S S E l , .2 MAKE -1NSTANCE E l ,.3 C O M P I L E - C L A S S E l . .4 DEFINE-METHOD E l .5 SEND eval>

(LOAD " S C O O P S . F S L " )

Vererbungsgraphen

= = > OK

e v a l > ( D E F I N E - C L A S S LIEFERANT ( I N S T V A R S FIRMENNAME L I E F E R S C H E I N - N U M M E R ) (OPTIONS G E T T A B L E - V A R I A B L E S ) ) = = > LIEFERANT e v a l > ( D E F I N E - C L A S S LAGERGUT (CLASSVARS (ANZAHL-TRANSAKTIONEN 0) AKTUELLE-MAGAZINVERWALTER) (INSTVARS PRODUKT-IDENTIFIKATION PRODUKT-BEZEICHNUNG DATUM-LETZTE-EINLAGERUNG DATUM-LETZTE-ENTNAHME (BESTAND 0 ) LAGERBEDINGUNG) ( M I X I N S L I E F E R A N T BESTELLUNG LAGERPLATZ) (OPTIONS GETTABLE-VARIABLES ( S E T T A B L E - V A R I A B L E S ANZAHL-TRANSAKT IONEN LAGERBEDINGUNG) I N I T T A B L E - V A R I A B L E S ) ) = > LAGERGUT e v a l > ( D E F I N E - C L A S S BESTELLUNG ( I N S T V A R S BESTELL-VORGANGS-NUMMER BESTELL-DATUM) ( O P T I O N S GETTABLE- V A R I A B L E S ) ) = = > BESTELLUNG e v a l > ( D E F I N E - C L A S S LAGERPLATZ ( I N S T V A R S RAUM REGAL FACH K U R Z Z E I C H E N - M A G A Z I N - V E R W A L T E R ) (OPTIONS GETTABLE-VARIABLES (SETTABLE-VARIABLES KURZZEICHEN-MAGAZIN-VERWALTER) I N I T T A B L E - V A R I A B L E S ) ) = = > LAGERPLATZ e v a l > ( D E F I N E - C L A S S STUECKGUT (INSTVARS LAGER-DIMENSION (KOSTEN/STUECK 0) BESTANDSWERT) ( M I X I N S LAGERGUT) (OPTIONS G E T T A B L E - V A R I A B L E S ) )

==>

STUECKGUT

e v a l > ( D E F I N E - C L A S S EISENWARE (INSTVARS ROST-FREIHEIT) ( M I X I N S STUECKGUT) (OPTIONS G E T T A B L E - V A R I A B L E S ) )

==>

EISENWARE

II. Konstruktionen (Analyse und Synthese)

300

e v a l > ( D E F I N E - C L A S S FARBE (INSTVARS VERFALLS-DATUM) ( M I X I N S S T U E C K G U T FLU E S S I G K E I T ) (OPTIONS GETTABLE-VARIABLES)) ==>

FARBE

eval> (DEFINE-CLASS FLUESSIGKEIT (INSTVARS BEHAELTER-KLASSE ZUEND-TEMPERATUR) ( M I X I N S LAGERGUT) (OPTIONS GETTABLE-VARIABLES)) ==> FLUESSIGKEIT e v a l > ( D E F I N E - C L A S S MAGAZIN-VERWALTER ( I N S T V A R S NAME B E F U G N I S - K L A S S E K U R Z Z E I C H E N ) (OPTIONS GETTABLE- VARIABLES SETTABLE- VARIABLES I N I T T A B L E - V A R I A B L E S ) ) ==> MAGAZIN-VERWALTER e v a l > (DEFINE E0001 (MAKE-INSTANCE EISENWARE 'PRODUKT-IDENTIFIKATION

'E0001))

==>

E0001

e v a l > (DEFINE F0001 ( M A K E - I N S T A N C E FARBE 'PRODUKT-IDENTIFIKATION

'F0001))

==>

F0001

e v a l > ( D E F I N E SCHMIDT (MAKE - 1 N S T A N C E M A G A Z I N - V E R W A L T E R 'NAME " H a n s O t t o S c h m i d t " ' B E F U G N I S - K L A S S E ' ( ( E 1) (F 4 ) ) 'KURZZEICHEN ' S C H ) ) = = > SCHMIDT e v a l > ( D E F I N E MUELLER (MAKE-INSTANCE MAGAZIN-VERWALTER 'NAME " W e r n e r M u e l l e r " ) ) = = > MUELLER eval>

(COMPILE-CLASS

LAGERGUT)

==>

LAGERGUT

e v a 1 > ( D E F I N E - M E T H O D (LAGERGUT E I N L A G E R N ) ( ) (BEGIN (WRITELN " E i n l a g e r u n g von: " PRODUKT-IDENTIFIKATION) (WRITELN " = = = = = = = = = = = = = = = = " ) (LET ((ZUGANG (BEGIN (PRINC " Z u g a n g s m e n g e : ") (READ))) (KOSTEN (BEGIN (PRINC " K o s t e n pro S t u e c k : ") (READ)))) ( S E T ! KOSTEN/STUECK (/ (+ ( * KOSTEN/STUECK BESTAND) ( * KOSTEN Z U G A N G ) ) ( + BESTAND ZUGANG))) ( S E T ! BESTAND ( + BESTAND ZUGANG)) ( S E T C V LAGERGUT A N Z A H L - T R A N S A K T I O N E N ( 1 + (GETCV LAGERGUT A N Z A H L - T R A N S A K T I O N E N ) ) ) (WRITELN " E i n l a g e r u n g g e b u c h t ! " ) ) ) ) ==> EINLAGERN

10. Klasse-Instanz-Modell eval>

301

( D E F I N E - M E T H O D ( L A G E R G U T E N T N E H M E N ) () (BEGIN (WRITELN "Entnahme von: " PRODUKT-IDENTIFIKATION) (WRITELN "================") ( L E T ( ( A B G A N G ( B E G I N ( P R I N C " E n t n a h m e m e n g e : ") (READ)))) (COND (( ENTNEHMEN

; ; ; ; Bei spi e l e eval > ( S E N D E 0 0 0 1 Einlagerung

EINLAGERN) von: E0001

==>

Z u g a n g s m e n g e : 100 Kosten pro S t u e c k : 12.00 Einlagerung gebucht!

()

eval> (SEND E0001 E N T N E H M E N ) Entnahme von: E0001

==>

E n t n a h m e m e n g e : 40 Entnahme gebucht! () eval> (SEND Einlagerung

E0001 EINLAGERN) von: E0001

==>

Z u g a n g s m e n g e : 100 Kosten pro Stueck: 13.00 Einlagerung gebucht! () eval>

(SEND

E0001

GET-KOSTEN/STUECK)

==>

12.625

ProgrammfragmentlO.5-1.Magazinverwa1tungrealisiert (vgl. Bild 10.5-1.) SCOOPS-Konstrukten

Legende: ::= K l a s s e , N a m e ::= I n s t a n z I ... [ ...

in

Fettdruck

} ::= M e t h o d e ] ::= K l a s s e n v a r i a b l e ( " s h a r e d s l o t " ) ::= I n s t a n z v a r i a b l e ("local s l o t " ) > ::= V e r e r b u n g s r i c h t u n g

mit

302

II. Konstruktionen (Analyse und Synthese)

MAGAZINVERWALTER IBerechti gung) Name, Befugnisklasse, Kurzzei c h e n BESTELLUNG {Bestellen} Bestel1 V o r g a n g s n u m m e r , Bestell datum LAGERGUT ¡Einlagern) ; Entnehmen) [Anzahl-Transaktionen] [Aktuelle-Magazinverwalter] Produkt-Identifikation Produkt-Bezeichnung Datum-Letzte-Einlagerung Datum-Letzte - Entnahme. Lagerbedi ngung Bestand

-IEFERANT ; Li e f e r n 1 Fi r m e n n a m e Li e f e r scheinnummer

STUECKGUT Lagerdimension Kosten/Stueck Bestandswert

FLUESSIGKEIT Behaelterklasse Zuendtemperatury

FARBE Verfal1sdatum)

E0001

...

E9999

3ild 1 0 . 5 - 1 .

F0001

Magazinverwaltung: Skizze eines Vererbungsgraphen

.

F9999

III. Konstruktionsempfehlungen

Das Primärziel der Softwarekonstruktion ist eindeutig und unstrittig. Es ist: o nützliche Software zu konstruieren, d.h. Software, die eine nützliche Aufgabe erledigt, wobei die Erledigung vereinfacht oder erst ermöglicht wird, o und zwar hinreichend fehlerfrei und termingerecht. Von diesem Primärziel leiten sich eine Vielzahl von Sekudärzielen ab, wie z.B.: o Konstruiere Programm(teil)e in gewünschter Qualität, o Konstruiere Programm(teil)e, die wiederverwendbar sind, o Konstruiere Programm(teil)e, die sich pflegen und ergänzen lassen, o Konstruiere Programm(teil)e, die dokumentierbar und durchschaubar sind, o ... Diese Sekundärziele sind kein Selbstzweck. Wir fordern daher z.B. keine qualitativ hervorragende Dokumentation, sondern eine „angemessene", also am ökonomischen Primärziel „Nützlichkeit" gemessene Dokumentation. Die Dokumentation ist stets, auch im juristischen Sinne, wesentlicher Bestandteil der Konstruktion. Software ohne Dokumentation ist vergleichbar mit einer Wohnung ohne Fenster. Fehlt ein wesentlicher Bestandteil, dann ist die Übergabe unvollständig und das „Produkt" kaum nutzbar. Ein wesentlicher Bestandteil ist (häufig) ein wesentlicher Kostenfaktor. Daher wollen wir aus Kostenerwägungen das einmal „Erarbeitete" möglichst mehrfach nutzen. Aus diesem Wiederverwendungsziel hat sich das Baukasten-Konstruktionsprinzip entwickelt. Dieses in vielen Ingenieuerdisziplinen angewendete Prinzip fordert, daß eine Konstruktion aus möglichst wenigen standardisierten Bausteinen zu realisieren ist. Das Baukasten-Konstruktionsprinzip hat einen analytischen und einen synthetischen Aspekt. Das analytische Problem lautet: Wie kann eine gegebene Konstruktion durch eine Konstruktion gleicher Leistung ersetzt werden, die nur Konstrukte aus der vorgegebenen Konstruktemenge des genormten Baukastens verwendet? Das synthetische Problem lautet: Welche Konstrukte aus der vorgegebenen Menge werden benötigt, und welche Verknüpfungen dieser Konstrukte sind vorzunehmen, um eine Konstruktion mit der spezifizierten Leistung zu erhalten? Beide Aspekte betreffen die gesamte Konstruktion einschließlich Spezifikation und Dokumentation, also nicht nur die Konstruktion des Quellcodetextes. In diesem Kapitel diskutieren wir Empfehlungen für eine durchschaubare („transparente") Dokumentation (Abschnitt 11). Fragen der Benennung von Konstrukten (Abschnitt 11.1) und der Kommentierung im Quellcodetext werden ver-

304

III. Konstruktionsempfehlung

tieft (Abschnitt 11.2). Dabei erörtern wir die Bezugnahme auf vorhergehende und nachfolgende Dokumente (Abschnitt 11.3). Ein kleines Regelsystem verdeutlicht diese Empfehlungen (Abschnitt 11.4). Es weist Grundzüge der Konstruktion von sogenannten Produktionssystemen auf (z.B. im Sinne von OPS5, vgl. Forgy, 1981). LISP ist (wie jede höhere Programmiersprache) zur Spezifikation einer Aufgabe einsetzbar. Wir können mit LISP nicht nur die Lösung einer Aufgabe formulieren, sondern auch den Lösungsraum beschreiben. Die Spezifikation des Lösungsraums befaßt sich, etwas salopp formuliert, mit dem Problem „was gesollt werden soll" (kurz: mit dem WAS), d.h. mit der deskriptiven Aufgabenpräzisierung. Die Programmiersprache im engeren Sinne zielt auf das Problem, wie das Spezifizierte realisert wird (kurz: auf das WIE). Die Grenze zwischen WAS und WIE ist fließend. Das realisierbare WAS enthält in der Praxis (mindestens implizite) WIE-Aussagen. Eine Programmiersprache kann die Grenze in Richtung Lösungsraumbeschreibung zumindest im Umfang einer Pseudocode-Notation verschieben. Der Vorteil einer Pseudocode-Notation liegt in ihrer Flexibilität und der Einsatzmöglichkeit über mehrere Projektphasen. Sämtliche Eigenschaften und Merkmale eines Programms sind jedoch so nicht spezifizierbar, z.B. können keine Aussagen zur Antwortzeit, zum Betriebsmittelbedarf oder zu Realtime-Eigenschaften notiert werden. Programmiersprachen ersetzen daher nicht spezielle Spezifikationssprachen (z.B. SLAN-4, vgl. Beichter u.a., 1981). Die Empfehlungen zum Spezifizieren mit LISP basieren auf einer Beschreibung der Import- und Export-Schnittstellen. Ihre schrittweise Verfeinerung und Präzisierung wird anhand einer Checkliste vollzogen (Abschnitt 12.2). Vorab erörtern wir die Randbedingungen für das Spezifizieren in LISP-Notation (Abschnitt 12.1). Die Anforderungen an eine Konstruktion wie z.B. Effizienz, Transparenz, Wartbarkeit oder Portabilität bestimmen die Wahl der geeigneten Abbildungsbasis („Datenrepräsentation", vgl. Kapitel II). Die gewählte Abbildungsbasis erfüllt nicht alle gewünschten Eigenschaften gleich gut, da es sich teilweise um konkurrierende Eigenschaften handelt. Verschieben sich die Bewertungsgewichte, dann kommt ein Wechsel in Betracht. Einen solchen Wechsel der Abbildungsoption erörtern wir mit einem Beispiel aus der Graphik (Abschnitt 13). Dazu definieren wir Zeichenkonstrukte einer "Turtle Graphik" (Abschnitt 13.1). Ein Zeichengenerator für Struktogramme ist zunächst als „Nachrichtenversand"Lösung in SCOOPS definiert (Abschnitt 13.2). Zur Verbesserung der Antwortzeit wechseln wir zu einer Lösung mit dem DEFINE-STRUCTURE-Konstrukt und einer zugeordneten Verzweigungsfunktion (Abschnitt 13.3). Zur reinen Freude ist in diese Lösung auch eine Fraktalfigur eingebaut (Abschnitt 13.4).

11. Transparenz der Dokumentation

305

11. Transparenz der Dokumentation Konstruieren heißt dokumentieren. Das Ergebnis dieser Tätigkeit, die Konstruktion, ist ebenfalls ein Dokument. Softwarekonstruktion bedeutet, eine vorgegebene Kette von Dokumenten zu produzieren. Das Kostenbudget und die Termin vorgaben bedingen, daß wir diese dominierende Aufgabe nicht mißinterpretieren: „Je mehr Dokumente produziert werden, umso besser". Häufig gibt es zu viele (unnütze, nicht zweckorientierte) Dokumente (vgl. z.B. Schnupp/Floyd, 1979, S.205). Bei der Dokumentation geht es nicht um die Menge, sondern um die Qualität, d.h. um die Transparenz in Bezug auf ein schnelles und hinreichend genaues Verstehen der Konstruktion. Die populäre Dokumentationsstrategie orientiert sich an der Maxime: Erst Entwurf, dann Reinschrift! Damit ist Dokumentieren stets eine nachträgliche Arbeit. Die Dokumentation, die erst nach vollbrachter Tat, d.h. nach Abschluß der eigentlichen Konstruktion, entsteht, vergeudet Zeit und ist (fast) nutzlos. Zu solchen Nacharbeiten zählen insbesondere (vgl. z.B. Rogers, 1988, S.140): o die verbale Beschreibung der Kontrollstruktur, o Programmablaufpläne (PAP's) und o das Einfügen von Kommentaren. Ab einer gewissen Komplexität der Konstruktionsaufgabe sind: o vorbereitende Dokumente (Lastenheft, Pflichtenheft, Begründungen für das gewählte Konzept etc.), o Konstruktions-begleitende Dokumente (Testprotokolle, Integrationspläne etc.) und o nachbereitende Dokumente (Wartungsrichtlinien, Installationsanweisungen etc.) zu erstellen. Wir untergliedern daher die Dokumentation in Dokumente, die das Ergebnis (die Software im engeren Sinne) beschreiben und in Dokumente, die den Prozeß ihrer Erstellung nachweisen. Dokumentation ::= (+ Konstruktions-Dokumentation Konstruktions-Prozeßdokumentation) In der Praxis entstehen Dokumente mehr oder weniger iterativ. Ideenskizzen werden ergänzt, verworfen, weiterentwickelt. Bei diesem (Iterations-)Prozeß ist die allgemein übliche Arbeitsteilung in „so-eben-mal-skizziert" und in „Reinschrift-mit-ausreichender-Erklärung" auf kreative Ausnahmefälle zu begrenzen. Jede Aktivität im Rahmen des Konstruktionsprozesses ist gleich so zu notieren, daß sie Teil der Dokumentation sein kann. Unstrittig ist es ein echtes Problem,

306

III. Konstruktionsempfehlung

wie die entscheidenden Konstruktions-Absprachen, vielleicht notiert auf einem Bierdeckel, ohne Fehler in die Konstruktions-Prozeßdokumentation gelangen (zumal keiner den Mut haben wird, den Bierdeckel dort abzuheften). Zu fordern ist daher, jede Idee unverzüglich rechnergestützt zu notieren, entweder direkt in ein LISP-System oder in einen Texteditor. Sowohl in den ersten Phasen des Konstruktions-Prozesses als auch in den Phasen Wartung/Pflege und Fortentwicklung hat man das Problem, sich auf die wesentlichen Aussagen zu konzentrieren. Im ersten Fall fehlt eine stabile Basis (Spezifikation), im zweiten sucht und vergleicht man und kann die Informationsfülle nicht verarbeiten. Beide Situationen zeigen, daß es notwendig ist, unsere Aussagen in einer auf das Wesentliche verkürzten Form zu notieren. Was Wesentlich ist richtet sich: o nach den Adressaten und o dem Zweck des Dokumentes. Adressaten-Ausrichtung:

Die einzelnen Dokumente wenden sich an unterschiedliche Adressaten, z.B. an Benutzer, Operateure, Auftraggeber, Projektmanager, Systemdesigner, Programmierer, Vertriebspersonal etc. Kaum jemand von ihnen liest eine Dokumentation gern, freiwillig oder mehr als nötig. Schon aus diesem Grunde ist die Dokumentation in „bewältigbare" Abschnitte zu gliedern, die von den einzelnen Adressaten tatsächlich gelesen werden.

Wir formulieren keine Zeile ohne Annahmen über das Vorwissen und die vertraute Terminologie des Lesers. Zumeist unterstellen wir implizit ein unscharf durchdachtes Adressatenmodell. Notwendig ist jedoch hinreichende Klarheit über das annehmbare Leserverständnis. Welche Aspekte zu beschreiben sind und welche unerwähnt bleiben können, weil sie zum angenommenen „Leser-

11. Transparenz der Dokumentation

307

fundus" gehören, können wir nur anhand unseres unterstellten „Norm"-Adressaten entscheiden. Ist der Leser z.B. ein LISP-Programmierer, der den LISP-Quellcodetext zu pflegen hat, dann ist anzunehmen, daß dieser Adressat, die eingebauten, primitiven LISP-Konstrukte erkennt und versteht. Eine Kommentarzeile: „Hier wird die Datei X geöffnet" nach einem OPEN-Konstrukt ist daher unsinnig. Sinnvoll wäre z.B. die Erläuterung: „X ist als Datei abgebildet, damit der maximale Arbeitsspeicherbedarf kleiner als 1 MB bleibt". Da das Verständnis und der Kenntnisstand des Lesers mit der Benutzung der Dokumention wächst und sich infolgedessen seine Terminologie verändert, wäre eine Anpassung der Dokumentation an diese Veränderung wünschenswert. Soweit das Dokument rechnergestützt geführt wird, ist die Einprogrammierung verschieden kundiger Leserklassen (z.B. Anfänger, Eingeübter, Experte) ein üblicher Ansatz. Anzustreben ist eine Aktivitätsverschiebung. An die Stelle eines Dokumentes, das gelesen werden kann, tritt ein Dokumentations-Programm, das den Inhalt zugeschnitten auf den aktuellen Wissenstand des Lesers präsentiert. Lösungsansatz ist ein Dialog mit dem Leser, der Aufschluß über sein aktuelles Verständnis gibt (ohne dabei die Datenschutzaspekte zu mißachten!). Diese dynamische Klassifikation des Lesers wird genutzt, um eine Anpassung der anzuzeigenden Formulierungen zu realisieren. Während der Adressat „Rechner" den beherrschten Sprachschatz eindeutig in Form der verarbeiteten Konstrukte und Konstruktionen "offenbart", ist beim jeweiligen Leser ungewiß, ob dieser eine Formulierung tatsächlich hinreichend genau versteht. Kennt z.B. der Programmierer den Begriff „Rechnungsabgrenzungs-Konto"? Assoziiert er damit die Definition, die an anderer Stelle (vielleicht außerhalb der Dokumentationskette) in der Terminologie der Kaufleute formuliert wurde, oder muß dieser Begriff in dem betreffenden Dokument erst erklärt werden, oder genügt ein Verweis auf eine einschlägige Fundstelle? Jede einzelne Formulierung hat sich an der Antwort auf die Frage zu orientieren: Welche Assoziationen kann der Adressat beim Lesen haben, welche davon sind nicht präzise genug oder stehen sogar im Widerspruch zum Gemeinten? Es geht um die Entscheidung: Was ist unbedingt zu beschreiben, und was kann für sich selbst sprechen? Je mehr für sich selbst spricht, um so kürzer wird das Dokument. Die Umfangreduktion ist im Hinblick auf die Lesemotivation ein positives Ziel. Wir wollen daher nur etwas (zusätzlich) erläutern, wenn der Leser ohne diese Erläuterung die Aussage nicht oder falsch versteht. Damit beugen wir einer kostenintensiven, unnützen und zum Teil schädlichen Aufblähung der Dokumentation vor. Prinzipiell ist aus dem Quellcodetext (mit Kenntnis des jeweiligen LlSP-Systems) die Abarbeitung nachvollziehbar. Das WIE ist, wenn auch recht mühsam, rekonstruierbar. Aus welchen Gründen wir ein Konstrukt gerade so und nicht anders definieren, ist vom Leser nicht ohne weiteres rekonstruierbar. Ein paar Sätze zur WARUM-Darstellung sind daher oftmals hilfreicher als ausführliche

308

III. Konstruktionsempfehlung

Erläuterungen zum WIE. Wegen der Nicht-Rekonstruierbarkeit besteht eine besondere Pflicht, das WARUM explizit zu notieren. Zweck-Ausrichtung: Ein Dokument ist auf die Zwecke auszurichten, die es erfüllen soll. Gliederung, Inhalt und Gestaltung (Layout) des Dokuments sind zweckorientiert zu erarbeiten. Ein Pflichtenheft, als eine Vertragsgrundlage zwischen Auftraggeber und Softwarekonstrukteur wird so gestaltet, daß Inhaltsänderungen nur über schriftliche Zusatzvereinbarungen möglich sind (d.h. z.B. gebundene Seiten mit durchlaufender Seitennumerierung gemäß „Seite i von n Seiten"). Ein Benutzerhandbuch ist stattdessen eine Loseblattsammlung, um Ergänzungen und Korrekturen an den richtigen Platz durch Austausch der Seiten aufnehmen zu können. Ein Formulierungskonflikt besteht zwischen der Zweckausrichtung und dem Baukasten-Konstruktionsprinzip (Mehrfachverwendbarkeit). Die Aussagen sind so zu formulieren, daß eine Mehrfachverwendung einzelner Passagen und/oder ganzer (Teil-)Abschnitte in anderen Dokumenten möglich ist. Beispielsweise sollen Textpassagen des Pflichtenheftes auch für Vertriebs- und EinführungsDokumente verwendbar sein. Diese Forderung nach Mehrfachverwendung verlangt möglichst kontextunabhängig zu formulieren. Die Zweckausrichtung führt jedoch zur Kontextintegration. Zu vermeiden ist eine Kontextabhängigkeit, die sich nicht aus der Zweckausrichtung ableitet. Für ein Plichtenheft sind daher z.B. die Aussagen nicht in der Zukunftsform zu notieren, sondern im Präsens. Weil es zunächst um etwas Zukünftiges geht, wählt der Autor spontan die Zukunfsform („Das Programm R E C H N U N G wird die Mehrwertsteuer berechnen"). Erst die Notation im Präsens bringt die Herauslösung aus dem Kontext „zukünftige Eigenschaft". („Das Programm R E C H N U N G berechnet die Mehrwertsteuer"). Für große Konstruktionen (vgl. Tab. 12-3) sind Dokumentationsregeln verbindlich vorzuschreiben. Im Bereich der öffentlichen Verwaltung sind es z.B. die KoopA-ADV-Rahmenrichtlinien (Näheres dazu vgl. z.B. Bonin, 1988). Solche Richtlinien bestimmen welchen Inhalt die einzelnen Dokumente des Konstruktionsprozesses haben (sollen!). Sie ermöglichen erst eine effiziente Kooperationen bei einer größeren Anzahl von Beteiligten und Betroffen. Allerdings motiviert eine Richtlinie zum Schreiben, bei dem ein schnelles Abhaken einer lästigen Vorgabe in den Mittelpunkt rückt. Die angestrebte Vollständigkeit wird in der Praxis mit mehr oder weniger aussageleeren Phrasen erfüllt. Die pragmatische Empfehlung lautet daher: „Sei kein Formalist, der Richtlinien abhakt, sondern schreibe adressaten- und zweck-ausgerichtet!". Im folgenden wird die Transparenz des Quellcodetextes behandelt. Dazu erörtern wir die drei Dokumentationsprobleme:

11. Transparenz der Dokumentation

309

o Benennung von Konstrukten, o Kommentierung und o Vor- und Rückwärtsverweise. Diese Punkte sind von praktischer Bedeutung, wenn eine vorgegebene Spezifikation umzusetzen ist. Empfehlungen zur Erarbeitung einer solchen Spezifikation behandeln wir anschließend (Abschnitt 12).

11.1 Benennung Der „Name" eines Konstruktes sei nicht irgendein Symbol, sondern benennt diejenige Eigenschaft, die es von allen anderen unterscheidet. Offensichtlich ist der Name bedeutsam für die Transparenz der gesamten Dokumentation (incl. Quellcodetexte). Mit der Namensvergabe für „Objekte", Funktionen, Makros, Variablen, Konstanten etc. sind zumindest zwei Aspekte verknüpft: o mnemotechnischer Aspekt Ziel ist eine leicht einprägsame Benennung im Sinne eines Verweises auf das „Gemeinte" (Objekt) in der „realen" Welt. Es geht um eine Korrelation zwischen der Bezeichnung des „realen" Objektes und der Bezeichnung des „formalen" Objektes (vgl. Abschnitt 2.1) o konstruktionsbedingter Aspekt Ziel sind Aussagen zu konstruktions-spezifischen Aufgaben und Eigenschaften (vgl. Abschnitt 4); z.B. Informationen zur Beantwortung der Fragen: - Ist es ein eingebautes Konstrukt? - Ist es ein risikoreiches Konstrukt?

Die Benennung ist zusätzlich abhängig vom jeweiligen Denkrahmen („Paradigma") des Konstrukteurs (vgl. Abschnitt 4). Daher wird beispielsweise empfohlen, Funktionsnamen so zu wählen, „daß sie implizit Annahmen über ihre Arbeit

310

III. Konstruktionsempfehlung

in Form von , Verträgen' über Eingabe-Ausgabe-Relationen aufdrängen." (Stoyan/Görz, 1984, S.56). Im objektgeprägten Denkrahmen sind Operationen mit Verben und Operanden mit Substantiven zu benennen. Im imperativgeprägten Denkrahmen haben alle Bezeichnungen Befehlscharakter an den ausführenden Rechner („Kasernenhof'-Formulierungen). Orientiert am jeweiligen Denkrahmen wären für ein Konstrukt, das z.B. den Firmenkurznamen eines Kunden selektiert, folgende Benennungen zu wählen: Imperativgeprägte Konstruktion: GIB-FIRMEN-KURZNAME Funktionsgeprägte Konstruktion: KUNDE->FIRMEN-KURZNAME Objektgeprägte Konstruktion: SELEKTIEREN-FIRMEN-KURZNAME Als konstruktionsbedingte Informationen wollen wir mit der Benennung z.B. vermitteln: o den (Daten-)Typ Ist es eine Funktion, eine Variable, ein Makro etc.? Z.B.: F-BILANZABGLEICH ;Funktion V-BILANZABGLEICH ;Variable M-BILANZABGLEICH ;Makro o den aufgabensspezifischen Typ Ist es ein Konstruktor, Prädikat, Selektor oder Mutator? (vgl. Tab. 11.1-2) o die Flußrichtung In welche Richtung fließen die Daten? Z.B.: INPUT-KUNDENDATEI oder OUTPUT-KUNDENDATEI o den Wertebereich Ist es ein Zahlatom oder String? Z.B.: ZAHL-BILANZ-AKTIVA oder STRING-BILANZ-AKTIVA o die Aufgabe Ist es eine LAMBDA-Variable, ein Argument, eine Hilfsvariable etc.? Z.B.: BILANZ. AKTIVA ;Unterstrich für LAMBDA-Variable BILANZ-AKTIVA ¡Bindestrich für Argument BA ;weniger als 3 Buchstaben für ; temporäre Hilfsvariable o die Lebensdauer (Persistenz) Ist der Speicherort eine Stamm-, Bewegungs-, Hilfs-Datei oder der Arbeitsspeicher? Z.B.: DB-KUNDE ;Stammdatei (Datenbank) WS-KUNDE; Arbeitsspeicher ; (working storage) Empfehlungen zur Namensvergabe beziehen sich häufig auf konstruktionsspezifische Informationen. So wird empfohlen, einen Konstruktor oder Selektor derart zu bezeichnen, daß man den Datentyp ersehen kann, den die Schnittstelle des Konstruktes erwartet. Ein typisches Beispiel für einen solchen Namen ist:

11. Transparenz der Dokumentation

311

ARGLIST!FIRST-ARG (Steele, B„ 1980, S.176). Oder es wird präzisiert, in welchen Fällen die Selektoraufgabe vor der Prädikataufgabe im Namen Vorrang hat. In Scheme haben daher die Konstrukte MEMQ, MEMV, MEMBER, ASSQ, ASSV und ASSOC kein Fragezeichen am Namensende, weil sie nützliche Werte und nicht nur den Standardwahrheitswert #T zurückgeben (vgl. Rees/Clinger, 1986). Sind viele Aspekte mit dem Namen zu vermitteln, dann führt dies zwangsläufig zu sehr langen Namen. Lange Namen vergrößern den Umfang der Dokumente. Sie führen darüber hinaus schnell zu Problemen bei einer begrenzten Zeilenlänge. Vor- und Nachteile bezogen auf die Namenslänge zeigt Tabelle 11.1-1. B e w e r t u n g s k r i teri um

langer

11.1-1

Namenslänge:

Vorteile

-

-

(+)

und

(-)

Beispiel:

einen Konstruktor: ein Prädikat: ein d e s t r u k t i v e s Konstrukt (Mutator): für eine b e s o n d e r e (globale) Variable: eine eingebaute Konstante: 11.1-2

Nachteile

:[][]

für für für

Tab.

Namen

+ + +

-

Empfehlung:

für

kurzer

+

(mnemotechnische) Aussagekraft Lesbarkeit/Leseaufwand Schrei baufwand Schreibfehlerrisiko Tab.

Namen

AFFIX-Empfehl

< p r ä f i x> < s u f f i x>

:= M A K E := ?

MAKE-WINDOW E0F-0BJECT?

< s u f f i x>

:=

SET-CDR!



:= * := *

< p r ä f i x>

:= #i

!

*GRAPHICS-COLORS* #! EOF

ungen

Ein Name, insbesondere ein langer Name, ist zu strukturieren. Wir unterteilen einen Namen in eine Vorspann (Präfix), den Hauptteil (Stamm) und einen Nachspann (Suffix). Die Tabelle 11.1-2 zeigt die verwendete Regelung für Präfix und Suffix, die zusammen als Affix bezeichnet werden. Die Optionen zur Wahl und Strukturierung eines zweckmäßigen Namens erörtern wir anhand eines Beispiels. Es sei der Kurzname der Firma aus den Kundendaten zu selektieren. Dazu betrachten wir sowohl die Namen bei der Funktionsdefinition, also die Benennung der LAMBDA-Abstraktion, als auch die Funktionsapplikation, also zusätzlich die Namen der Argumente. Uns interessieren

312

III. Konstruktionsempfehlung

die Wirkungen der unterschiedlichen Benennung im Hinblick auf die Transparenz der gesamten Konstruktion. Dazu betrachten wir exemplarisch vier Entscheidungsprobleme: o die Informationsaufteilung zwischen dem Funktionsnamen und den Namen für die LAMBDA-Variablen, o das (standardisierte) Einbeziehen von konstruktionsbedingten Informationen, o die Informationsaufteilung zwischen Namen und einem zugeordnetem (Daten-)Lexikon und o die Sortierfähigkeit für ein schnelles Recherchieren. Aus Vereinfachungsgründen liegen die Kundendaten in unserem Beispiel als Liste vor, wobei der Firmenkurzname als optionales zweites Listenelement angegeben ist. Zwei Kunden sind vorab folgendermaßen definiert: eval> (DEFINE KUNDE -1 '("Software AG" "SAG")) ==> KUNDE -1 eval> (DEFINE KUNDE - 2 '("Siemens AG")) ==> KUNDE-2 Ist der Firmenkurzname nicht angegeben, dann ist ein Ersatzwert zu selektieren. Dieser ist, wie folgt, definiert: eval> (DEFINE UNBEKANNTER-KURZNAME* " - ? - " ) ==> *UNBEKANNTER-KURZNAME* Problem 1:

Informationsaufteilungzwischen Funktionsname und Namen der LAMBDA-Variablen

Bei der Benennung des gewünschten Selektors ist zu entscheiden, welche Informationen im Funktionsnamen und welche in den Namen der LAMBDA-Variablen zu hinterlegen sind. Lösung A: o Funktionsname Haupt-Informationsträger o Kurze beliebige Namen für die LAMBDA-Variablen eval> (DEFINE KUNDEN->FIRMEN-KURZ NAME/ERSATZ (LAMBDA ( X . Y ) (OR (LIST-REF X 1) (LIST-REF Y 0 ) ) ) ) ==> KUNDEN->FIRMEN-KURZNAME/ERSATZ eval> (KUNDEN->FIRMEN-KURZNAME/ERSATZ KUNDE -1 *UNBEKANNTER-KURZ NAME*) ==> "SAG" Lösung B: o Funktionsname und Namen der LAMBDA-Variablen sind gleichbeteiligte Informationsträger eval> (DEFINE FIRMEN-KURZNAME (LAMBDA (KUNDE . ERSATZ_KURZNAME)

11. Transparenz der Dokumentation (OR

(LIST-REF (LIST-REF

313

KUNDE 1 ) ERSATZ_KURZNAME

0)))) ==>

eval>

(FIRMEN-KURZNAME

FIRMEN-KURZNAME

KUNDE - 1 U N B E K A N N T E R - K U R Z N A M E * )

Problem 2: Einbeziehen von konstruktionsbedingten

==>

"SAG"

Informationen

Die Namen in den Lösungen A und B enthalten keine Typ-Aussagen. So ist nicht erkennbar, daß die Funktion ein Selektor ist und ihr erstes Argument eine Liste sein muß. Lösung C: o Präfix des Funktionsnamens verweist auf den Typ Selektor o LAMBDA-Variablen geben Informationen in Bezug auf die zulässigen Argumente eval>

(DEFINE SELEKTIERE-FIRMEN-KURZNAME (LAMBDA ( L I S T E . E R S A T Z . S T R I N G ) (OR ( L I S T - R E F L I S T E 1 ) ( L I S T - R E F E R S A T Z _ S T R I N G 0 ) ) ) ) = = > S E L E K T I E R E - F I R M E N - KURZNAME

eval>

(SELEKTIERE-FIRMEN-KURZNAME

KUNDE - 2

UNBEKANNTER-KURZNAME*)

Diese Lösung ist auch weniger imperativgeprägt, wie folgt, formulierbar: eval>

(DEFINE SELEKTION-FIRMEN-KURZNAME (LAMBDA ( L I S T E . E R S A T Z . S T R I N G ) (OR ( L I S T - R E F L I S T E 1 ) ( L I S T - R E F E R S A T Z _ S T R I N G 0 ) ) ) ) = = > S E L E K T I O N - F I R M E N - KURZ NAME

eva1>

(SELEKTION-FIRMEN-KURZNAME

KUNDE - 2

UNBEKANNTER-KURZNAME*)

Die Information, daß die Funktion ein Selektor darstellt, ist sicherlich kürzer mit einem vorgegebenen Präfix, z.B. GET-, notierbar. Die Information, daß die erste LAMBDA-Variable an eine Liste zu binden ist, kann ebenfalls kürzer notiert werden. Wir können für eine ganze Konstruktion vereinbaren, daß z.B. eine LAMBDA-Variable mit dem Namen L, stets ein Argument vom Typ Liste erfordert. Die zweite LAMBDA-Variable im Beispiel verweist auf einen String. Diese Information ist in diesem Funktionskörper unerheblich. Die LAMBDA-Variable ERSATZ_ STRING wird aufgrund der Punkt-Paar-Notierung in der Schnittstelle des Konstruktes stets an eine Liste gebunden (vgl. Abschnitt 6.3). Das Konstrukt (LIST-REF E R S A T Z . STRING 0) selektiert das erste Element, unabhängig davon, von welchem Typ es ist. Wir können daher in diesem Konstrukt auf den Namensteil STRING verzichten. Lösung D: o Standard-Selektor-Präfix beim Funktionsnamen o Standard-Namen für LAMBDA-Variablen mit Informationen in Bezug auf die zulässigen Argumente (soweit erforderlich)

314 eval>

eval>

III. Konstruktionsempfehlung (DEFINE GET-FIRMEN-KURZNAME (LAMBDA (L . E R S A T Z ) (OR ( L I S T - R E F L 1 ) ( L I S T - R E F (GET-FIRMEN-KURZNAME

KUNDE-2

ERSATZ 0 ) ) ) ) ==> GET-FIRMEN-KÜRZNAME UNBEKANNTER-KURZNAME*) = = >

" - ? - "

Problem 3: Informationsaufteilung zwischen Namen und Lexikon Nachteilig ist, insbesondere bei häufiger Applikation der Lösung D, daß der Name GET-FIRMEN-KURZNAME mit 19 Zeichen recht lang ist. Wir verkürzen ihn daher. Zusätzlich präzisieren wir bei der LAMBDA-Variable ERSATZ, wofür diese als Ersatz dienen soll. Wir nennen sie jetzt NAME und verweisen mit dem Sonderzeichen ? als Präfix auf ihre Aufgabe. Sie ist Ersatz für den Firmenkurznamen, wenn dieser in den Kundendaten unbekannt ist. Lösung E: o Verkürzung der Namen von Lösung D o Nutzung von Sonderzeichen eval>

eval>

(DEFINE GET-F-K-NAME (LAMBDA (L . ?_NAME) (OR ( L I S T - R E F L 1 ) (GET-F-K-NAME

(LIST-REF

?_NAME

0)))) ==>

KUNDE-1 ^UNBEKANNTER-KURZNAME*)

GET-F-K-NAME ==>

"SAG"

Dieses Verkürzen führt rasch zu Namen, deren Bedeutung sich niemand merken kann. Wofür steht F und wofür K ? In der Praxis ist dann permanent in einem (Daten-)Lexikon nach der eigentlichen Bedeutung nachzuschauen. Das Suchen können wir für das rechnergestützte Lesen vereinfachen. Mit Hilfe der Eigenschaftsliste speichern wir für jeden verkürzten Namen die vollständige Fassung. Dazu formulieren wir zwei Makros. Das Makro ::= schreibt die Erläuterung in die Eigenschaftsliste. Das Makro ? ermöglicht das Nachfragen. Da die Eigenschaftsliste global ist, kann in jeder Umgebung nachgefragt werden (Zu den Nachteilen dieser Globalität vgl. Abschnitt 5.3). Lösung F: o Lösung E in Verbindung mit einem (Daten-)Lexikon als Eigenschaftsliste eval>

eval>

(DEFINE * I S T * (GENSYM)) ==>

(MACRO

?

(LAMBDA

'(GETPROP eval>

(MACRO

::=

"(PUTPROP

*IST*

;GENSYM d a m i t k e i n e E i g e n s c h a f t ü b e r ; schrieben wird (vgl. Abschnitt 7.4).

(SEXPR)

\(LIST-REF (LAMBDA

SEXPR

1)

MST*)))

==>

(SEXPR)

' , ( L I ST-REF

SEXPR

1)

.(LIST-REF

SEXPR

2)

MST*)))

==>

?

315

11. Transparenz der Dokumentation

eval>

(DEFINE

GET-F-N-NAME

(BEGIN (::= GET-F-N

"Kurzname der

Firma")

(::= ?_NAME "Ersatzwert für den Kurznamen der (LAMBDA

Firma")

(L . ?_NAME)

(OR CLIST-REF L 1) (LIST-REF ?_NAME

0))))) ==>

GET-F-K-NAME

eval>

( G E T - F - K - N A M E KUNDE-1 * U N B E K A N N T E R - K U R Z NAME*) ==>

eval>

(? G E T - F - K - N A M E ) ==>

"Kurzname der

"SAG"

Firma"

Das so mitgeführte Lexikon ist eine wenig effiziente, beschränkt nützliche Lösung, die beim Testen hilft (vgl. auch „documentation string", Abschnitt 11.2). In allen nicht rechnergestützten Lesefällen ist sie nicht besser als eine Semikolon-Notation (vgl. Abschnitt 11.2). Problem 4: Reihenfolge im Lexikon Mit dem Verweis auf eine zusätzliche Beschreibung in einem Lexikon wird deutlich, daß der Name so zu wählen ist, daß wir diese Beschreibung leicht finden; auch im Suchfall ohne direkte Rechnerunterstützung. Mit einem standardisierten Präfix für Selektoren (hier: GET-) werden diese im Lexikon alle hintereinander ausgewiesen. Wahrscheinlich bevorzugen wir jedoch eine Ordnung, bei der unser Verbund von Konstruktor, Selektor, Prädikat und Mutator (vgl. Abschnitt 4.2) hintereinander ausgewiesen wird. Suchen wir z.B. in PC Scheme die Beschreibung für WINDOW-Konstrukte, dann durchlaufen wir folgende Reihenfolge der Lexikoneinträge: WINDOW-CLEAR WINDOW-DELETE WINDOW-GET-ATTRIBUTE

WINDOW-SET-SIZE! WINDOW?

Selbst hier ist der zugehörende Konstruktor nicht konsequent mit WINDOWMAKE, sondern mit MAKE-WINDOW bezeichnet und befindet sich daher viel weiter vorn im Lexikon. Wir können die gewünschte Reihenfolge erreichen, indem wir jeweils einen Standard-Suffix für Konstruktor, Selektor, Prädikat und Mutator verwenden.

316

III. Konstruktionsempfehlung

Lösung G: o Standard-Suffix für den Verbund von Konstruktor, Selektor, Prädikat und Mutator eval>

(DEFINE FIRMEN-NAME-MAKE (LAMBDA (LANGNAME . KURZNAME) ( I F KURZNAME (CONS LANGNAME KURZNAME) (LIST LANGNAME)))) ==>

FIRMEN-NAME-MAKE

eval>

(DEFINE FIRMEN-KURZNAME-GET (LAMBDA ( K U N D E N L I S T E . E R S A T Z . K U R Z N A M E ) (OR ( L I S T - R E F KUNDENLISTE 1) ( L I S T - R E F ERSATZ_KURZNAME 0)))) ==> F I R M E N - K U R Z N A M E - G E T

eval>

(DEFINE FIRMEN-KURZNAME? (LAMBDA (KURZNAME) (AND (STRING? KURZNAME) ( F I R M E N - K U R Z N A M E ?

eval>

(DEFINE FIRMEN-KURZNAME-SET! (LAMBDA ( K U N D E N L I S T E KURZNAME) (SET-CDR! KUNDENLISTE (LIST KURZNAME)))) ==> F I R M E N - K U R Z N A M E - S E T !

eval>

(FIRMEN-KURZNAME-GET

KUNDE-1

*UNBEKANNTER-KURZNAME*) ==>

"SAG"

Die Berücksichtigung der Reihenfolge führt zu Formulierungen, die im Vergleich zur Präfix-Verschlüsselung (Lösung D), ungewohnt und weniger flüssig zu lesen sind. eval> eval>

FIRMEN-KURZNAME-GET GET-FIRMEN-KURZNAME

==> ==>

... ...

¡zweckmäßige Sortierbarkeit ¡ r e l a t i v gute Lesbarkeit

Wir haben für die Strukturierung eines Namens die Sonderzeichen, Bindestrich und Unterstrich, verwendet. Unsere Konvention reserviert den Bindestrich für Funktionsnamen und damit auch für Argumente. Der Unterstrich ist für die LAMBDA-Variablen reserviert. Option:

Groß/Kleinschreibung

Eine Strukturierung kann auch durch Groß/Kleinschreibung erfolgen. Darüber hinaus nutzen wir dann den Vorteil, daß Geschriebenes in Kleinbuchstaben um ca. 20 bis 50 Prozent schneller als in Großbuchstaben gelesen wird (untersucht von Stuart/Stuart Gardner, 1955; zitiert nach Carte, 1973). Wesentliche Ursache ist die Wellenform der oberen und unteren Linie bei Wörtern mit Kleinbuchstaben; z.B. „Der neue'Weg" statt „DER NEUE WEG". Die Groß/Kleinschreibung kommt allerdings nicht für jedes LISP in Betracht, weil häufig, so auch in PC Scheme, in der READ-Phase Kleinbuchstaben in große Buchstaben umgesetzt werden (vgl. Abschnitt 7.3).

11. Transparenz der Dokumentation

317

Lösung H: o Groß/Kleinschreibung zur Namensstrukturierung e v a l > ( d e f i n e FirmenKurzname (lambda ( K u n d e n L i s t e . ErsatzName) (or ( l i s t - r e f KundenListe 1) ( l i s t - r e f ErsatzName 0 ) ) ) ) ==> FIRMENKURZNAME In PC Scheme wäre eine solche Groß/Klein-Notation, wie sie z.B. in SmallTalk üblich ist (vgl. Goldberg/Robson, 1983), nicht sinnvoll. Schon der Rückgabewert des DEFINE-Konstruktes ist ein Symbol aus Großbuchstaben. Laden wir das Konstrukt aus einer Datei oder geben es z.B. mit dem eingebauten „PrettyPrint"-Konstrukt aus, stets verlieren wir die Groß/Kleinunterscheidung. e v a l > (PP FirmenKurzname) ==> # = (LAMBDA (KUNDENLISTE . ERSATZNAME) (OR ( L I S T - R E F KUNDENLISTE 1) ( L I S T - R E F ERSATZNAME 0 ) ) ) Man kann ein eigenes PP-Konstrukt definieren, das bei seiner Ausgabe große und kleine Buchstaben nutzt. Es kann eingebaute LISP-Konstrukte in Kleinbuchstaben und selbstdefinierte Konstrukte in Großbuchstaben ausgeben (vgl. z.B. das PP-Konstrukt in T L C - L I S P ) . e v a l > (MEIN-PP FirmenKurzname) ==> ( d e f i n e FIRMENKURZNAME (lambda (KUNDENLISTE . ERSATZNAME) (or ( l i s t - r e f KUNDENLISTE 1) ( l i s t - r e f ERSATZNAME 0 ) ) ) Um in PC Scheme die Groß/Kleinschreibung wie in anderen LISP-Systemen (z.B. in Allegro Common L I S P ) als Strukturierungsoption nutzen zu können, ist das Fluchtsymbol (vgl. Abschnitt 7.3) mitzunotieren, wie die Lösung I zeigt. Lösung /: o Groß/Kleinschreibung mit Fluchtsymbol zur Namensstrukturierung e v a l > (DEFINE |SeiektionFirmenKurznameI (LAMBDA ( ¡ K u n d e n L i s t e ! . ¡ErsatzName!) (OR ( L I S T - R E F ¡KundenListe! 1) ( L I S T - R E F ¡ErsatzName! 0 ) ) ) ) ==> ] S e i e k t i o n FirmenKurzname! Mit dem (mühsam) notierten Fluchtsymbol könnte man auch Leerzeichen zum Strukturieren des Namens verwenden. Ein Name kann somit ein ganzer Satz sein. e v a l > (DEFINE e v a l > (DEFINE

!* Unbekannter Kurzname * ! " - ? - " ) ==> !* Unbekannter Kurzname * | ¡Kunde 3| ' ( " T o s h i b a " ) ) ==> ¡Kunde 31

318

III. Konstruktionsempfehlung

eval>

(DEFINE ¡ S e l e k t i e r e FirmenKurzname a n d e r n f a l l s g i b ErsatzName! (LAMBDA ( i K u n d e n L i s t e I . ! E r s a t z N a m e | ) (OR ( L I S T - R E F ! K u n d e n L i s t e ! 1 ) (LIST-REF |ErsatzNameI 0)))) ==> ' S e l e k t i e r e FirmenKurzname a n d e r n f a l l s gib ErsatzName!

eval>

(¡Selektiere ¡Kunde 3!

FirmenKurzname |* U n b e k a n n t e r

a n d e r n f a l l s gib ErsatzName! Kurzname * | ) = = > " - ? - "

Option: ALIAS-Namen Mit Hilfe des ALIAS-Konstruktes kann ein Name wie ein anderer, schon existierender Name verwendet werden. Beide Namen bezeichnen dieselbe „Einheit". Man könnte spontan einen „Sonntags"- und einen „Alltags"-Namen kreieren. Der „Sonntags"-Name ist lang und enthält die gewünschten Informationen. Der „Alltags"-Name ist eine Kurzschreibweise dessen. Zunächst wird nur der „Sonntags"-Name genutzt. Käme dieser Name jedoch in einem Textbereich häufig vor, dann wechselt man zum „Alltags"-Namen. Man mutet dem Leser zu, sich die Bedeutung eines häufig notierten Namens einzuprägen, um die Verkürzungsvorteile nutzen zu können. Lösung J: o zwei Namen eval> eval> eval>

( A L I A S f LAMBDA) = = > f (ALIAS = DEFINE) ==> = (= FIRMEN-KURZNAME-GET (f (L . ERSATZ) (OR ( L I S T - R E F L 1 )

eval>

(ALIAS

F-K-GET

eval>

(F-K-GET

(LIST-REF

ERSATZ ==>

FIRMEN-KURZNAME-GET)

KUNDE-2

==>

*UNBEKANNT ER-KURZNAME*)

0)))) FIRMEN-KURZNAME-GET F-K-GET ==>

"-?-"

Die Gefahren, die eine solche doppelte Benennung hervorruft, sind nicht zu unterschätzen. Im Analysefall sind stets beide Namen zu verfolgen. Wir wählen daher für selbstdefinierte Mutatoren stets nur einen Namen. Beachten wir bei der Wahl eines Namens nicht die Informationsaufteilung mit dem (Daten-)Lexikon und mit weiteren Namen, die z.B. im Zusammenhang mit seiner Anwendung relevant sind, dann wird der Name lang, und wir müssen die damit verbundenen Nachteile (vgl. Tab. 11-1) in Kauf nehmen (Lösung A). Anzustreben ist eine Rechnerunterstützung beim Lesevorgang, weil damit ein zweckmäßiger Kompromiß zwischen Namen und Lexikon erreichbar ist (Ansatz z.B. Lösung F). Die Groß/Kleinschreibung ist eine geeignete Strukturierungsoption für den Namen, vorausgesetzt, es bedarf keiner Fluchtsymbolnotation. Zur Vermittlung konstruktionsbedingter Aspekte ist im Hinblick auf kurze Namen die Verwendung normierter Affixe zweckmäßig (Lösungen D und G).

11. Transparenz der Dokumentation

319

11.2 Kommentierung Einerseits wissen wir (zumindest) seit der Diskussion über die strukturierte Programmierung („Go To Statement Considered Harmful"; Dijkstra, 1968), daß ein undurchschaubarer Quellcodetext durch eingefügte Kommentarzeilen kaum transparenter wird. Springen wir kreuz und quer (d.h. es liegt eine nichtlineare Kontrollstruktur vor), dann kann der Transparenzmangel nicht mit einer Kommentierung (quasi nebenläufig) behoben werden. Andererseits wissen wir, daß sehr komplexe Konstruktionen mit unstrukturierten Konstrukten relativ kurz notiert werden können. Zu Recht sind daher auch Strukturen zu berücksichtigen, „die wir heute als ,ziemlich unstrukturiert' empfinden und vor denen wir in der Ausbildung (noch) warnen" (Claus, 1987, S.50). Beispielsweise sind wir mit dem CALL/CC-Konstrukt (vgl. Abschnitt 8.2) in der Lage komplizierte nichtlineare Strukturen abzubilden. Zur Transparenz solcher nichtlinearen Strukturen können verbale Kommentarzeilen nur begrenzt beitragen. Letztlich helfen sie nur bei transparenten Programmen. Diese brauchen Kommentare jedoch nicht, da sie ja schon transparent sind; im Gegenteil, eine Kommentierung verlängert das Dokument, erhöht den Wartungsaufwand, weil die Kommentare (mit)gepflegt werden müssen und schadet daher. So gesehen müßten wir jede Kommentarzeile ablehnen. Vorsichtshalber wäre eine Streichung des Semikolons als Kommentarpräfix aus unserem LISPSystem vorzunehmen (vgl. Kaelbling, 1988). Im Gegensatz dazu setzen viele Dokumentations-Empfehlungen auf die intensive Kommentierung: „Großzügiger Gebrauch von Kommentaren ist guter Programmierstil" (Winston/Horn, 1984, deutsche Fassung S. 48) oder „..., I can't recommend them highly enough" (Hasemer, 1984, p. 39). Wir befürworten einen pragmatischen Standpunkt und empfehlen, Kommentarzeilen sparsam zu verwenden, und warnen vor einer „Überkommentierung" (vgl. auch Kessler, 1988, S.377). Ihre Aufgabe ist die WARUM-Erläuterung und nur in Fällen, wenn unserem Adressaten die Abarbeitungsfolge fremd sein wird, auch die WIE-Erläuterung. So werden Kommentare kein Ersatz für eine unzulängliche Benennung von Konstrukten. Umgekehrt muß die Benennung nicht mit Informationen zum WARUM belastet werden. Nicht zu übersehen sind die gravierenden Nachteile von Kommentarzeilen. Zunächst ist zu vereinbaren, ob sie das ihnen nachfolgende Programm(fragment) beschreiben oder das vorhergehende. Selbst bei strikter Einhaltung einer entsprechenden Vereinbarung (vgl. Tab 11.2-1) bleibt der Nachteil, daß die Reichweite eines Kommentars nicht unmittelbar erkennbar ist, sondern durch Interpretation des Textes zu folgern ist. Konstruktionsmäßig wird eine Kommentarzeile eher wie ein global definierter Wert behandelt. Die Kommentierung bleibt damit auf dem Erkenntnisniveau primitiver Programmiersprachen stehen, die keine Blockstrukturen, d.h. keine

III. Konstruktionsempfehlung

320

lokalen Werte, kennen. Wünschenswert wäre ein Mechanismus zur Kommentierung, der die Reichweite und die Verknüpfung mit dem jeweiligen Programmfragment gewährleistet (vgl. Belli/Bonin, 1989). Solange das eingesetzte LISPSystem einen solchen Mechanismus nicht bietet, sind wir auf eine Ersatzlösung angewiesen. Eine einfache Lösung zeigt Tab. 11.2-1. ;[

;] ;< ;> Tab

< k o m m e n t a r> < k o m m e n t a r> 11.2-1.

m a r k i e r t den Beginn der Reichweite

ist e i n K o m m e n t a r und m a r k i e r t d a s der R e i c h w e i t e von < k o m m e n t a r > erläutert erläutert

Kennzei

chnung

von Ende

das das

vorhergehende Konstrukt nachfolgende Konstrukt

der

Rei chwei te eines

Komentärs

Bei den Anwendern von MacLISP (vgl. Anhang B) hat sich insbesondere im Umfeld vom Massachusetts Institute of Technology Cambridge (MIT) eine Konvention mit Semikolon-Folgen durchgesetzt, die Eingang in die Common LISPBeschreibung gefunden hat (vgl. Steele u.a., 1984, S. 348). Ein Semikolon dient zur Kommentierung der Zeile, auf der es steht. Dabei sind mehrere Einfach-Semikolon-Kommentare spaltenmäßig auszurichten, d.h. sie beginnen stets in der gleichen Spalte. Ist der Kommentar länger als die verbleibende Restzeile, dann wird er auf der nächsten Zeile fortgesetzt, allerdings folgt diesem Semikolon ein Leerzeichen. Ein Doppeiter-Semikolon-Kommentar beginnt in der Spalte, in der das zu erläuternde Konstrukt anfängt. Nach dem doppelten Semikolon folgt ein Leerzeichen. Die Kommentierung bezieht sich in der Regel auf das nachstehende Konstrukt. Ein Dreifach-Semikolon-Kommentar beginnt stets in der ersten Spalte einer Zeile. Er beschreibt ein größeres Konstrukt. Ein Vierfach-Semikolon-Kommentar kennzeichnet Überschriften. Er beginnt ebenfalls in der ersten Spalte. Die Beispiele in diesem Buch sind gemäß dieser Konvention (vgl. Tab. 11.2-2) kommentiert. Kommentarzeilen sind gleichzeitig mit den LISP-Zeilen zu schreiben, die sie erläutern. Es ist sicherzustellen, daß Kommentare mitgepflegt werden. Ein nicht mehr gültiger Kommentar ist irreführend und daher schädlicher als ein fehlender Kommentar. Bei jeder Modifikation empfiehlt es sich, vorsichtshalber Kommentare in Bezug auf die Änderungsstelle vorab zu löschen. /Common LISP: Documentation string. Beim Definieren einer Funktion (mit dem DEFUN-Konstrukt) oder eines Makros (mit dem DEFMACRO-Konstrukt) kann im Deklarationsbereich ein Kommentar hinterlegt werden: (DEFUN {} ) Ist eine vom Typ String, dann dient sie zur Kommentierung des

11. Transparenz der Dokumentation

321

Konstruktes. Der Kommentar ist abrufbar mit der Applikation von DOCUMENTATION, z.B: eval>

(DOCUMENTATION

Semi kolonanzahl

'F00

'FUNCTION)

Begi nn i n Spai te

==>

"Kommentar-Text"

Verwendung

; ; ;;#\SPACE

1

Ü b e r s c h r i f t für K a p i t e l , und U n t e r a b s c h n i t t e

; ;;#\SPACE

1

K o m m e n t a r für e i n (z.B. Funktion)

; ;#\S PAC E

Legende: #\SPACE Tab.

::=

]

für d a s

gößeres

Abschnitte Konstrukt

des Konstruktes

Kommentar Konstrukt

(nachfolgende)

lokal vorgegeben

K o m m e n t a r für d i e s e Z e i l e . F a l l s P l a t z mangel, dann nächste Zeile mit Semikolon, g e f o l g t von einem L e e r z e i c h e n .

Leerzeichen

11.2-1.

Eine bewährte (vgl. Steele

Konvention u.a., 1984)

für

Kommentare

Das folgende ARRAY-Beispiel zeigt eine Situation, bei der eine Kommentierung notwendig ist, obwohl die Kommentarzeile das WIE beschreibt. Beispiel: ARRAY initialisieren Es ist eine zweidimensionale Tabelle (engl.: array) zu konstruieren. Dazu sind die beide folgenden Konstrukte MAKE-ARRAY und ARRA Y-SET! definiert. eval>

(DEFINE MAKE-ARRAY (LAMBDA (MAX_ZEILEN MAX_SPALTEN) (LET ((V ( M A K E - V E C T O R M A X _ Z E I L E N ) ) ) (DO ((I 0 (1+ I))) ((> I ( - 1 + M A X _ Z E I L E N ) ) V) (VECTOR-SET! V I (MAKE-VECTOR MAX_SPALTEN)))))) ==> MAKE-ARRAY

eval>

(DEFINE ARRAY-SET! (LAMBDA (ARRAY ZEILE SPALTE N E U E R . W E R T ) (LET ( ( N E U E - Z E I L E (VECTOR-SET! (VECTOR-REF ARRAY (-1+ ZEILE)) (-1+ SPALTE) NEUER_WERT))) (VECTOR-SET! ARRAY (-1+ ZEILE) NEUE-ZEILE)))) ==> ARRAY-SET!

322

III. Konstruktionsempfehlung

Mit dem Konstrukt MAKE-ARRAY definieren wir ein Tabellenbeispiel FOO mit 4 Zeilen und 5 Spalten. Das anschließenden LET-Konstrukte modifiziert den Wert von FOO. Wir beurteilen hier nicht, ob es sich um ein „trickreiches" Konstrukt handelt, das besser durch eine durchschaubare Lösung zu ersetzen wäre (dazu vgl. Kernighan/Plauger, 1978). Uns intressiert das Bestehen der Kommentierungs-Pflicht. eval> (DEFINE eval> FOO ==> #(#(() #(() #(() #(()

FOO (MAKE-ARRAY 4 5)) ==> FOO () () () ()

(:) (:) (:) (:)

() o o

o

(LET ((N 4) (M 5) (DO ((I 1 (1+ I ((> I N)) (DO ((J 1 (1+ J ) ) ) ((> J M)) (ARRAY-SET! FOO I J (* (QUOTIENT I J) (QUOTIENT J I ) ) ) ) ) ) ==> #T Der neue Wert von FOO ist für den angenommenen Leser nicht sofort nachvollziehbar. Dieser muß erkennen, daß das Argument (* (QUOTIENT I J) (QUOTIENT J I)) nur die beiden Werte 0 und 1 annehmen kann. Ist (EQ? I J) erfüllt, dann ist es der Wert 1, andernfalls ist es der Wert 0. Das QUOTIENT-Konstrukt hat als Rückgabewert die Integerzahl der Divisionsrechnung. Es ist daher adressatengerecht, auf diese Integerdivision hier mit einem Kommentar hinzuweisen. eval> (LET ((N 4) (M 5)) (DO ((I 1 (1+ I ) ) ) ((> I N)) (DO ((J 1 (1+ J ) ) ) ((> J M)) (ARRAY-SET! FOO I J ;; Integer-Di vision, Wert entweder 0 oder 1 (* (QUOTIENT I J) (QUOTIENT J I ) ) ) ) ) ) ==> #T eval> FOO ==> #(#(1 0 0 #(0 1 0 #(0 0 1 #(0 0 0

0 0 0 1

0) 0) 0) 0))

11.3 Vorwärts- und Rückwärtsverweise In den ersten Phasen des Konstruierens mag ein „lokal sequentielles" Lesen und Vergleichen mit anderen Passagen im selben Dokument oder in anderen dominieren. Spätestens in der Wartungs- und Pflegephase herrscht das Lesen als Suchprozeß vor. Über Verzeichnisse und Querverweise wird mehr oder weniger „dia-

11. Transparenz der Dokumentation

323

gonal" gelesen, um z.B. Fehler zu isolieren oder versteckte Abhängigkeiten aufzudecken. Daher ist es sinnvoll, von Anfang an das Suchen in einer Dokumentation zu unterstützen. Vorwärts- und Rückwärtsverweise sind deshalb Bestandteil einer „guten" Dokumentation. Tabelle 11.3-1 nennt die anzustrebenden Eigenschaften für eine Dokumentation (vgl. z.B. ANSI/IEEE Std. 830, 1984). Ei g e n s c h a f t e n : (1) e i n d e u t i g , (2) v o l l s t ä n d i g , (3) v e r i f i z i e r b a r , (4) konsistent, (5) m o d i f i z i e r b a r , (6) z u r ü c k f ü h r b a r (traceable) (7) h a n d h a b b a r

und

Tab. 11.3-1 Anzustrebende Eigenschaften für eine Dokumentati on

Die Eigenschaft „Eindeutigkeit" bedingt, daß wir mehrdeutige Begriffe vermeiden oder zumindest ihre angenommene Bedeutung in einem Begriffslexikon (Glossary) hinterlegen. Die Eigenschaft "Vollständigkeit" ist adressaten- und zweckausgerichtet zu erfüllen. Ist der Adressat der Rechner, dann ist „Vollständigkeit" (in der Praxis nachträglich) objektiv feststellbar. In allen anderen Fällen ist subjektiv zu entscheiden, ob etwas explizit zu beschreiben ist oder nicht (vgl. Abschnitt 11). Die Eigenschaften "Verifizierbarkeit" und „Konsistenz" (Widerspruchsfreiheit) sind, soweit es sich um verbale, nicht streng formaliserte Texte handelt, nicht exakt definierbar und kaum erreichbar. Sie bilden jedoch schon intuitiv einen Maßstab, der zur Präzision und zum Einbeziehen schon formulierter Aussagen zwingt. Die Eigenschaft „Modifizierbarkeit" können wir entsprechend dem folgenden Prädikat MODIFIZIERBAR? weiter aufschlüsseln. Modifizierbar ist eine Dokumentation, wenn die Änderung einfach, vollständig und konsistent durchführbar ist. eval>

(DEFINE M O D I F I Z I E R B A R ? (LAMBDA (ÄNDERUNG) (AND ( E I N F A C H - D U R C H F Ü H R B A R ? Ä N D E R U N G ) (VOLLSTÄNDIG-DURCHFÜHRBAR? ÄNDERUNG) (KONSISTENT-DURCHFÜHRBAR? ÄNDERUNG)))) ==> MODIFIZIERBAR?

Die einfache Durchführbarkeit der Änderung setzt eine dem Benutzer vertraute Gliederung der Dokumentation voraus. Für die Quellcodetexte ist z.B. die Reihenfolge der einzelnen Konstrukte festzulegen. Analysieren wir eine Konstrukte-Hierarchie (vgl. Bild 4.2.-1), dann folgen wir den Abstraktionsebenen; entweder von oben beginnend nach unten (,,top-down"-Ansatz) oder umgekehrt, von

324

III. Konstruktionsempfehlung

unten beginnend nach oben (,,bottom-up"-Ansatz). Einzuhalten ist dabei eine feste Reihenfolge innerhalb unseres Verbundes von Konstruktor, Selektor, Prädikat und Mutator. Zusätzlich sind Verweise auf abhängige und redundante Text(teil)e erforderlich, damit die Änderung vollständig und konsistent durchführbar ist. Für die gesamten Dokumentation wird keine Redundanzfreiheit angestrebt. Sie widerspräche der Adressaten- und Zweck-Ausrichtung der Dokumente. Das schließt nicht aus, daß einzelne Dokumente (z.B. die Quick-Referenzkarte) möglichst redundanzfrei sind. eval> (DEFINE EINFACH-DURCHFÜHRBAR? (LAMBDA (ÄNDERUNG) (AND (VERTRAUTE-DOKUMENTATIONS-GLIERDERUNG? *BENUTZER*) (VERSTEHBAR? ÄNDERUNG) (EXPLIZITES-VERWEISSYSTEM? *D0KUMENTATI0N*)))) ==> EINFACH-DURCHFÜHRBAR? Die Eigenschaft „Zurückführbarkeit" bezieht sich auf die Konstruktions-Pprozeßdokumentation. Wir wollen zu einer Aussage in einem Dokument die zugehörigen Aussagen in der Kette der vorhergehenden Dokumente auffinden. Die „Zurückführbarkeit" verlangt, daß wir z.B. von einem Konstrukt im Quellcodetext aus die betreffende Anforderung im Pflichtenheft identifizieren können. Die einzelnen Dokumente dürfen kein Konglomerat, d.h. kein bunt zusammengewürfeltes Papierwerk, bilden. Erst eine gegenseitige Abstimmung („Arbeitsteilung") der einzelnen Dokumente macht eine Dokumentation handhabbar. Die Eigenschaft „Handhabbarkeit" betrifft den Umfang jedes einzelnen Dokumentes. Ein adressaten- und zweckgerechter Umfang ist nur erreichbar, wenn wir problemlos auf Aussagen in anderer Dokumente verweisen können. Die Vor- und Rückwärtsverkettung verbessert die „Modifizierbarkeit", „Zurückführbarkeit" und „Handhabbarkeit" der Dokumentation. Voraussetzung ist dazu, daß jedes Dokument und jede Aussage in jedem Dokument mit einem eindeutigen Bezeichner (Identifier) versehen ist. Wir notieren daher keine Überschrift für ein Kapitel, einen Abschnitt oder Unterabschnitt, keine Unterschrift für eine Abbilung, Zeichnung oder Tabelle und keine wesentliche Aussage im Text ohne (alpha-)numerische Kennziffer. In welcher Form die Kennziffern als Basis dieser Vor- und Rückwärtsverkettung zu bilden sind, ist in den jeweiligen Dokumentationsrichtlinien festzuschreiben. In diesem Buch benutzen wir Zahlen mit einem vorangestelltem Buchstaben. Der Buchstabe steht für folgende Abkürzungen: = Anforderung = Enwurf = Implementation = Test Außerdem haben die Dokumentationsrichtlinien vorzuschreiben, welche Kenndaten ein selbstständiges Dokument aufzuweisen hat („Kopfdaten"- und „Fußdaten"-Vorschrift). Zumindest erforderlich sind die Angaben:

11. Transparenz der Dokumentation o o o o

325

Dokumentnummer (Identifier), Kurzbezeichnung (Titel) kurze Zweckangabe (Zusammenfassung) und Erstellungs- und Modifikationsdaten.

Die folgende Gegenüberstellung einer Beschreibung weniger Anforderungen zeigt Möglichkeiten einer Identifizierung. Beispiel: Plausibilitätspriifung Fall 1: Verbaler Text (So nicht!) Das Programm soll drei Datenelemente auf Plausibilität überprüfen. Ist das erste Datenelement falsch, wird eine Fehlernachricht ausgegeben. Ist das zweite Datenelement nicht richtig wird ebenfalls eine Fehlermeldung ausgegeben. Ist das dritte Datenelement falsch, soll das Programm den Wert „Unbekannt!" annehmen. Fall 2: Verbaler Text mit Bezeichnern A01 Das Programm PLAUSIBEL? überprüft die drei Datenelemente: ELEMENT-1, ELEMENT-2 und ELEMENT-3. A01.1 Ist ELEMENT-1 falsch, gibt PLAUSIBEL? die Fehlernachricht F01 aus. A01.2 Ist ELEMENT-2 falsch, gibt PLAUSIBEL? die Fehlernachricht F02 aus. A01.3 Ist ELEMENT-3 falsch, nimmt PLAUSIBLE? den Wert W01 = "Unbekannt!" an. Fall 3: Formalisierter Text mit Bezeichnern

ET-PLAUSIBEL?

R1

R2

R3

B1

ELEMENT-1

falsch?

J

-

-

B2

ELEMENT-2

falsch?

-

J

-

B3

ELEMENT-3

falsch?

-

-

J

AI

Fehlernachricht

F01

A2

Fehlernachricht

F02

A3

PLAUSIBEL? nimmt Wert W01 = " U n b e k a n n t " an

X X X

Legende (vgl. A b s c h n i t t 2 . 2 . 2 ) : M e h r t r e f f e r - E T ( A b a r b e i t u n g s f o l g e R1, R 2 , R3) J ::= J a , B e d i n g u n g t r i f f t zu - ::= I r r e l e v a n t , A n w o r t h a t k e i n e n E i n f l u ß a u f d i e Wahl d e r Regel X ::= A k t i o n ist zu v o l l z i e h e n Bild

11.3-1.

Beispiel:

ET-PLAUSIBEL?

326

III. Konstruktionsempfehlung

In den Fällen 2 und 3 ist jede einzelne Anforderung direkt angebbar. Jedes nachfolgende Dokument, z.B. der Quellcodetext, kann sich auf diese Identifizerung stützen. Bei einer Modifikation sind die Änderungsbereiche problemlos „adressierbar", z.B.: "A01.3 Wert W01 = .UNKLAR" (Fall 2) bzw. „ET-PLAUSIBLE? Aktion A3, Wert W01 = 'UNKLAR" (Fall 3).

11.4 Zusammenfassung: Namen, Kommentare und Verweise Die Konstruktions- und Konstruktions-Prozeßdokumente sind wesentliche Bestandteile der Software. Jede Aussage in jedem Dokument hat sich nach den Adressaten und dem Zweck zu richten. Das Baukasten-Konstruktionsprinzip bedingt, daß Formulierungen mehrfach verwendbar sind. Daher sind z.B. Anforderungen im Präsens zu formulieren. Der Name eines Konstruktes (z.B. einer Funktion, eines Makros oder einer Variablen) ist abhängig vom gewählten Paradigma. Einerseits verweist er auf das repräsentierte „reale" Objekt und andererseits transportiert er konstruktions-spezifische Informationen. Letztere lassen sich gut mit einer AFFIX-Konvention verschlüsseln. Bei der Benennung einer Funktion ist stets die Informationsaufteilung mit anderen Namen (LAMBDA-Variablen) und dem Eintrag im (Daten-)Lexikon zu bedenken. Groß/Kleinschreibung ist für einen Namen eine hilfreiche Strukturierungsoption, vorausgesetzt, es bedarf keiner Fluchtsymbol-Notierung. Kommentarzeilen im Quellcodetext dienen primär zur WARUM-Erläuterung. Mit einer verbindlichen Regelung ist festzulegen, worauf sie sich beziehen und welchen Textbereich ihre Erklärung umfaßt. Vor- und Rückwärtsverweise verbessern die Dokumentations-Eigenschaften „Modifizierbarkeit", „Zurückführbarkeit" und „Handhabbarkeit". Dazu ist jedes Dokument und jede Aussage in einem Dokument mit eindeutigen Bezeichnern zu versehen. Charakteristisches

Beispiel für Abschnitt

11:

;;;; D o k u m e n t 1 1 . 4 - 1 ;;;; T i t e l : E i n f a c h e r R e g e l i n t e r p r e t e r D I A G N O S E ;;;; E r s t e l l t am: 0 1 . 1 1 . 9 0 ¡ l e t z t e Ä n d e r u n g : 4 . 1 1 . 9 0 ;;; AI ; ;; ;;; A I . 1 ;;; A I . 2

D I A G N O S E a n a l y s i e r t A u s s a g e n , d i e aus B e d i n g u n g e n Aktionen bestehen. A u s s a g e n sind aus Regeln a b g e l e i t e t oder w e r d e n beim Benutzer nachgefragt.

;;; A2 ;;; A 2 . 1 ;;; A 2 . 2

Ei ne Regel h a t eine oder mehrere eine Aktion.

Bedingungen

und

und

11. Transparenz der Dokumentation

327

A3

E i n e A u s s a g e ist e i n e L i s t e , s e i n e r E i g e n s c h a f t und d e r e n

A4

D I A G N O S E e r m i t t e l t für ein v o r g e g e b e n e s v o r g e g e b e n e E i g e n s c h a f t den z u g e h ö r i g e n

A5

begründet diesen, durch Auflistung von z u t r e f f e n d e n Regeln.

El El.1

Abgebildte

El.2 El.3



El.4

El.5 El.6 El.7 T1 Tl.l

der

aus d e m

Objekt,

O b j e k t und W e r t und

eine

Bedingungen

Daten(strukturen): ::= ; vgl. A3 ( ) ::= < b e d i n g u n g > | < a k t i o n > ;vgl. AI ::= ( ( < b e d i n g u n g - 1 > ;vgl. A2 ... < b e d i n g u n g - n > ) < a k t i o n > ) ::= ( < r e g e l - l > ... < r e g e l - n > )

Regeln sind einer Eigenschaft zugeordnet: (PUTPROP 'REGELN) Der W e r t kommt vom B e n u t z e r , w e n n : ;vgl. A I . 2 ( P U T P R O P # T ' N A C H F R A G E ) Die S t a r t a u s s a g e und die von D I A G N O S E e r m i t t e l t e n Begründungen sind Listenelemente der Variablen ^ A U S S A G E * Bei spiel : Daten: (DEFINE *AUSSAGEN* '((BENZINPUMPE ARBEITET KORREKT))) (PUTPROP 'ARBEITET '((((BATTERIE STROM AUSSREICHEND) (KABEL KONTAKTE LEITEND)) (MOTOR ARBEITET GUT)) (((BENZINVERSORGUNG ARBEITET NICHT)) (MOTOR ARBEITET NICHT)) (((LICHTMASCHINE ARBEITET NICHT)) (MOTOR ARBEITET NICHT)) (((TANKFUELLUNG STAND AUSREICHEND) (BENZINPUMPE ARBEITET KORREKT) (BENZINFILTER DURCHLAESSIGKEIT KEINE)) (BENZINVERSORGUNG ARBEITET NICHT))) 'REGELN) (PUTPROP (PUTPROP

Tl.2

bestehend Wert.

'STAND #T 'NACHFRAGEN) 'DURCHLAESSIGKEIT #T 'NACHFRAGEN)

Applikation (DIAGNOSE 'MOTOR 'ARBEITET) ==> Nenne S T A N D von T A N K F U E L L U N G ? ausreichend ¡Benutzereingabe Nenne D U R C H L A E S S I G K E I T von B E N Z I N F I L T E R ? keine ¡Benutzereingabe ((MOTOR ARBEITET NICHT) (BENZINVERSORGUNG ARBEITET NICHT) (BENZINFILTER DURCHLAESSIGKEIT KEINE) (TANKFUELLUNG STAND AUSREICHEND) (BENZINPUMPE ARBEITET KORREKT))

III. Konstruktionsempfehlung

328

( D E F I N E DIAGNOSE (LAMBDA (OBJEKT (LETREC ( Primitive

EIGENSCHAFT) Konstruktoren

(MAKE-AUSSAGE (GET-OBJEKT

(LAMBDA (LIST

(LAMBDA

(GET-EIGENSCHAFT (GET-WERT

(LAMBDA

(GET-BEDINGUNGEN

und

Selektoren

El

( O B J E K T E I G E N S C H A F T WERT) OBJEKT EIGENSCHAFT W E R T ) ) )

(AUSSAGE)

(LAMBDA

(LAMBDA

(CAR

(AUSSAGE)

(AUSSAGE)

(GET-ERSTE-BEDINGUNG

;vgl.

AUSSAGE))) (LIST-REF

(LIST-REF

(REGEL)

(CAR

AUSSAGE

AUSSAGE

1)))

2)))

REGEL)))

(LAMBDA (BEDINGUNGEN) (CAR B E D I N G U N G E N ) ) )

(GET-REST-BEDINGUNGEN

(LAMBDA (BEDINGUNGEN) (CDR B E D I N G U N G E N ) ) )

(GET-AKTION

(LAMBDA

(REGEL)

(LIST-REF

(GET-REGELN

(LAMBDA ( E I G E N S C H A F T ) (GETPROP EIGENSCHAFT

REGEL

1))) ;vgl .

El.5

'REGELN)))

(GET-ERSTE-REGEL

(LAMBDA

(REGELN)

(CAR

REGELN)))

(GET-REST-REGELN

(LAMBDA

(REGELN)

(CDR

REGELN)))

; ; R e g e l i s t r e l e v a n t , wenn i h r e A k t i o n g l e i c h e s O b j e k t ; ; h a t w i e d i e zu p r ü f e n d e A u s s a g e ( R E L E V A N T E - R E G E L N (LAMBDA (REGELN OBJEKT E I G E N S C H A F T ) (COND ( ( N U L L ? R E G E L N ) N I L ) C C EQ? OBJEKT (GET-OBJEKT (GET-AKTION (GET-ERSTE-REGEL REGELN)))) (CONS ( G E T - E R S T E - R E G E L R E G E L N ) (RELEVANTE-REGELN (GET-REST-REGELN REGELN) OBJEKT E I G E N S C H A F T ) ) ) (T ( R E L E V A N T E - R E G E L N ( G E T - R E S T - R E G E L N R E G E L N ) OBJEKT E I G E N S C H A F T ) ) ) ) ) Konstrukte

zum F o r t s c h r e i b e n

von

*AUSSAGE*

;vgl .

( E R G A E N Z U N G - * A U S S A G E N * ! (LAMBDA (NEUE.AUSSAGE ) ( S E T ! ^ A U S S A G E N * (CONS NEUE_AUSSAGE ^ A U S S A G E N * ) ) ) ) (MODI F I KATION - * A U S S A G E N * ! (LAMBDA ( A L T E _ A U S S A G E NEUE_AUSSAGE) (SET-CAR! ALTE_AUSSAGE NEUE_AUSSAGE)))

El.7

329

11. Transparenz der Dokumentation ;; E f f i z i e n t e s Suchen d u r c h z w e i s t u f i g e s Verfahren ( G E T - W E R T - I N - * A U S S A G E N * (LAMBDA (OBJEKT E I G E N S C H A F T ) (DO ( ( F A K T ( A S S O C O B J E K T * A U S S A G E N * ) (ASSOC OBJEKT (CDR F A K T ) ) ) ) ((OR (NULL? FAKT) (EQ? (GET-EIGENSCHAFT FAKT) E I G E N S C H A F T ) ) ( I F ( N U L L ? FAKT) N I L (GET-WERT F A K T ) ) ) ) ) )

(AUSSAGE - 1 N - * A U S S A G E N * ! (LAMBDA ( A K T I O N ) (LET ((BISHERIGER-WERT (GET-WERT-IN-*AUSSAGEN* (GET-OBJEKT AKTION) (GET-EIGENSCHAFT A K T I O N ) ) ) ) (COND ( ( N U L L ? B I S H E R I G E R - W E R T ) (ERGAENZUNG-*AUSSAGEN*! AKTION)) (T ( M O D I F I K A T I O N - * A U S S A G E * ! BISHERIGER-WERT AKTION)))))) Wert beim B e n u t z e r n a c h f r a g e n ;vgl. El.6 (Benutzerschnittstelle) ;; V o r a u s s e t z u n g f ü r e i n e B e n u t z e r n a c h f r a g e ( N I C H T - A B L E I T E N ? (LAMBDA ( E I G E N S C H A F T ) (GETPROP EIGENSCHAFT 'NACHFRAGEN))) (BENUTZER-RUECKFRAGE! (LAMBDA (OBJEKT E I G E N S C H A F T ) (WRITELN "Nenne " EIGENSCHAFT " von " OBJEKT " ? " ) (ERGAENZUNG-*AUSSAGEN*! (MAKE-AUSSAGE OBJEKT EIGENSCHAFT ( R E A D ) ) ) ) ) Regelauswahl,

Bedingungen

testen,

neues

Ziel

bilden

( T E S T (LAMBDA (B_N) ;B_N : : = B e d i n g u n g e n (Plural) (COND ( ( N U L L ? B _ N ) T ) ((MEMBER ( G E T - E R S T E - B E D I N G U N G B_N) * A U S S A G E N * ) (TEST (GET-REST-BEDINGUNGEN B _ N ) ) ) ((ZIELBILDUNG ( G E T - O B J E K T ( G E T - E R S T E - B E D I N G U N G B_N ) ) ( G E T - E I G E N S C H A F T ( G E T - E R S T E - B E D I N G U N G B_ N ) ) ) ;: Z I E L B I L D U N G kann *AUSSAGEN* m o d i f i z i e r e n , daher ;; e r n e u t e s Nachschauen i n *AUSSAGEN* (COND ( ( M E M B E R ( G E T - E R S T E - B E D I N G U N G B _ N ) *AUSSAGEN*) ( T E S T ( G E T - R E S T - B E D I N G U N G E N B_ N ) ) ) (T N I L ) ) ) (T N I L ) ) ) ) ( P R U E F E - R E G E L N (LAMBDA (REGELN) (COND ( ( N U L L ? R E G E L N ) N I L ) ( ( T E S T (GET-BEDINGUNGEN ( G E T - E R S T E - REGEL R E G E L N ) ) ) (AUSSAGE - 1 N - ^ A U S S A G E N * ! (GET-AKTION (GET-ERSTE-REGEL REGELN)))) (T ( P R U E F E - R E G E L N ( G E T - R E S T - R E G E L N R E G E L N ) ) ) ) ) )

330

III. Konstruktionsempfehlung (ZIELBILDUNG (LAMBDA (AKT.OBJEKT A K T _ E I G E N S C H A F T ) (COND ( ( G E T - W E R T - I N - * A U S S A G E N * AKT_OBJEKT AKT_EIGENSCHAFT)) ( ( N I C H T - A B L E I T E N ? AKT_EI G E N S C H A F T ) (BENUTZER-RUECKFRAGE! AKT_OBJEKT AKT.EIGENSCHAFT)) ((PRUEFE-REGELN (RELEVANTE-REGELN (GET-REGELN A K T _ E I G E N S C H A F T ) AKT_OBJEKT AKT_EI G E N S C H A F T ) ) ) (T NIL)))))

;; Starten der R e g e l a b a r b e i t u n g (PP (ZIELBILDUNG OBJEKT E I G E N S C H A F T ) ) ) ) ) Programm 11.4 Einfacher Regelinterpreter ( z i e l o r i e n t i e r t e Prüfung)

DIAGNOSE

12. Spezifikation mit LISP Spezifizieren heißt eine (zunächst noch unbekannte) Aufgabenstellung durch sprachliche Festlegung ihres Ergebnisses, bzw. ein (zunächst noch unbekanntes) Objekt durch sprachliche Festlegung seines Gebrauchs zu präzisieren (vgl. z.B. Hesse u.a., 1984). Publikationen zur Thematik „Software-Spezifikation" gehen von unterschiedlichen Begriffsdefinitionen aus. Zumeist basieren sie auf der Abgrenzung einzelner Phasen des Lebenszyklus (vgl. Bild 2-1) und der Beschreibung ihrer Dokumente. Zielvorstellung ist dabei die Spezifikation mit der Präzision der Mathematik und der Verständlichkeit der Umgangssprache. Spezifizieren im Sinne eines Präzisierens umfaßt alle Phasen. In den ersten Phasen bezieht sich die Spezifikation auf die Analyse und Synthese möglicher Konzeptionen. Ausgehend von einer Problemanalyse, werden mit der Aufnahme der Ziele und Konflikte sowie der Ist-Situation des bisherigen System(umfelde)s der Bedarf und die Schwachstellen präzisiert. Alternative Lösungansätze sind dann anhand der präzisierten Ziele und Konflikte zu bewerten (vgl. Bild 12.1). Anschließend folgt das Spezifizieren im engeren Sinne, d.h. das Entwerfen und Formulieren von Quellcodetexten. Konzentriert man sich auf die Phase, in der anwendungsspezifische Datentypen zu präzisieren sind, dann besteht die Spezifikation aus Sorten, Operationen und Gleichungen. Diese Spezifikation notiert die Datentypen im Sinne von Algebren (Näheres zur algebraischen Spezifikation vgl. z.B. Kreowski, 1981). Wir gehen von einer gewählten Lösungsalternative aus (Bild 12-1, nach Phase P 6 ) und spezifizieren schrittweise die Aufgabenstellung so weit, bis diese

12. Spezifikation mit LISP

331

Dokumentati on [Anzahl Sei t e n ]

De

Bewertung/Auswahl

+

L ö s u n g s a l t e r n a t i ven

+ Bedarf

H

IST-Aufnahme

+ J

Ziele &

h

1

&

Schwachstellen

des

System(umfelde)s

Konflikte

Problemanalyse

1

P

P

2

3

P

P

4

P

5

6

Z e i t " > [Mannjahre]

r M

Legende: D-j Bild

::= D o k u m e n t 12-1.

als

Spezifizieren

Ergebnis

der

in den

P h a s e P-j

ersten

Phasen

(ohne Mißverständnisse) direkt konstruierbar (definierbar) ist. Diese Spezifikation präzisiert: Was leistet die Konstruktion im Einzelnen? Die Hauptaufgabe ist somit das Erarbeiten detaillierter Anforderungen (vgl. Tab. 12-1.). Die Anforderungen können sich auf numerische Berechnungen (z.B. Lösungen von Differentialgleichungen), geometrische Berechnungen (z.B. 3-dimensionale Modelle), Datenaggregationen (z.B. Besoldungsbescheide), Transaktionsprozesse (z.B. Platzreservierungssysteme), Wissensakquisition (z.B. Diagnosesysteme), Kommunikationsprozesse (z.B. Elektronische Post) und vieles mehr beziehen. Diese Vielfalt der Konstruktionsaufgaben macht unterschiedliche Arbeitstechniken notwendig. Arbeitstechniken umfassen einerseits Prinzipien (Handlungsgrundsätze) und Methoden (erfolgversprechende, begründbare Vorgehensweisen) und andererseits Instrumente („Werkzeuge"). Erste sind nicht, zweite sind (teilweise) selbst Software. Empfehlungen zur Spezifikation haben zumindest die Aufgabenart und die Aufgabengröße zu berücksichtigen. Es ist z.B. nicht unerheblich, ob ein administratives System oder ein System zur Steuerung eines technischen Prozesses zu spezifizieren ist. Im ersten Fall ist das Nutzen/Kostenverhältnis einzelner Systemleistungen disponibel bzw. unklar. Wir haben es mit Definitionsproblemen der Automationsaufgabe zu tun. Im zweiten Fall mag die geforderte Reaktionsgeschwindigkeit das eigentliche Konstruktionsproblem sein. Das Ziel und die Arbeitstechnik seien klar; das Problem liegt in der Durchführung (Vollzugsproblem). Ausge-

332

III. Konstruktionsempfehlung

AnforderungsKategori e

Kernfrage

Beispiel formuli erungen (vgl. P r o g r a m m D I A G N O S E Abschnitt 11.4)

1

funkti onale Anforderungen

W a s soll d a s System tun?

A u s s a g e n s i n d aus R e g e l n abgeleitet oder werden beim Benutzer nachgefragt.

2

Anforderungen an d a s f e r t i g e Produkt

W a s ist System?

Das D i a l o g s y s t e m D I A G N O S E ist e i n P r o d u k t für d e n E i n s a t z bei m e h r als 100 K f z - B e t r i e b e n .

3

Anforderungen zur Systemumgebung

Was sind die Einsatzbedingungen?

D I A G N O S E s e t z t d a s Betriebssystem MS-DOS Version 3.03 voraus.

4

Anforderungen an d i e Z i e l struktur

W a s ist d e r Bewertungs hi n t e r g r u n d ?

D I A G N O S E verkürzt die Fehlersuchzeit zumindest bei a n g e l e r n t e n K r ä f t e n .

5

Anforderungen zur Projektdurchführung

Was sind die R e s s o u r c e n für das Projektmanagement?

D I A G N O S E V e r s i o n 1.0 i s t i n n e r h a l b von 3 M o n a t e n mit 2 Mann zu e n t w i c k e l n .

Tab.

12-1.

Kategorien

von

das

Anforderungen

hend von unserem (Vor)-Wissen über die erfolgversprechende Arbeitstechnik und der Klarheit der Zielkriterien, sind verschiedene Problemfelder zu unterscheiden (vgl. Tab. 12-2). Unsere Konstruktionsaufgabe sei ein Vollzugsproblem (Feld 1.1 in Tab 12-2). Die erfolgversprechende Arbeitstechniken zur Spezifikation beziehen sich damit auf: o eine Schrittweise Verfeinerung (vertikale Ebene) und o eine modulare Strukturierung (horizontale Ebene) von Anforderungen. Für beide Fälle bedürfen wir einer Rechnerunterstützung. Die Konstruktionsgröße bestimmt welche (rechnergestützten) Arbeitstechniken anzuwenden sind. Bei einer kleinen Konstruktionsaufgabe ist die Spezifikation der einzelnen Verarbeitungsprozesse und der Datenrepräsentation' zu meistern. Bei einer sehr großen Aufgabe sind zusätzlich die Fortschritte der System-Software und -Hardware während der benötigten Planungs- und Realisierungszeit einzukalkulieren. Außerdem ändern sich Anforderungen in dieser relativ langen Zeit. Zu spezifizieren ist daher eine Weiterentwicklung, d.h. ein

12. Spezifikation mit LISP

333

Zu P r o j e k t b e g i n n die A r b e i t s T e c h n i ken 1

2

Tab.

erfolgversprechende Prinzipien, Methoden und Instrumente zur Problem1ösung

12-2.

Problemarten

Klarheit

gering II

Vol1zugs probleme

Defi ni t i o n s Probleme der Automati onsaufgäbe

unklar

Zielerrei c h u n g s probleme

Steuerungsprobleme eines konti nui erli c h e n Herantastens

beim

1

Kl ei ne K o n s t r u k t i on

2

Mi t t l e r e K o n s t r u k t i on

3

Große < K o n s t r u k t i on

100.000

4

Sehr große mehrmals K o n s t r u k t i on 100.000 (noch größer "zerfal1en" in e i g e n ständige Tei 1 e




(DEFINE REALISIERBAR? (LAMBDA (ANFORDERUNG DULDBARE_ABWEICHUNG) ( R E A L I S I E R B A R ?

Entsprechend diesem Prädikat kann die D U L D B A R E , ABWEICHUNG oder die ANFORDERUNG selbst modifiziert werden, um Realisierbarkeit zu erreichen. Die Aussage A2 („Benutzerfreundlichkeit") zeigt, daß auch die Modifikation einer Anforderung in gewissen Interpretationsgrenzen in Betracht kommen kann. Statt einen Teil von A2 mit A2.i (überlappende Bildschirmfenster) zu approximieren, ist ein Teil von A2 als „Kommandos über Funktionstasten" (A2.j) interpretierbar. In der Regel ist jedoch die Toleranzschwelle das Mittel, um eine Realisierbarkeit noch zu erreichen. Für eine Vorgabe können wir daher folgendes Prädikat definieren: e v a l > ( D E F I N E VORGABE? (LAMBDA (ANFORDERUNG DULDBARE,ABWEICHUNG) (LETREC ((ANFORDERUNG? (LAMBDA (AUSSAGE) (AND ( S P E Z I F I Z I E RT- SYSTEM? AUSSAGE) (OR (SPEZI FI Z I E R T - Z U - E R F Ü L L E N D E - L E I S T U N G ? AUSSAGE) (SPEZI FIZIERT-QUANTITATIVE-EIGENSCHAFT? AUSSAGE) (SPEZI FI ZIERT-QUALITATIVE-EIGENSCHAFT? AUSSAGE))))) (REALISIERBAR? (LAMBDA (ANFORDERUNG DULDBARE_ABWEICHUNG) (COND ( ( < = (BESTIMMUNG-SOLL-IST-ABWEICHUNG (FESTSTELLUNG-IST-VERHALTEN-BEZOGEN-AUF-SOLL (PLANVOLLZUG (PLANUNG ANFORDERUNG DULDBARE,ABWEICHUNG)))) DULDBARE,ABWEICHUNG) #T) ((MODIFIZIERBAR? ANFORDERUNG) (REALISIERBAR? (MODIFIKATION ANFORDERUNG) DULDBARE,ABWEICHUNG))

12. Spezifikation mit LISP

(AND

337

( ( M O D I F I Z I E R B A R ? DULDBARE_ABWEICHUNG) (REALISIERBAR? ANFORDERUNG (MODIFIKATION DULDBARE.ABWEICHUNG))) (T N I L ) ) ) ) ) (ANFORDERUNG? ANFORDERUNG) ( R E A L I S I E R B A R ? ANFORDERUNG DULDBARE_ABWEICHUNG))))) ==> V O R G A B E ?

Die einzelne Anforderung mag das Prädikat V O R G A B E ? erfüllen; im Zusammenhang mit anderen jedoch nicht. Naturgemäß gibt es konkurrierende und sich widersprechende Vorgaben. Vorrang- und Kompromißentscheidungen sind daher im Rahmen der Spezifikation zu treffen. Eine pragmatische Vorgehensweise beachtet bei den einzelnen Spezifikationsschritten vorrangig kritische Vorgaben und gewährleistet die anderen quasi nachträglich durch gezielte Modifikationen. Die kritische Vorgaben intuitiv zu erkennen, ist die „Kunst" des erfahrenen Konstrukteurs. Ziel der Spezifikation ist es, die Vorgaben so auszuarbeiten, daß sie durch passende Verknüpfungen möglicher Konstrukte abbildbar werden. Dazu bedarf es ihrer Strukturierung in Teilbereiche, die dann weitgehend unabhängig von einander mehr und mehr präzisiert werden. Die Konstruktion wird als „Geflecht" von Konstrukten („Moduln"), die über Schnittstellen miteinander verknüpft sind, spezifiziert. Wir verwenden hier den üblichen Begriff „Modul" im Sinne eines Synonyms für Konstrukt bzw. Teilkonstruktion. Die "Dopppelgesichtigkeit" jeder Schnittstelle, die auch der englische Begriff „interface" betont, bezieht sich auf die Menge aller Annahmen, die jeweils der eine Modul vom anderen unterstellt und umgekehrt. Innerhalb der Schnittstelle werden daher die relevanten Eigenschaften für ihre Kommunikation präzisiert.

338

III. Konstruktionsempfehlung

12.2 Import-Export-Beschreibung Die Wechselwirkungen zwischen den einzelnen Moduln sind mit Hilfe einer „Benutzt"-Relation und einer „Enthält"-Relation beschreibbar. Die „Enthält"Relation verweist auf die Möglichkeit, daß ein Modul selbst wieder in Module zerlegbar sein kann. Die „Benutzt"-Relation verweist auf die Modul-Schnittstelle. Die Modul-Schnittstelle ist gliederbar in einen Import- und einen Export-Teil. Die Import-Schnittstelle beschreibt welche Konstrukte der Modul aus anderen Moduln benutzen kann. Die Export-Schnittstelle nennt die Konstrukte, die der Modul für andere Moduln bereitstellt. Wir sprechen daher von einem Import, wenn ein Modul M eine Leistung eines Moduls M £ verwendet. Der Modul M( importiert (benutzt) ein Konstrukt, das M g exportiert. Bei der Diskussion des Verbundes von Konstruktor, Selektor, Prädikat und Mutator haben wir eine Hierarchie unterstellt (vgl. Bild 4.2-1). Sie ist im Sinne der „Benutzt"-Relation zyklenfrei, d.h. aus „M( benutzt M E " folgt, daß M ß nicht Mj benutzt und daß kein in der Hierarchie unter ME befindlicher Modul den Modul Mj benutzt. Außerdem gibt es einen hervorgehobenen Modul (Hauptmodul) mit einer besonderem Konstrukt, das als erstes appliziert wird. Der gesamte übrige Berechnungsvorgang (dynamische Ablauf) erfolgt über „Benutzt"-Relationen. In der Praxis geschieht das Spezifizieren der Moduln zunächst "skelettartig" mit lose verknüpften, verbalen Beschreibungen und Absichtserklärungen. Darauf folgt eine Phase der Konsolidierung, in der Schnittstellen und Implementationansätze verbindlich vereinbart werden. Anschließend sind diese Beschreibungsrahmen („Skelette") auszufüllen. Das Ausfüllen kann mehr oder weniger lokal sequentiell erfolgen. Erforderlich ist allerdings die Bezugnahme auf andere bereits teilweise erstellte „Skelette". Empfohlen wird dazu folgender Beschreibungsrahmen, der die Aufgabe einer Checkliste hat (BFN-Notation vgl. Abschnitt 2.2): (

II (EFFEKTE < t e x t > ) {))

mit

: : = Mnemotechnisch p r ä g n a n n t e s Symbol, das s i c h von a l l e n a n d e r e n u n t e r s c h e i d e t < s c h n i t t s t e l 1 e > : : = S c h n i t t s t e l l e zu M o d u l n a u f h ö h e r e r und tieferer Hierarchie-Ebene < t e x t > : : = F r e i e r T e x t z u r K u r z b e s c h r e i b u n g des Moduls ::= Verbale Beschreibung einzelner Modul"Funktionen

12. Spezifikation mit LISP

339

Mit der Import-/Export-Unterscheidung bei der Schnittstelle entsteht daraus folgendes „Skelett". Dabei verdeutlichen die fettgedruckten Begriffe die Angaben, auf die man sich im jeweiligen Spezifikationsschritt konzentriert. ( ((EXPORT ( K e x p o r t - f u n k t i o n X ) ) (IMPORT ({ 1))) (EFFEKTE ( < t e x t - z u r - a l 1 gerneinen-beschreibung>) ({ 1)) Ausgangspunkt ist ein eindeutiger, kennzeichnender Modulname (vgl. Abschnitt 11.1). Mit der Eintragung unter EFFEKTE beschreiben wir alle zu erfüllenden Leistungen und Eigenschaften des gesamten Moduls in Form eines kurzen Textes. Die EXPORT- und IMPORT-Angaben definieren die Modul-Hierarchie. In der Praxis können wir selten diese Hierarchie vom Hauptmodul ausgehende konsistent aufbauen. Neben den „top down"-Schritten entsteht unser Skelett „bottom up", „middle out", „inside out", "outside in" und/oder „hardest first". Die EXPORT- und IMPORT-Schnittstellen beschreiben wir daher eher Zug um Zug mit laufenden Korrekturen. Während im Abschnitt EFFEKTE der Modul insgesamt beschrieben ist, sind die einzelen zu erfüllenden Leistungen, hier als bezeichnet, zunächst durch einen kennzeichnenden Namen anzugeben. Dieses erste Skelett ermöglicht eine Modul-Hierarchie als Systemarchitektur zu entwerfen. Im allgemeinen ist jedoch festzustellen, daß einzelne Leistungen von mehreren Moduln auf unterschiedlichen Hierarchieebenen benötigt werden. Ein Beipiel hierfür ist die Behandlung von Fehlermeldungen. Um das Schichtenprinzip (Importe nur aus der nächst tieferen Hierarchiestufe!) nicht generell zu durchbrechen, betrachten wir solche überall gebrauchten Moduln gesondert, also außerhalb der Hierarchie. Diese sogenannten „Dienstleister" (quasi globale Moduln) können in jeden Modul „eingebettet" werden. Wir geben sie im Abschnitt NUTZT an. ( ((EXPORT ( ( < e x p o r t - f u n k t i o n > ) ) ) (IMPORT ({ 1))) (EFFEKTE ( < t e x t - z u r - a l 1 gerneinen-beschreibung>) (NUTZT ( ( < d i e n s t l e i s t e r - m o d u l > ) ) ({ ) ) ) Im nächsten Verfeinerungsschritt sind die Funktionen des Moduls weiter zu präzisieren und zwar durch die Beschreibung ihrer Ein- und Ausgabe. Im Abschnitt INPUT geben wir daher die Argumente, hier , an. Im Abschnitt OUTPUT formulieren wir ihr Ergebniss, hier . Der Ab-

340

III. Konstruktionsempfehlung

schnitt EFFEKT-FKT dient zu verbalen Beschreibung der Leistung der einzelnen Funktionen, hier . ( ((EXPORT (EFFEKTE

({}))

(IMPORT

(( })))

()

( N U T Z T ( { < d i e n s t l e i s t e r > 1) ( ( ( < m o d u l - f u n k t i o n ) (INPUT ( ( < a r g u m e n t > ) ) ) (OUTPUT ( < w e r t - b e s c h r e i b u n g > } ) (EFFEKT-FKT ( < t e x t - z u r - b e s c h r e i bungder-funkti on>)))( ))

Eine weitere Verfeinerung, insbesondere der IMPORT-Schnittstelle, führt, indem die benötigten Funktionen aus den einzelnen Moduln genannt werden, zu einer Systemarchitektur, die als Ausgangsbasis für die Implementation als LISPKonstrukte geeignet ist. Um gestaltungsrelevante Restriktionen für die weitere Verfeinerung bzw. die Implementation festhalten zu können, ist ein Abschnitt EINSCHRÄNKUNG vorgesehehen. Grundprinzip des Abschirmens von Werten („informationen hiding") ist das Verhindern des direkten Zugreifens auf interne Zustände (vgl. Abschnitt 9.1). Dem Benutzer eines importierten Wertes ist zu verheimlichen, ob dieser z.B. aus einer Datei (permanenter Datenträger) oder aus einem Arbeitsspeicher (temporärer Datenträger) stammt. Um jedoch gegebenenfalls deutlich zu machen, welche Veränderungen bleibende Wirkung haben, also länger leben als die Aktivierung des Moduls (Dauerhaftigkeit, Persistenz), dient der Beschreibungsslot NEBENEFFEKT. ( ((EXPORT ({})) (IMPORT ( { ( < m o d u l > ( { < f u n k t i o n > } ) ) } ) ) (EFFEKTE

()

(((

(INPUT ((})) (OUTPUT {))} {(NEBENEFFEKT ())))1 ))

Dieses Spezifizieren ist ein Wechselspiel von Problemdurchdringung (Analyse), Erarbeitung einer Modulhierarchie (Synthese) und Überprüfung von Modulbeschreibungen (Revision). In diesem Wechselspiel entstehen neue, zum Teil bessere Ideen, die häufig eine Umstrukturierung bedingen. Der Beschreibungs-

341

12. Spezifikation mit LISP

rahmen erleichtert das Modifizieren. Mit Hilfe des Texteditors sind die einzelnen Slots des Rahmens leicht „umhängbar". Beispiel: FÜRSORGE (Automation im Sozialamt einer Kommune) Das System FÜRSORGE rationalisiert das Zahlungbarmachen von Sozialhilfe. Es unterstützt die Antragstellung, die Aktenverwaltung und die Aktualisierung von Berechnungsgrundlagen und Regelsätzen. Die Anforderungen seien so weit erarbeitet, daß der Entwurf einer Modul-Hierarchie zu spezifizieren ist. Wir skizzieren diese grob, wie folgt: (FÜRSORGE ((EXPORT (IMPORT (EFFEKTE

()) (ANSPRUCHSANALYSE AKTENVERWALTUNG

(FÜRSORGE p r ü f t A n t r ä g e a u f Z a h l u n g von S o z i a l h i l f e , e r s t e l l t t e r m i n g e r e c h t A u s z a h l u n g s a n o r d u n g e n und f ü h r t d i e A k t e n . FÜRSORGE w e i s t u n b e r e c h t i g t e T e r m i nalbenutzer ab.)

(FORMULARBEZOGENE-DATENPFLEGE (ANSPRUCHSANALYSE ((EXPORT ( B E S C H E I D ) ) (IMPORT ( R E G E L S Ä T Z E (EFFEKTE

(BESCHEID

ZUGANGSPRÜFUNG)))

RECHTSBELEHRUNG))

BERECHNUNGSGRUNDLAGE)))

(ANSPRUCHSANALYSE e r m i t t e l t a u s den g e p r ü f t e n A n t r a g s d a t e n B e t r ä g e und Z a h l u n g s t e r m i n e , f a l l s A n sprüche bestehen)) RÜCKRECHNUNG))

(AKTENVERWALTUNG ( ( E X P O R T (VORGANG)) (IMPORT ( A N T R A G S T E L L E R ) ) ) (EFFEKTE

(AKTENVERWALTUNG s p e i c h e r t f ü r j e d e n A n t r a g s t e l l e r a l l e i h n b e t r e f f e n d e n V o r g ä n g e u n a b h ä n g i g vom S a c h b e a r b e i t e r i n e i n e r A k t e . Für f e s t g e s t e l l t e A n s p r ü c h e g e n e r i e r t AKTENVERWALTUNG d i e A u s z a h l u n g s a n o r d n u n g . Das A k t e n z e i c h e n , Name des A n t r a g s t e l l e r s oder der e i n e s b e t r o f f e n e n F a m i l i e n m i t g l i e d e s sind Seiektionsmerkmale)

(VORGANG

AUSZAHLUNGSANORDNUNG))

(ZUGANGSPRÜFUNG ((EXPORT ( B E F U G N I S ) ) ) (EFFEKTE

(Sozialamtsleiter, Abteilungsleiter, Sachbearbeiter und M i t a r b e i t e r haben u n t e r s c h i e d l i c h e B e f u g n i s s e . ZUGANGS-PRÜFUNG v e r w a l t e t entsprechende Passwörter))

(FORTSCHREIBUNG-PAßWORT

BEFUGNIS))

342

III. Konstruktionsempfehlung

Im Rahmen der weiteren Verfeinerung könnten wir z.B. folgenden Modul A N S P R U C H S A N A L Y S E erhalten. Er zeigt, daß zu diesem Zeitpunkt die einzelnen Modulfunktionen unterschiedlich fein spezifiziert sind. Die interne Funktion R Ü C K R E C H N U N G ist verbal beschrieben, während die exportierte Funktion B E S C H E I D auf ihre Implementations-Restriktionen verweist. (ANSPRUCHSANALYSE ((EXPORT ( B E S C H E I D ) ) (IMPORT ( ( R E G E L S Ä T Z E (WOHNGELD S O Z I A L H I L F E ) ) BERECHNUNGSGRUNDLAGE))) (EFFEKTE

(ANSPRUCHSANALYSE e r m i t t e l t aus den g e p r ü f t e n Ant r a g s d a t e n B e t r ä g e und Z a h l u n g s t e r m i n e , f a l l s Ansprüche bestehen))

(NUTZT

(BEREITSTELLEN-FORMULARE

ARBEITSTAGE))

((BESCHEID

(INPUT (DATEN - FORMULAR-13 AKTE-ANTRAGSTELLER)) (OUTPUT DATEN-FORMULAR-412) (EFFEKT-FKT ( B e r e c h n e t B e t r ä g e / T e r m i n e und p r ü f t d i e im H i n b l i c k a u f Ü b e r z a h l u n g ) ) (EINSCHRÄNKUNG ( D i e e i n z e l n e n B e r e c h n u n g s s c h r i t t e s i n d d i e D i a l o g s c h n i t t s t e l l e zu q u i t t i e r e n zu k o r r i g i e r e n . ) ) (NEBENEFFEKT ( J e d e r A u f r u f f ü h r t zu einem E i n t r a g im tem-Journal ) ) )

(RÜCKRECHNUNG (EFFEKT-FKT

))

Akte über oder Sys-

( E r m i t t e l t den z u s ä t z l i c h e n A n s p r u c h bei r ü c k w i r k e n d e n Ä n d e r u n g e n der R e g e l s ä t z e ) ) )

12.3 Zusammenfassung: Spezifikation Spezifizieren heißt Präzisieren und umfaßt alle Phasen. Ziel ist eine Spezifikation mit der Präzision der Mathematik und der Verständlichkeit der Umgangssprache. Die Aufgabenart (Vollzugs-, Defintions-, Zielerreichungs- oder Steuerungsproblem) und die Aufgabengröße (ein Mann, kleines Team oder große Mannschaft) erfordern unterschiedliche (rechnergestützte) Arbeitstechniken. Eine funktionale Anforderung beschreibt eine Leistung bzw. eine quantitative oder qualitative Systemeigenschaft. Von ihr fordern wir Realisierbarkeit und Gestaltungsrelevanz. Schrittweise werden qualitative Vorgaben verfeinert, bis die einzelnen Eigenschaften durch Ersatzleistungen approximierbar sind. "Benutzt"- und „Enthält"-Relationen dienen zum Spezifizieren von Moduln und ihren Wechselwirkungen. W i r definieren eine azyklische Modul-Hierarchie über E X P O R T - und IMPORT-Schnittstelle. Schritt für Schritt verfeinern wir dieses „Skelett". Dabei definieren wir allgemeine „Dienstleister" (globale M o duln) und geben Restriktionen und Nebeneffekte an.

12. Spezifikation mit LISP Charakteristische

343

Beispiele für Abschnitt 12:

;;;; F Ü R S O R G E (Automation im S o z i a l a m t einer Kommune) ;;; D e f i n i t i o n s p r o b l e m bei m i t t l e r e K o n s t r u k t i o n s g r ö ß e ;; A p p r o x i m i e r b a r e

Vorgabe

AI FÜRSORGE u n t e r s t ü t z t a u s r e i c h e n d das

Sozialamt-Management.

;; K o n k u r r i e r e n d e Vorgabe zu AI A2 FÜRSORGE schützt den S a c h b e a r b e i t e r vor permanenter Arbeitskontrol1e. ;; Keine Vorgabe ( R e a l i s i e r b a r k e i t und G e s t a l t u n g s r e l e v a n z fehlen) A3 FÜRSORGE schafft große Freude.

Die schrittweise Approximation von AI führt zu folgenden Anforderungen: Al.l FÜRSORGE b i l a n z i e r t das IST-Geschehen in S o z i a l a m t bezogen auf S a c h b e a r b e i t e r , V o r g a n g s a r t und Zeitraum. AI.2 FÜRSORGE v e r g l e i c h t die B i l a n z e r g e b n i s s e mit denen von ähnlichen S o z i a l ä m t e r n . AI.3 FÜRSORGE stellt sogenannte "WENN ... DANN ..."-Situationen dar. Offensichtlich

konkurriert Al.l mit A2.

Ein Kompromiß wäre

Al.l FÜRSORGE b i l a n z i e r t das IST-Geschehen im S o z i a l a m t auf A b t e i l u n g e n , V o r g a n g s a r t alle 14 Tage.

z.B.:

bezogen

;; Skizze einer a u s g e f ü l l t e n C h e c k l i s t e zur M o d u l - B e s c h r e i b u n g (REGISTRATUR ((EXPORT (NEUZUGANG Ä N D E R U N G LÖSCHUNG S P E R R U N G RECHERCHE)) (IMPORT ((JOURNAL (EINTRAGUNG)) (VERTRAULICHKEIT (ÜBERPRÜFUNG V E R S C H L Ü S S E L U N G ENTSCHLÜSSELUNG)))) (EFFEKTE

(Speichern und B e r e i t s t e l l e n

(NUTZT

(FEHLERMANAGEMENT))

((NEUZUGANG

von

Akten))

(INPUT (AKTENZEICHEN V O R G A N G B E R E C H T I G U N G ) ) (OUTPUT V O L L Z U G S M E L D U N G ) (EFFEKT-FKT (NEUZUGANG legt eine Akte an, falls unter dem A k t e n z e i c h e n noch keine g e s p e i c h e r t ist, und vergibt den B e - r e c h t i g u n g s c o d e . ) ) ( E I N S C H R Ä N K U N G (Zur D a t e n s i c h e r h e i t führt ein korrekter Neuzugang zum s o f o r t i g e n Rückschreiben das Datensatzes in die Datei.)) (NEBENEFFEKT (Das J O U R N A L weist jeden Aufruf mit Vertraul i c h k e i t s s t u f e > 5 aus.))) (ÄNDERUNG . (LÖSCHUNG . (SPERRUNG . (RECHERCHE ))

344

III. Konstruktionsempfehlung

13. Wechsel der Abbildungsbasis Effizienz, Transparenz, Wartbarkeit, Ergänzbarkeit oder Portabilität sind (teilweise konkurrierende) Anforderungen für die Wahl der geeigneten Abbildungsbasis („Datenrepräsentation"). Verschieben sich die Bewertungsgewichte, dann kann ein Wechsel angebracht sein. Wir unterstellen eine Konstruktion in SCOOPS (vgl. Abschnitt 10.1), die nicht akzeptable Anwortzeiten aufweist (Effizienzmangel). Alternativ könnte es um eine Portierung gehen, bei der die SCOOPS-Konstrukte nicht verfügbar sind. Von dieser Klassen-Instanz-Abbildung wechseln wir auf eine DEFINE-STRUCTURE-Abbildung (vgl. Abschnitt 9.2), die mit einem Konstrukt zur Selektion der jeweiligen Operation zusammenarbeitet. Als Beispiel dient ein Graphikprogramm, das einen verschiebbaren Zeichenstift („Turtle") verwendet (Abschnitt 13.1). Damit zeichnen wir Struktogrammen (Nassi/Shneiderman-Zeichensymbole, vgl. Abschnitt 2.2). Die SCOOPSKonstruktion basiert auf dem Versenden von Nachrichten (engl.: message passing). Wir nennen sie kurz "Nachrichtenversand" (Abschnitt 13.2). Bei der DEFINE-STRUCTURE-Lösung selektiert eine Verzweigungsfunktion (engl.: dispatch procedure) die jeweils anzuwendend Zeichenoperation. Wir nennen sie kurz „Verzweigungsfunktion" (Abschnitt 13.3). Um in beiden Fällen die gleichen Eingabebefehle zu realisieren, bilden wir die Benutzeroberfläche mit Makros ab (vgl. Abschnitt 8.3).

13.1 Turtle Graphik Die Struktogramme zeichnen wir mit Hilfe graphischer Grundprozeduren (engl.: primitives), die in PC Scheme eingebaut sind. Solche graphischen „Primitives", wie z.B. DRAW-LINE-TO, erfordern ein Ausgabe-Fenster, das sich in einem Graphikmodus befindet. Welcher Graphikmodus mit dem SET-VIDEOMODE!-Konstrukt aktivierbar ist, hängt von der jeweiligen Hardware ab. Mit der Zuweisung (SET-VIDEO-MODE! 4) gehen wir von einem (kompatiblen) IBM PC aus und verwenden dessen CGA-Modus. Bild 13.1-1 zeigt das zugehörige Koordinatensystem. /Hinweis: Graphikmodus. Verfügt Ihr Rechner über eine Graphikkarte mit höherer Auflösung, z.B. EGA oder VGA, dann ist an die Stelle von (SET-VIDEOMODE! 4) ein anderer Wert zu setzen (EGA := 14 oder VGA := 16; vgl. TI PC Scheme Manual). Die Koordinaten- und Längenangaben sind dann entsprechend anzupassen./ Eine Linie wird auf dem Bildschirm erzeugt, indem wir den Cursor von ihrem

345

13. Wechsel der Abbildungsbasis

(-160

99)

(159,99)

A

1 Y *

(0,0)

(-160 Bild

-100) 13.1-1.

X

>

Koordi natensystem -

(SET-VIDEO-MODE!

(159

im 4)

CGA-Modus (IBM

-100)

PC)

-

Anfangspunkt zu ihrem Endpunkt verschieben. Der Anfangspunkt wird durch die aktuelle Cursorstellung definiert. Mit dem eingebauten POSITION-PENKonstrukt ist die aktuelle Position durch Angabe des X,Y-Wertes setzbar. Der X,Y-Wert des DRAW-LINE-TO-Konstruktes ist der Endpunkt. Als Nebeneffekt der Applikation des DRAW-LINE-TO-Konstruktes entsteht die Linie. Mit diesen Primitives definieren wir einen Zeichenstift. Es ist ein Pfeil, der eine aktuelle Position und eine definierte Richtung hat. Er entspricht damit der sogenannten „Turtle Graphik", wie sie durch die Programmiersprache LOGO bekannt geworden ist. Unsere Turtle (deutsch: Schildkröte, hier gleich Zeichenstift) kann vorwärts in der eingestellten Richtung bewegt werden und dabei entweder eine „Spur" zeichnen oder nicht. Dies hängt davon ab, ob der Zustand der Turtle auf Malen gesetzt wurde. Wir können die Turtle drehen und damit ihre Zeichenrichtung verändern. Mit den beiden Operationen Drehen und Vorwärtsverschieben zeichnen wir die gewünschten Figuren. /Exkurs: Turtle-Graphik. Der Name stammt von einem schildkrötenähnlichen Roboter. Seymour Papert entwickelte diesen Roboter, um Kindern das Programmieren anschaulich zu machen. Der Roboter konnte sich nach Befehlen auf dem Boden fortbewegen und mit einem Schreiber eine Spur ziehen. Später wurde der Roboter als ein Pfeil auf dem Bildschirm abgebildet. Der Name blieb als „Markenzeichen" dieser Graphikart erhalten.7 Eine einfaches Rechteck mit den Koordinaten (x , y,) und (x2, y2) für zwei gegenüberliegende Ecke läßt sich, wie folgt, definieren:

III. Konstruktionsempfehlung

346 eval>

( D E F I N E RECHTECK (LAMBDA ( X _ l Y_1 X_2 X _ 2 )

(SET-VIDEO-MODE!

4)

(POSITION-PEN

X_1 Y_1)

(DRAW-LINE-TO

X_1 Y_2)

(DRAW-LINE-TO

X_2

Y_2)

(DRAW-LINE-TO

X_2

Y_l)

;Wir u n t e r s t e l l e n ganze Z a h l e n ; im g ü l t i g e n B i l d b e r e i c h . ;Graphikmodus

(DRAW-LINE-TO X_1 Y _ l ) ; ; Damit d a s B i l d e i n e b e l i e b i g l a n g e ; ; werden k a n n , w i r d a u f e i n e E i n g a b e ; ; den T e x t m o d u s g e s c h a l t e t w i r d . (READ-CHAR) ( S E T - V I D E O - M O D E ! 3 ) ) ) = = > RECHTECK

einschalten

Zeit betrachtet gewartet, bis in

Ein Struktogramm-Block zeichnen wir als ein Rechteck, wobei wir die linke untere Ecke in X,Y-Koordinaten, die Breite (X-Richtung) und die Höhe (Y-Richtung) angeben. Die beiden Iterationen: "while do od" und „do until od" sowie die Alternative „if then eise fi" positionieren wir entsprechend. Stets geben wir die linke untere Ecke, die Breite und die Höhe an.

13.2 Lösung: Nachrichtenversand In der Nachrichtenversand-Lösung ist unsere Zeichenaufgabe (vgl. Abschnitt 13.1) in eine Hierarchie von drei Klassen gegliedert (Bild 13.2-1). Die oberste Klasse heißt BASIC-GRAPHIK. In Flavor-Systemen kennzeichnet man den Flavor, auf den einen ganze Familie von Flavors aufbaut, mit dem Präfix BASIC. Die Klasse BASIC-GRAPHIK umfaßt Konstanten und Hilfsoperationen. Sie speichert z.B. die Methode GRAD->BOGEN, die eine Winkelangabe in Grad in einen Wert in Bogenmaß umrechnet. Auf der Klasse BASICGRAPHIK baut die Klasse ZEICHENSTIFT auf. Die Klasse ZEICHENSTIFT definiert ein Objekt, dessen Slots die aktuellen X,Y-Koordinaten, die Richtung und den Malzustand festhalten. Zusätzlich ist ein Slot für die Farbe des Zeichenstifts vorgesehen. Die dritte Hierarchieebene bildet die Klasse FIGUR. Sie enthält die Methoden zum Zeichen der Struktogrammfiguren. Einer Instanz der Klasse FIGUR werden die einzelnen Nachrichten geschickt. Sie führen zur Anwendung der Methoden, die das gewünschte Struktogramm auf dem Bildschirm erzeugen. Ist eine Struktogrammfigur zu zeichnen, dann ist die zugehörige Methode der Klasse FIGUR anzuwenden. Das Applizieren einer Methode erfolgt in SCOOPS mit Hilfe des SEND-Konstruktes. Beim Abarbeiten der Methode benötigen wir

13. Wechsel der Abbildungsbasis

347

Methoden:

Slots:

GRAD->BOGEN BOGEN ->GRAD NORMALISIERUNG GRAPHIK-MODUS TEXT-MODUS GET-X-GERUNDET GET-Y-GERUNDET SET-POSITION! VORWAERTS! ZURUECK! SET-RICHTUNG! LINKS-DREHEN! RECHTS-DREHEN! SET-COLOR!

RICHTUNG MALEN FARBE

RECHTECK UNTIL WHILE I F-THEN- ELSE

eval > (S->0 BILD-1 IF-THEN-ELSE -150 -90 300 180) ==> ... ; Zeichnet die I F - T H E N - E L S E - F i g u r auf dem B i l d s c h i r m Legende: Vgl. Bild 10-3. Vgl. Programm 11.2-1. Bild 13.2-1. Programm zum Zeichen von V e r e r b u n g s g r a p h der Lösung

Struktogrammen "Nachrichtenversand"

die Kenntnis, an welche Instanz die Nachricht gesendet wurde. Da SCOOPS über kein entsprechendes SELF-Konstrukt verfügt, helfen wir uns wieder mit der Bindung des Instanznamens an die erste LAMBDA-Variable jeder Methode (vgl. Abschnitt 10.1). Wir definieren daher alle Methoden einheitlich mit zumindest dieser einen LAMBDA-Variablen, auch wenn in der Methode der Instanzname nicht benötigt wird. Zur Vereinfachung der Schreibweise definieren wir das Makro S->0. Dieses Makro transformiert: (S->0 OBJEKT NACHRICHT ARG-1 . . . ARG-N) in das S C O O P S - K o n s t r u k t : (SEND O B J E K T NACHRICHT 'OBJEKT ARG-1 ... ARG-N).

348

III. Konstruktionsempfehlung

Wenn innerhalb einer Methodendefinition eine andere Methode anzuwenden ist, müssen wir beachten, daß ihre LAMBDA-Variablen noch nicht an die Werte ihrer Argumente gebunden sind. Da das SEND-Konstrukt sein erstes „Argument" (das Objekt, an das die Nachricht gesendet wird) nicht evaluiert, können wir nicht, wie folgt, formulieren: eval> (DEFINE-METHOD (FIGUR RECHTECK) (OBJEKT X-START Y-START X-LAENGE Y-LAENGE) (SET! MALEN NIL) (S->0 OBJEKT SET-POSITION! X-START Y-START) ¡Falsch !!! (SET! MALEN #T) (S->0 OBJEKT VORWAERTS! X-LAENGE) ¡Falsch !!! ... ) = = > RECHTECK Mit dieser Formulierung schicken wir die Nachricht an die nicht existierende Instanz OBJEKT statt an den Wert von OBJEKT. Erst wenn die Methode RECHTECK angewendet wird, hat OBJEKT die Instanz als Wert. Erst zu diesem Zeitpunkt kennen wir für unser S->0-Konstrukt die Instanz. Die Zeitpunktverschiebung können wir bekanntermaßen durch die Konstruktekombination QUOTE und EVAL erreichen (vgl. Abschnitt 2.3). Hier nutzen wir die READMakros QUASIQUOTE (Kurzform: ' ) und UNQUOTE (Kurzform:, ) als syntaktische Erleichterung (vgl. Abschnitt 8.3). Die Methode RECHTECK ist dann wie folgt zu definieren: eval> (DEFINE-METHOD (FIGUR (OBJEKT (SET! MALEN NIL) ( EVAL ' (S->0 .OBJEKT (SET! MALEN #T) (EVAL ' (S->0 .OBJEKT (EVAL '(S->0 .OBJEKT (EVAL '(S->0 .OBJEKT (EVAL '(S->0 .OBJEKT (EVAL ' ( s - > o .OBJEKT (EVAL '(S->0 .OBJEKT (EVAL '(S->0 .OBJEKT (EVAL '(S->0 .OBJEKT

RECHTECK) X-START Y-START X-LAENGE Y-LAENGE) SET-POSITION! .X-START

.Y-START))

VORWAERTS! .X-LAENGE)) LINKS-DREHEN! .90)) VORWAERTS! .Y-LAENGE)) LINKS-DREHEN! ,90)) VORWAERTS! .X-LAENGE)) LINKS-DREHEN! 90)) VORWAERTS! .Y-LAENGE)) LINKS-DREHEN! 90))) = = > RECHTECK

In diesem RECHTECK-Konstrukt ist die immer wiederkehrende Folge: (EVAL '(S->0 , ... )) durch ein Makro ersetzbar, so daß wir Schreibarbeit einsparen. Wir definieren daher S-> als Makro so, daß die folgende Transformation entsteht: (S-> OBJEKT MESSAGE ARG-1 ... ARG-N) expandiert zu (EVAL "(S->0 .OBJEKT MESSAGE ,ARG-1 ... .ARG-N)) Diese Schreibweise mit den Kurzformen ' und , entspricht folgender Formulierung (vgl. Abschnitt 8.3):

349

13. Wechsel der Abbildungsbasis (EVAL

(QUASIQUOTE ( S - > (UNQUOTE MESSAGE

OBJEKT)

(UNQUOTE

ARG-1)

(UNQUOTE

ARG-N))))

S-> soll für eine beliebige Anzahl von Argumenten benutzbar sein. Aus ARG-1 ... ARG-N ist die Liste: ((UNQUOTE A R G - 1 )

...

(UNQUOTE

ARG-N)

zu konstruieren. Für diese Iteration bietet sich das MAP-Konstrukt an. Es wendet die anonyme LISP-Funktion: (LAMDA

(X)

(LIST

'UNQUOTE

x))

an. S-> ist, wie folgt, definierbar: eval>

(MACRO S - > (LAMBDA ( S E X P R ) ( L E T ( ( O B ( L I S T - R E F SEXPR 1 ) ) (MES ( L I S T - R E F SEXPR 2 ) ) ( A R G - L I S T E (MAP (LAMBDA ( X ) ( L I S T 'UNQUOTE X ) ) (CDDDR S E X P R ) ) ) ) ( L I S T 'EVAL ( L I S T 'QUASIQUOTE (CONS " S - > 0 (CONS ( L I S T 'UNQUOTE OB) (CONS MES A R G - L I S T E ) ) ) ) ) ) ) ) = = > S - >

Dokument 1 3 . 2 - 1 T i t e l : Programm zum Z e i c h n e n von S t r u k t o g r a m m e n . L ö s u n g "NACHRICHTENVERSAND" E r s t e l l t am: 2 3 . 1 2 . 8 9 ¡ l e t z t e Ä n d e r u n g : 2 3 . 1 2 . 9 0 AI Al.l AI.2 AI.3 AI.4

NACHRICHTENVERSAND z e i c h n e t : ein Rechteck, e i n e I t e r a t i o n : DO . . . U N T I L . . . OD, e i n e I t e r a t i o n : WIHLE . . . DO . . . OD und e i n e A l t e r n a t i v e : I F . . . THEN . . . F I .

El

NACHRICHTENVERSAND v e r w e n d e t d i e K l a s s e n - H i e r a r c h i e B A S I C - G R A P H I K - > Z E I C H E N - S T I F T - > FIGUR (vgl. Bild 13.2-1)

E2

NACHRICHTERVERSAND v e r w e n d e t ( v g l . Abschnitt 10.1)

SCOOPS-Konstrukte.

E3

NACHRICHTENVERSAND P r i mi t i v e s : GET-VIDEO-MODE! SET-VIDEO-MODE! CLEAR-GRAPHICS DRAW-LINE-TO POSITION-PEN SET-PEN-COLOR!

folgende

E3.1 E3.2 E3.3 E3.4 E3.5 E3.6

verwendet

graphische

350

III. Konstruktionsempfehlung

; ; ; TI

Bei spi el

;; E i n e A l t e r n a t i v e , in d e r e n l i n k e m Z w e i g e i n e W h i 1 e - I t e r a t i on ;; u n d in i h r e m r e c h t e n Z w e i g e i n e Unti 1 - I t e r a t i on d a r g e s t e l l t ;; ist. B e i d e I t e r a t i o n e n e n t h a l t e n e i n e S e q u e n z ; ; (vgl. Bild 13.2-2). (BEGIN (S->0 BILD-1 G R A P H I K - M O D U S ) (S - > o B I L D - 1 I F - T H E N - E L S E - 1 5 0 - 9 0 3 0 0 1 8 0 ) 0 - 9 0 150 1 4 4 ) (S - > o B I L D - 1 U N T I L (S - > o B I L D - 1 W H I L E - 1 5 0 - 9 0 150 1 4 4 ) (S - > o B I L D - 1 R E C H T E C K 15 - 5 0 1 3 5 4 0 ) (S - > 0 B I L D - 1 R E C H T E C K -135 -50 135 40) (READ) (S->0 BILD-1 T E X T - M O D U S ) ) ;; L a d e n d e r S C O O P S - K o n s t r u k t e (LOAD "SCOOPS.FSL") ;;;;

(vgl.

E2)

Klassen-Definitionen

;; B A S I C - G R A P H I K v e r e r b t d i e K o n s t a n t e n und H i l f s o p e r a t i o n e n . (DEFINE-CLASS BASIC-GRAPHIK (CLASSVARS (*Pi* 3.141592654)) (OPTIONS GETTABLE-VARIABLES SETTABLE-VARIABLES INITTABLE-VARIABLES)) ;; Z E I C H E N S T I F T v e r e r b t d i e X , Y - K o o r d i n a t e n , d i e R i c h t u n g , ;; d e n Z u s t a n d , ob e i n e " S p u r " g e z o g e n w e r d e n soll o d e r n i c h t , ;; u n d d i e F a r b e d e r " S p u r " . D a z u v e r e r b t s i e d i e S e l e k t o r e n ;; u n d M u t a t o r e n ; s o w o h l d i e g e n e r i e r t e n w i e d i e s e l b s t ;; d e f i n i e r t e n Z u g r i f f s m e t h o d e n , z . B . G E T - X - G E R U N D E T . (DEFINE-CLASS ZEICHENSTIFT ( I N S T V A R S (X 0) (Y 0) ( R I C H T U N G (MIXINS BASIC-GRAPHIK) (OPTIONS GETTABLE- VARIABLES SETTABLE- VARIABLES INITTABLE-VARIABLES))

0)

(MALEN #T)

(FARBE

1))

;; F I G U R v e r e r b t d i e M e t h o d e n z u m Z e i c h n e n d e r S t r u k t o g r a m m e . ;; A u s ihr w e r d e n d i e I n s t a n z e n g e b i l d e t , an d i e m i t S - > 0 ;; d i e N a c h r i c h t e n g e s e n d e t w e r d e n . (DEFINE-CLASS FIGUR (MIXINS ZEICHENSTIFT)) ;; I n s t a n z - B e i s p i e l (DEFINE BILD-1 (MAKE-INSTANCE ;;;;

FIGUR))

Syntax-Vereinfachung

; ; ; Makro S->0 : ; ;; (S->0 OBJEKT M E S S A G E ;;; e x p a n d i e r t zu: ;;; (SEND O B J E K T M E S S A G E (MÄCRO S->0 (LAMBDA (SEXPR) ' ( S E N D . ( L I S T - R E F S E X P R 1) . ( L I S T - R E F S E X P R 2) ' . ( L I S T - R E F S E X P R 1) ,@(CDDDR SEXPR))))

ARG) 'OBJEKT

ARG)

351

13. Wechsel der Abbildungsbasis Makro

S->

:

( S - > OBJEKT MESSAGE A R G - 1 . . . ARG-N) zu: (EVAL 4 ( S - > 0 .OBJEKT MESSAGE , A R G - 1 . . . MACRO S - > (LAMBDA ( S E X P R ) ( L E T ( ( O B ( L I S T - R E F SEXPR 1 ) ) (MES ( L I S T - R E F SEXPR 2 ) ) ( A R G - L I S T E (MAP (LAMBDA ( X ) ( L I S T 'UNQUOTE X ) ) (CDDDR S E X P R ) ) ) ) (LIST 'EVAL ( L I S T 'QUASIQUOTE (CONS ' S - > 0 (CONS ( L I S T 'UNQUOTE OB) (CONS MES A R G - L I S T E ) ) ) ) ) ) ) ) expandiert

;;;;

.ARG-N))

Methoden-Definitionen

(DEFINE-METHOD ( B A S I C - G R A P H I K ( * WERT (/ * P I * 1 8 0 ) ) )

GRAD->BOGEN)

(OBJEKT

WERT)

(DEFINE-METHOD (BASIC-GRAPHIK ( * WERT (/ 180 * P I * ) ) )

BOGEN->GRAD)

(OBJEKT

WERT)

NORMALISIERUNG s t e l l t s i c h e r , daß f ü r d i e B o g e n m a ß - A n g a b e WERT s t e t s g i l t : 0 < = WERT OBJEKT GET-X-GERUNDET) ( S - > OBJEKT G E T - Y - G E R U N D E T ) ) ) ( S - > OBJEKT G E T - X - G E R U N D E T ) ( S - > OBJEKT G E T - Y - G E R U N D E T ) ) ) ) )

; VORWAERTS! b e r e c h n e t a u s d e r L ä n g e n a n g a b e den E n d p u n k t d e r ; L i n i e . Dazu v e r w e n d e t s i e den R i c h t u n g s w e r t des j e w e i l i g e n : Zeichenstiftes. DEFINE-METHOD ( Z E I C H E N S T I F T VORWAERTS!) (OBJEKT LAENGE) ( S - > OBJEKT S E T - P O S I T I O N ! ( + X ( * LAENGE (COS R I C H T U N G ) ) ) ( + Y ( * LAENGE ( S I N R I C H T U N G ) ) ) ) )

352

III. Konstruktionsempfehlung

(DEFINE-METHOD (ZEICHENSTIFT ZURUECK!) (OBJEKT LAENGE) (S-> OBJEKT V O R W A E R T S ! (- LAENGE))) (DEFINE-METHOD (ZEICHENSTIFT S E T - R I C H T U N G ! ) (OBJEKT GRAD) (LET ((BOGEN (S-> O B J E K T G R A D - > B O G E N GRAD))) (SET! RICHTUNG (S-> OBJEKT N O R M A L I S I E R U N G BOGEN)))) (DEFINE-METHOD ( Z E I C H E N S T I F T LINKS-DREHEN!) (OBJEKT GRAD) (LET ((BOGEN (S-> OBJEKT G R A D - > B O G E N GRAD))) (SET! RICHTUNG (S-> OBJEKT N O R M A L I S I E R U N G (+ RICHTUNG B O G E N ) ) ) ) ) (DEFINE-METHOD (ZEICHENSTIFT RECHTS - DREHEN!) (OBJEKT GRAD) (S-> OBJEKT L I N K S - D R E H E N ! (- GRAD))) (DEFINE-METHOD (ZEICHENSTIFT S E T - C O L O R ! ) (OBJEKT WERT) (SET! FARBE WERT) (SET-PEN-COLOR! WERT)) (DEFINE-METHOD (FIGUR RECHTECK) (OBJEKT X-START Y-START X - L A E N G E Y-LAENGE) (SET! MALEN N I L ) (S-> OBJEKT S E T - P O S I T I O N ! X-START Y-START) (SET! MALEN # T ) (S-> O B J E K T V O R W A E R T S ! X-LAENGE) (S-> OBJEKT L I N K S - D R E H E N ! 90) (S-> OBJEKT V O R W A E R T S ! Y - L A E N G E ) (S-> OBJEKT LINKS-DREHEN! 90) (S-> OBJEKT VORWAERTS! X-LAENGE) (S-> OBJEKT L I N K S - D R E H E N ! 90) (S-> OBJEKT V O R W A E R T S ! Y - L A E N G E ) (S-> OBJEKT LINKS-DREHEN! 9 0 ) ) (DEFINE-METHOD (FIGUR UNTIL) (OBJEKT X-START Y-START X-LAENGE Y-LAENGE) (S-> OBJEKT RECHTECK X-START Y - S T A R T X - L A E N G E Y - L A E N G E ) (S-> OBJEKT RECHTECK (+ X-START (* 0.1 X - L A E N G E ) ) (+ Y-START (* 0.1 Y - L A E N G E ) ) (- X-LAENGE (* 0.1 X-LAENGE)) (- Y - L A E N G E (* 0.1 Y - L A E N G E ) ) ) ) (DEFINE-METHOD (FIGUR WHILE) (OBJEKT X-START Y - S T A R T X - L A E N G E Y-LAENGE) (S-> OBJEKT RECHTECK X-START Y - S T A R T X - L A E N G E Y-LAENGE) (S-> OBJEKT RECHTECK (+ X-START (* 0.1 X-LAENGE)) (+ Y-START) (- X-LAENGE (* 0.1 X-LAENGE)) (- Y - L A E N G E (* 0.1 Y - L A E N G E ) ) ) ) (DEFINE-METHOD (FIGUR IF-THEN-ELSE) (OBJEKT X-START Y - S T A R T X - L A E N G E Y - L A E N G E ) (LET ((X-ZW (+ X-START (/ X - L A E N G E 2))) (Y-ZW (+ Y - S T A R T (* 0.8 Y - L A E N G E ) ) ) )

13. Wechsel der Abbildungsbasis (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T (SET! MALEN (S-> O B J E K T Programm

353

RECHTECK X-START Y-START X-LAENGE Y-LAENGE) ()) SET-POSITION! X-START Y-ZW) T) VORWAERTS! X-LAENGE) ()) SET-POSITION! X-ZW Y-ZW) T) S E T - P O S I T I O N ! X - S T A R T (+ Y - S T A R T Y - L A E N G E ) ) ()) SET-POSITION! X-ZW Y-ZW) T) S E T - P O S I T I O N ! (+ X - S T A R T X - L A E N G E ) (+ Y - S T A R T Y - L A E N G E ) ) ()) SET-POSITION! X-ZW Y-ZW) T) SET-POSITION! X-ZW Y-START) ()) SET-POSITION! X-START Y-START)))

13.2-1

Zeichnen - Lösung

von Struktogrammen "Nachrichtenversand"

Legende: Nassi/Shneiderman-Kontrol1 struktur, Bild

13.2-2 (vgl.

Struktogramm-Beispiel Programmfragment

vgl.

-

Bild

2.2.3-2.

13.2-1)

13.3 Lösung: Verzweigungsfunktion In der obigen Nachrichtenversand-Lösung ist das Abarbeiten der SEND-Konstrukte aufwendig. Wir verkürzen die Antwortzeit, indem wir den Zeichenstift auf eine DEFINE-STRUCTURE-Lösung umstellen und zusätzlich alle obigen Methoden zusammen an eine globale Variable *OPERATION* binden, die

354

III. Konstruktionsempfehlung

selbst die Selektion der jeweils benötigten Operation („Methode") vornimmt. (Dies beschleunigt die Ausführung bei einem I B M AT ungefähr um 1:25). Dabei stellen wir durch die Änderung des Makros S - > 0 sicher, daß die gleichen Kommandos verarbeitet werden. Unser Beispielbild erzeugen wir wieder durch das Konstrukt: eval>

(BEGIN (S->0 BILD-1 (S->0 BILD-1 (S->0 BILD-1 C S->0 BILD-1 (S->0 BILD-1 (S->0 BILD-1 (READ-CHAR) (S->0 BILD-1

GRAPHIK-MODUS) IF-THEN-ELSE -150 UNTIL 0 WHILE -150 RECHTECK 15 RECHTECK -135 TEXT-MODUS))

==>

-90 -90 -90 -50 -50

300 150 150 135 135

180) 144) 144) 40) 40)

; S t r u k t o g r a m m gemäß ; Bild 11.2-2.

Das Konstrukt *OPERATION* ist eine Funktion höherer Ordnung. Sie hat als Wert die nachgefragte Operation zum Zeichnen der Figur. Sie ist daher eine Verzweigungsfunktion (engl.: dispatch function). Der Rückgabewert ist diejenige Funktion, die anhand des Arguments (der „Nachricht") ausgewählt wird; z.B.: eval>

(^OPERATION* 'RECHTECK) = = > #

¡"Methode"

RECHTECK

Das Konstrukt O P E R A T I O N * hat folgende Struktur: eval>

(DEFINE ^OPERATION* (LET ( . . . (RECHTECK (LAMBDA (UNTIL (LAMBDA ( D I S P A T C H (LAMBDA (CASE OPERATION

( (

... ...

) ... ) ...

(OPERATION)

) )

13. Wechsel der Abbildungsbasis (RECHTECK (UNTIL

...

)

355 RECHTECK) UNTIL)

) ¡Ende d e r l o k a l e n DISPATCH)) ==> O P E R A T I O N *

Variablen

Ein Rechteck, dessen linke untere Ecke beispielsweise die Position X := -150 , Y := -90 hat und dessen Breite := 300 und Höhe := 180 ist, zeichnen wir, wie folgt: eval>

( ( ^ O P E R A T I O N * 'RECHTECK) BILD-1 -150 -90 300 180) = = > ... ; R e c h t e c k f i g u r a u f d e m B i l d s c h i r m

Das neue Makro S - > 0 hat aus unserem bisherigen Zeichenkommando einen solchen Ausdruck zu expandieren. Es hat die Syntax: (S->0 O B J E K T

MESSAGE ARG-1

...

in folgendes Konstrukt zu überführen: ((•OPERATION*

'MESSAGE)

OBJEKT

ARG-N)

ARG-1

...

ARG-N)

Mit Hilfe der Konstrukte Q U A S I Q U O T E (Kurzform: ' ), UNQUOTE (Kurzform: , ) und UNQUOTE-SPLICING (Kurzform: ) können wir das Makro S - > 0 einfach formulieren (vgl. Abschnitt 8.3): eval>

(MACR0 S->0 (LAMBDA (SEXPR) ' ( ^OPERATION* ' , ( L I S T - R E F S E X P R 2)) . ( L I S T - R E F S E X P R 1) ,@(CDDDR SEXPR)))) ==> S->0

;'MESSAGE ;0BJEKT ;ARG-1 ...

ARG-N

Dokument 13.3-1 T i t e l : P r o g r a m m zum Z e i c h n e n von S t r u k t o g r a m m e n und F r a k t a l f i g u r (vgl. A b s c h n i t t 13.4). Lösung "VERZWEIGUNGSFUNKTION" E r s t e l l t am: 2 3 . 1 2 . 8 9 ¡ l e t z t e Ä n d e r u n g : 2 3 . 1 2 . 9 0 AI VERZWEIGUNGSFUNKTION zeichnet: Al.l ein Rechteck, A I . 2 e i n e I t e r a t i o n : DO . .. U N T I L ... O D , A I . 3 e i n e I t e r a t i o n : W I H L E . . . DO . . . O D , A I . 4 e i n e A I t e r n a t i ve: IF ... T H E N .. . FI u n d AI.5 die Farktalfigur "Koch-Kurve".

einer

356

III. Konstruktionsempfehlung

;;; ;;; ;;;

El

VERZWEIGUNGSFUNKTION verwendet das D E F I N E - S T R U C T U R E - K o n s t r u k t ( v g l . A b s c h n i t t 9.2) zur D e f i n t i o n eines Zei c h e n s t i f t e s .

;;; ;;;

E2

VERZWEIGUNGSFUNKTION a r b e i t e t mit e i n e r "dispatching-Funktion OPERATION*.

;;; ;;; ; ;; ;;; ;;; ;;; ; ;; ;;;

E3

;;; ;;; ;;;

E4

Das Makro sprechend berei t.

;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;;

T1 Tl.l Tl.2

Bei spi e l e v g l . Programm 1 3 . 2 - 1 Fraktalgeometrie "Schneeflocke" (vgl. Bild (BEGIN ( S - > 0 B I L D - 1 GRAPH I K - M O D U S ) ( S - > 0 B I L D - 1 KOCH-KURVE - 1 2 0 -47 4 1 5 8 ) (READ) ( S - > 0 B I L D - 1 TEXT-MODUS))

NACHRICHTENVERSAND Pr i mi t ives: E 3 . 1 GET - V I DEO - MODE! E 3 . 2 SET - V I DEO-MODE! E3.3 CLEAR-GRAPHICS E3.4 DRAW-LINE-TO E3.5 POSITION-PEN E 3 . 6 S E T - PEN - C O L O R !

verwendet

graphische

S->0 s t e l l t die Benutzeroberfläche entNACHRICHTENVERSAND ( v g l . Programm 1 3 . 2 - 1 )

(DEFINE-STRUCTURE ZEICHENSTIFT (X 0 ) (Y 0 ) ( R I C H T U N G 0 ) ( M A L E N T ) ;; B e i s p i e l (DEFINE BILD-1

folgende

globalen

(FARBE

13.4-1)

1))

(MAKE-ZEICHENSTIFT))

(DEFINE *0PERATI0N* (LET* ( ( * P I * 3.141592654) (GRAD->B0GEN (LAMBDA (OBJEKT

WERT)

(*

WERT

(/

*PI*

(BOGEN - > G R A D (LAMBDA (OBJEKT

WERT)

(*

WERT

(/

180

*PI*))))

( N O R M A L I S I E R U N G ( L A M B D A ( O B J E K T WERT) (LET ( ( 2 P I ( * 2 * p i * ) ) ) ( - WERT ( * ( F L O O R ( / WERT 2 P I ) )

2PI)))))

180))))

(GRAPHIK-MODUS (LAMBDA ( O B J E K T ) (COND ( ( = ( G E T - V I D E O - M O D E ) 4 ) ) (T ( S E T - V I D E O - M O D E ! 4 ) (CLEAR-GRAPHICS))))) (TEXT-MODUS (LAMBDA ( O B J E K T ) (SET-VIDEO-MODE! 3))) ( G E T - X - G E R U N D E T (LAMBDA ( O B J E K T ) (FLOOR ( + ( Z E I C H E N S T I F T - X OBJEKT)

0.5))))

13. Wechsel der Abbildungsbasis

357

(GET-Y-GERUNDET (LAMBDA (OBJEKT) (FLOOR (+ (ZEICHENSTIFT-Y OBJEKT) 0.5)))) (SET-POSITION! (LAMBDA (OBJEKT X-WERT Y-WERT) (SET! (ZEICHENSTIFT-X OBJEKT) X-WERT) (SET! (ZEICHENSTIFT-Y OBJEKT) Y-WERT) (COND ( ( Z E I C H E N S T I F T - M A L E N OBJEKT) (DRAW-LINE-TO (GET-X-GERUNDET OBJEKT) ( G E T - Y - G E R U N D E T OBJEKT))) (t (POSITION-PEN ( G E T - X - G E R U N D E T OBJEKT) (GET-Y-GERUNDET O B J E K T ) ) ) ) ) ) (VORWAERTS! (LAMBDA (OBJEKT LAENGE) ( S E T - P O S I T I O N ! OBJEKT (+ (ZEICHENSTIFT-X OBJEKT) (* LAENGE (COS (ZEICHENSTIFT - R I C H T U N G O B J E K T ) ) ) ) (+ (ZEICHENSTIFT-Y OBJEKT) (* LAENGE (SIN (ZEICHENSTI FT - R I C H T U N G O B J E K T ) ) ) ) ) (ZURUECK! (LAMBDA (OBJEKT LAENGE) (VORWAERTS! OBJEKT (- LAENGE)))) (SET-RICHTUNG! (LAMBDA (OBJEKT GRAD) (LET ((BOGEN (GRAD->BOGEN OBJEKT GRAD))) (SET! ( Z E I C H E N S T I F T - R I C H T U N G OBJEKT) ( N O R M A L I S I E R U N G OBJEKT BOGEN))))) (LINKS-DREHEN! (LAMBDA (OBJEKT GRAD) (LET ((BOGEN (GRAD->BOGEN OBJEKT GRAD))) (SET! ( Z E I C H E N S T I F T - R I C H T U N G OBJEKT) ( N O R M A L I S I E R U N G OBJEKT (+ ( Z E I C H E N S T I F T - R I C H T U N G OBJEKT)

BOGEN))))))

(RECHTS-DREHEN! (LAMBDA (OBJEKT GRAD) (LINKS-DREHEN! OBJEKT (- G R A D ) ) ) ) (SET-COLOR! (LAMBDA (OBJEKT WERT) (SET! ( Z E I C H E N S T I F T - F A R B E OBJEKT) WERT) ( S E T - P E N - C O L O R ! WERT))) (RECHTECK (LAMBDA (OBJEKT X-START Y-START X-LAENGE Y-LAENGE) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) ()) (SET-POSITION! OBJEKT X-START Y-START) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) T) (VORWAERTS! OBJEKT X-LAENGE) (LINKS-DREHEN! OBJEKT 90) (VORWAERTS! OBJEKT Y-LAENGE) ( L I N K S - D R E H E N ! OBJEKT 90) (VORWAERTS! OBJEKT X-LAENGE) (LINKS-DREHEN! OBJEKT 90) (VORWAERTS! OBJEKT Y - L A E N G E ) (LINKS-DREHEN! OBJEKT 9 0 ) ) )

358

III. Konstruktionsempfehlung (UNTIL (LAMBDA (OBJEKT X-START Y-START X - L A E N G E Y - L A E N G E ) (RECHTECK OBJEKT X-START Y-START X-LAENGE Y-LAENGE) (RECHTECK OBJEKT (+ X-START (* 0.1 X-LAENGE)) (+ Y-START (* 0.1 Y - L A E N G E ) ) (- X - L A E N G E (* 0.1 X-LAENGE)) (- Y - L A E N G E (* 0.1 Y - L A E N G E ) ) ) ) ) (WHILE (LAMBDA (OBJEKT X-START Y-START X - L A E N G E Y - L A E N G E ) (RECHTECK OBJEKT X-START Y-START X - L A E N G E Y-LAENGE) (RECHTECK OBJEKT (+ X-START (* 0.1 X-LAENGE)) (+ Y-START) (- X - L A E N G E (* 0.1 X-LAENGE)) (- Y - L A E N G E (* 0.1 Y - L A E N G E ) ) ) ) ) (IF-THEN- ELSE (LAMBDA (OBJEKT X-START Y-START X - L A E N G E Y - L A E N G E ) (LET ((X-ZW (+ X-START (/ X-LAENGE 2))) (Y-ZW (+ Y-START (* 0.8 Y - L A E N G E ) ) ) ) (RECHTECK OBJEKT X-START Y-START X-LAENGE Y-LAENGE) (SET! ( Z E I C H E N S T I F T - M A L E N O B J E K T ) ()) (SET-POSITION! O B J E K T X-START Y-ZW) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) T) (VORWAERTS! OBJEKT X-LAENGE) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) ()) (SET-POSITION! OBJEKT X-ZW Y-ZW) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) T) (SET-POSITION! OBJEKT X-START (+ Y-START Y - L A E N G E ) ) (SET! ( Z E I C H E N S T I F T - M A L E N O B J E K T ) ()) (SET-POSITION! OBJEKT X-ZW Y-ZW) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) T) (SET-POSITION! OBJEKT (+ X-START X-LAENGE) (+ Y-START Y - L A E N G E ) ) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) ()) (SET-POSITION! OBJEKT X-ZW Y-ZW) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) T) (SET-POSITION! O B J E K T X-ZW Y-START) (SET! ( Z E I C H E N S T I F T - M A L E N OBJEKT) ()) (SET-POSITION! O B J E K T X-START Y - S T A R T ) ) ) )

13. Wechsel der Abbildungsbasis

359

;; V g l . A b s c h n i t t 13.4 (KOCH-KURVE ( L A M B D A ( O B J E K T X - S T A R T Y - S T A R T ORDNUNG L A E N G E ) ( L E T R E C ( ( S C H R I T T ( L A M B D A ( 0 N L) (COND ( ( Z E R O ? N) ( V O R W A E R T S ! 0 L ) ) (T ( L E T ( ( M ( - 1 + N ) ) (WEG ( / L 3 . 0 ) ) ) ( S C H R I T T 0 M WEG) (RECHTS-DREHEN! 0 60) ( S C H R I T T 0 M WEG) (LINKS-DREHEN! 0 120) ( S C H R I T T 0 M WEG) (RECHTS-DREHEN! 0 60) (SCHRITT 0 M W E G ) ) ) ) ) ) ) (SET! (ZEICHENSTIFT-MALEN OBJEKT) ()) ( S E T - P O S I T I O N ! OBJEKT X-START Y - S T A R T ) (SET-RICHTUNG! OBJEKT 0) ( S E T ! ( Z E I C H E N S T I F T - M A L E N OBJEKT) T) ( S C H R I T T O B J E K T ORDNUNG L A E N G E ) ( L I N K S - D R E H E N ! OBJEKT 120) ( S C H R I T T O B J E K T ORDNUNG L A E N G E ) ( L I N K S - D R E H E N ! OBJEKT 120) ( S C H R I T T O B J E K T ORDNUNG L A E N G E ) ( L I N K S - D R E H E N ! OBJEKT 1 2 0 ) ) ) )

i ntern: i ntern: i ntern: ; ;

intern: intern:

( D I S P A T C H (LAMBDA ( O P E R A T I O N ) (CASE OPERATION (GRAD->BOGEN GRAD->BOGEN) (BOGEN->GRAD BOGEN->GRAD) (NORMALISIERUNG NORMALISIERUNG) (GRAPHIK-MODUS GRAPHIK-MODUS) (TEXT-MODUS TEXT-MODUS) (GET-X-GERUNDET GET-X-GERUNDET) (GET-Y-GERUNDET GET-Y-GERUNDET) (SET-POSITION! SET-POSITION ! ) (VORWAERTS! VORWAERTS!) (ZURUECK! ZURUECK!) (SET-RICHTUNG! SET-RICHTUNG!) (LINKS-DREHEN! LINKS-DREHEN!) (RECHTS-DREHEN! RECHTS-DREHEN!) (SET-COLOR! SET-COLOR!) (RECHTECK RECHTECK) (UNTIL UNTIL) (WHILE WHILE) (IF-THEN-ELSE IF-THEN-ELSE) (KOCH-KURVE KOCH-KURVE) ( E L S E (ERROR " U n b e k a n n t e O p e r a t i o n : OPERATION))

))))

DISPATCH))

"

III. Konstruktionsempfehlung

360 ;;;;

Benutzeroberfläche

(gemäß

Programm

13.2-1)

;;; Makro S - > 0 : ;;; ( S - > 0 OBJEKT MESSAGE ARG-1 . . . ;;; e x p a n d i e r t zu: ;;; ( ( * 0 P E R A T I 0 N * ' M E S S A G E ) OBJEKT (MACRO S - > 0 (LAMBDA ( S E X P R ) ' C O O P E R A T I O N * ' , C L I S T - R E F SEXPR 2 ) ) . ( L I S T - R E F SEXPR 1) ,@(CDDDR S E X P R ) ) ) ) d

Programm 1 3 . 3 - 1 Zeichnen -

Lösung

von

ARG-N) ARG-1

...

ARG-N)

Struktogrammen

"Verzweigungsfunktion"

-

Das DISPATCH-Konstrukt (Programm 13.3-1) ermöglicht auf einfache Weise, bestimmte Funktionen („Methoden") dem Benutzer vorzuenthalten. Eine Kennzeichnung als Kommentar, d.h. ein Semikolon in der entsprechenden Zeile des CASE-Konstruktes, genügt. Die Funktion wird dann nicht exportiert (vgl. Abschnitt 12.2). Im obigen Programm haben wir z.B. die Funktion GRAD->BOGEN so gesperrt. Sie wird nicht exportiert, kann jedoch intern angewendet werden.

13.4 Fraktalfigur Struktogramme sind nützliche, aber sehr nüchterne Figuren. Um uns an der Schönheit einer Graphik erfreuen zu können, zeichnen wir eine einfache Fraktalfigur, die wie eine Schneeflocke aussieht (Bild 13.4-2). Mit einer solchen Figur hat sich schon um 1900 der Mathematiker Helge von Koch befaßt. Sie wird ihm zu Ehren daher auch als tiradische Koch-Insel oder als Koch'sehe Schneeflocke bezeichnet. /Exkurs: Fraktale (Objekte). Der Begriff „fractals" stammt vom polnisch-französischen Mathematiker Benoit B. Mandelbrot (vgl. Mandelbrot, 1977). Man bezeichnet damit Objekte, die sich nicht mehr mit den Begriffen wie Linie oder Fläche beschreiben lassen. Solchen Objekten zwischen Linie (Dimension = 1) und Fläche (Dimension = 2) ordnet man eine nicht ganzahlige, eine „fraktale" (gebrochene) Dimension zwischen 1 und 2 zu. Aufgrund wissenschaftlicher Vorarbeiten des französischen Mathematikers Gaston Julia spricht man in diesem Zusammenhang auch von Julia-Mengen. Kennzeichned für „Fraktale" sind die beiden Eigenschaften: o „Selbstähnlichkeit", d.h. man findet die Form des Gesamten in jedem Detail wieder und o „Verkrumpelung", d.h. es treten kein glatte Begrenzungen auf, so daß eine Länge oder ein Flächeninhalt nicht zu bestimmen ist. Beim genauen Hinschauen, findet man solche „Fraktale" überall in der Natur

13. Wechsel der Abbildungsbasis

361

(vgl. Mandelbrot, 1983). Der Blumenkohl-Kopf ist ein populäres Beispiel. Ein praxisnahes Software-Konstruktionsgebiet ist die Computer-Animation. Man modelliert z.B. mit „stochastischen Fraktalen" detailreiche Gebirgsketten. Die Koch'sehe Schneefocke gehört (wie auch das populäre „Apfelmännchen") zur "deterministischen (kausal abhängigen) Fraktale", da sie mit einer einfachen Rekursionsformel beschreibbar ist. (Fraktale Kunstwerke vgl. z.B. Peitgen/Richter, 1986). ] Unsere Schneeflocke basiert auf einem gleichseitigen Dreieck. Die Dreieckseiten bestehen aus Fraktallinien der angegebenen Ordnung. Eine Fraktallinie der ersten Ordnung ist eine „gebrochene", also frakturierte Linie (vgl. Bild 13.4-1). Eine Fraktallinie 0-ter Ordnung ist eine Gerade.

Eine Fraktallinie n-ter Ordnung ist eine Linie, bei der jede Gerade durch eine Fraktallinie (n-l)ter Ordnung dargestellt ist. Jede Fraktallinie n-ter Ordnung vergrößert den Flächeninhalt der Figur aus Fraktallinien (n-l)ter Ordnung. Statt einer Geraden der Länge L zeichnen wir einen Streckenzug mit 4 Teilabschnitten, die eine Länge von jeweils 1/3 L haben. Nachdem der Zeichenstift den ersten Teilabschnit gezeichnet hat, drehen wir seine Richtung rechtsherum, um 60 Grad und zeichnen den zweiten Teilabschnitt. Anschließend drehen wir die Richtung linksherum um 120 Grad und zeichnen den dritten Teilabschnitt. Dann drehen wir die Richtung rechtsherum um 60 Grad und zeichnen den vierten Teilabschnitt. Wiederholt man dieses Verfahren auf alle entstehenden Teilabschnitte, so entsteht beim Grenzübergang zu unendlich vielen Wiederholungen die sogenannte Kochsche Kurve. Aufgrund der begrenzten Punktezahl im Graphikmodus können nicht beliebig kurze Teilstrecken dargestellt werden. Kürzere Strecken als ein Graphik„Punkt" sind nicht abbildbar. Die auflösbare Ordnung n unserer Fraktalfigur ist daher abhängig von der verfügbaren Hardware. Bild 13.4-2 zeigt eine Fraktalfigur der vierten Ordnung. Das obige KOCH-KURVE-Konstrukt (vgl. Programm 13.3-1) basiert auf der lokalen rekursiven Funktion SCHRITT. Ist die Ordnung N = 0, dann ist eine Gerade zu zeichnen (trivialer Fall der Rekursion). Ist N > 0 konstruieren wir eine Fraktallinie, wobei die Teilabschnitte jeweils aus Fraktallinen der (n-1 )ten

362

III. Konstruktionsempfehlung

Ordnung bestehen. Damit ist die „Rekursions-Route" (vgl. Abschnitt 3) definiert.

13.5 Zusammenfassung: Graphik, SEND- und DISPATCH-Konstruktion Effizienz, Transparenz, Wartbarkeit, Ergänzbarkeit oder Portabilität sind (teilweise konkurrierende) Anforderungen für die Auswahl der geeigneten Abbildungsbasis. Verschieben sich die Bewertungsgewichte, dann kommt ein Wechsel in Betracht. Objektgeprägte Konstruktionen (z.B. realisiert mit SCOOPS) haben im Vergleich zu imperativgeprägten einen größeren Speicher-und Rechenzeitbedarf. Eine Umstellung des Kommunikations-Konzeptes („message passing") z.B. durch ein explizites Ansteuerern der einzelnen Funktion, vermindert den Bedarf. Statt die Nachricht (mit dem SCOOPS-Konstrukt SEND) an das Objekt zu schikken, um damit die Applikation der gewünschten Methode zu erreichen, wird die Funktion über ein Verzweigungs-Konstrukt (engl.: dispatch procedure) ausgewählt und angewendet. Dabei kannt die bisherige Benutzoberfläche (Syntax des SEND) durch Makros simuliert werden. Mit den eingebauten grapischen „Primitives": GET-VIDEO-MODE, SETVIDEO-MODE!, CLEAR-GRAPHICS, DRAW-LINE-TO, POSITION-PEN und SET-PEN-COLOR! in Verbindung mit rekursiven Definitionen sind wunderschöne Figuren aus der Fraktalgeometrie abbildbar.

363

13. Wechsel der Abbildungsbasis Charakteristisches

Beispiel fiir Abschnitt 13:

;;;; D o k u m e n t 1 3 . 5 - 1 ;;;; T i t e l : Das H a u s d e s N i k o l a u s ;;;; E r s t e l l t a m : 1 9 . 0 4 . 9 0 ¡ l e t z t e Ä n d e r u n g : ;;; ;;; ;;; ;;; ;;; ;;; ;;;

AI Al.l AI.2 AI.3

23.12.90

NIKOLAUS zeichnet Linie für Linie, ohne den Zeichenstift a b z u s e t z e n , das "Haus des N i k o l a u s . D i e r e c h t e u n t e r e H a u s e c k e ist d e r S t a r t p u n k t . D i e K o o r d i n a t e n d e s S t a r t p u n k t e s s i n d als I n t e g e r z a h l e n im A n t w o r t - F e n s t e r a n z u g e b e n . D i e im A n t w o r t - F e n s t e r e r f r a g t e S e i t e n l a e n g e b e s t i m m t die G r o e s s e des H a u s e s .

;;; A2 ;;; ;;; A 2 . 1 ;;; ;;; A 2 . 2 ;;;

V o r g e g e b e n e E r s a t z w e r t e k o r r i g i e r e n nicht p l a u s i b l e Eingaben. Ein P o p u p - M e l d u n g s f e n s t e r v e r w e i s t a u f e i n e s o l c h e K o r rektur. Nach einer angemessenen Lesezeit verschwindet dieses Meldungsfenster.

;;: A 3 ;;;

Die Seiten des Druecken einer

Haus entstehen s c h r i t t w e i s e beliebigen Taste.

;;: ; ;; ;;; ;;;

El NIKOLAUS verwendet El.l SET - V I D E O - M O D E ! El.2 D R A W - L I N E - T O El.3 POSITION-PEN

;;; ;;; ; ;; ;;; ;:: ::; ; ;;

E2

nach

dem

folgende

graphische

Primitives:

NIKOLAUS verwendet folgende Konstrukte: E2.1 M A K E - W I N D O W E2.2 W I N D O W - P O P U P E2.3 WINDOW-POPUP-DELETE E2.4 W I N D O W - S E T - P O S I T I O N ! E2.5 W I N D O W - S E T - S I Z E !

eingebaute

WIND0W-

;;; T1 :;; (DEFINE (LET*

Beispiel (NIKOLAUS) ==>

... B i l d

NIKOLAUS (LAMBDA ( ) ((GRAPHIK-MODUS (LAMBDA (X-MIN -160) (Y-MIN -100) (X-MAX 159) (Y-MAX 99) ( T E X T - M O D U S ( L A M B D A ()

... ()

¡Startwerte

im

(SET-VIDEO-MODE!

(SET-VIDEO-MODE!

Dialog 4)))

3)))

(WARTEN (LAMBDA (LET*

(SEKUNDEN) ( ( A N F A N G ( R U N T I M E ) ) ¡vgl. A b s c h n i t t 8 . 5 ( E N D E (+ (* S E K U N D E N 1 0 0 ) A N F A N G ) ) ) (DO ( ( A K T U E L L E - Z E I T ( R U N T I M E ) ( R U N T I M E ) ) ) ( C O R (>= A K T U E L L E - Z E I T E N D E ) (< A K T U E L L E - Z E I T A N F A N G ) ) T) "Arbeite!"))))

;; U m L i n i e n n a c h e i n a n d e r z u z e i c h n e n ( W E I T E R ( L A M B D A () ( R E A D - C H A R ) ) )

(vgl.

A3)

364

III. Konstruktionsempfehlung (MELDUNG (LAMBDA ( ) (LET ((PROMPTER

(MAKE-WINDOW "Ni kolaus-Haus-Mel dung" # T ) ) ) ( W I N D O W - S E T - S I Z E ! PROMPTER 2 5 0 ) ( W I N D O W - S E T - P O S I T I O N ! PROMPTER 10 8 ) (WINDOW-POPUP PROMPTER) (DISPLAY " I h r e A n g a b e l i e g t n i c h t im a b b i l d b a r e n B e r e i c h ! ' PROMPTER) (NEWLINE PROMPTER) (DISPLAY " Es w i r d e i n E r s a t z w e r t a n g e n o m m e n . " PROMPTER) (WARTEN 4 ) (WINDOW-POPUP-DELETE PROMPTER))))

(ANTWORT (LAMBDA () (LET ((FENSTER

(MAKE-WINDOW "Nikolaus-Haus-Groesse" #T)) (X-ERSATZ 50) (Y-ERSATZ -60) (SEITEN-LAENGE-ERSATZ 80) (X-START-HAUS 0) (Y-START-HAUS 0) (SEITEN-LAENGE-HAUS 0)) ( W I N D O W - S E T - S I Z E ! F E N S T E R 10 4 5 ) ( W I N D O W - S E T - P O S I T I O N ! FENSTER 5 6) (WINDOW-POPUP F E N S T E R ) (DISPLAY " B i t t e rechte untere Hausecke angeben!" FENSTER) (NEWLINE FENSTER) (DISPLAY " I h r X - S t a r t w e r t : " FENSTER) (SET! X-START-HAUS (READ FENSTER)) ( I F (OR (NOT ( N U M B E R ? X - S T A R T - H A U S ) ) (= X-START-HAUS X-MAX)) (BEGIN (MELDUNG) • (SET! X-START-HAUS X-ERSATZ) ( D I S P L A Y " Der X - E r s a t z w e r t i s t : (DISPLAY X-ERSATZ FENSTER) (NEWLINE F E N S T E R ) ) )

(DISPLAY " I h r Y - S t a r t w e r t : " FENSTER) ( S E T ! Y-START-HAUS (READ FENSTER)) ( I F (OR (NOT ( N U M B E R ? Y - S T A R T - H A U S ) ) (= Y-START-HAUS Y-MAX)) (BEGIN (MELDUNG) (SET! Y-START-HAUS Y-ERSATZ) ( D I S P L A Y " Der Y - E r s a t z w e r t i s t : (DISPLAY Y-ERSATZ FENSTER) (NEWLINE F E N S T E R ) ) ) (NEWLINE FENSTER)

"

FENSTER)

"

FENSTER)

13. Wechsel der Abbildungsbasis

365

(DISPLAY " Bitte die Seitenlaenge angeben!" FENSTER) (NEWLINE FENSTER) (DISPLAY " I h r e S e i t e n l a e n g e : " FENSTER) ( S E T ! S E I T E N - L A E N G E - H A U S (READ FENSTER)) ( I F (OR (NOT ( N U M B E R ? S E I T E N - L A E N G E - H A U S ) ) (< S E I T E N - L A E N G E - H A U S 2) (> (+ Y-START-HAUS SEITEN-LAENGE-HAUS (/ SEITEN-LAENGE-HAUS 2 ) ) Y-MAX) (< ( - X-START-HAUS SEITEN-LAENGE-HAUS) X-MIN) ) (BEGIN (MELDUNG) (SET! SEITEN-LAENGE-HAUS (MIN (/ ( * 0 . 9 9 9 ( - Y-MAX Y - S T A R T - H A U S ) ) 1.5) ( * 0.999 ( - X-START-HAUS X - M I N ) ) ) ) ( I F (< S E I T E N - L A E N G E - H A U S 2) (BEGIN (DISPLAY " Keine s i n n v o l l e Seitenlaenge berechenbar!" FENSTER) (NEWLINE FENSTER) (DISPLAY " Es w i r d d a s S t a n d a r d h a u s gezeichnet!" FENSTER) (SET! X-START-HAUS X-ERSATZ) (SET! Y-START-HAUS Y-ERSATZ) (SET! SEITEN-LAENGE-HAUS SEITEN-LAENGE-ERSATZ)) (BEGIN (DISPLAY " D i e angenommen S e i t e n l a e n g e n i s t : " FENSTER) (DISPLAY SEITEN-LAENGE-HAUS FENSTER) (NEWLINE F E N S T E R ) ) ) ) ) (WARTEN

7)

¡Lesezeit

(WINDOW-POPUP-DELETE (LIST

;;;

ca.

7

Sekunden.

FENSTER)

X-START-HAUS Y-START-HAUS SEITEN-LAENGE-HAUS)) ) )

S t a r t w e r t e fuer das B i l d (XO-YO&LAENGE (ANTWORT)) ;; DRAW-LINE v e r l a n g t I n t e g e r - Z a h l e n . (XO ( T R U N C A T E ( L I S T - R E F X O - Y O & L A E N G E 0 ) ) ) (YO ( T R U N C A T E ( L I S T - R E F X O - Y O & L A E N G E 1 ) ) ) (LAENGE (TRUNCATE ( L I S T - R E F XO-YO&LAENGE 2 ) ) ) ) (GRAPHIK-MODUS) ( P O S I T I O N - P E N XO Y O )

366

III. Konstruktionsempfehlung Z e i c h n e n d e s H a u s e s vom N i k o l a u s ( D R A W - L I N E - T O XO ( + YO L A E N G E ) ) ¡Rechte Seitenwand (WEITER) (DRAW-LINE-TO Decke ( - XO L A E N G E ) ( + YO L A E N G E ) ) (WEITER) ( D R A W - L I N E - T O XO Y O ) D i a g o n a l e zum S t a r t p u n k t (WEITER) ( D R A W - L I N E - T O ( - XO L A E N G E ) Y O ) Grundlinie (WEITER) ( D R A W - L I N E - T O XO ( + YO L A E N G E ) ) D i a g o n a l e von l i n k s (WEITER) unten nach r e c h t s oben (DRAW-LINE-TO Rechte D a c h s e i t e ( - XO ( T R U N C A T E ( / LAENGE 2 ) ) ) ( + YO LAENGE ( + ( T R U N C A T E ( / LAENGE 2 ) ) ) ) ) (WEITER) (DRAW-LINE-TO ( - XO L A E N G E ) ( + YO L A E N G E ) ) ¡Linke Dachseite (WEITER) ( D R A W - L I N E - T O ( - XO L A E N G E ) Y O ) ¡Linke Seitenwand (WEITER) (TEXT-MODUS)))) Programm

13.5-1.

"Das

Haus

des

Nikolaus"

14. Ausblick Jeder Text von derartiger Länge und „Tiefe" verlangt ein abschließendes Wort für seinen getreuen Leser. Es wäre nicht fair, nach über 370 Seiten, die nächste aufzuschlagen und dann den Anhang zu finden. Daher zum Schluß ein kleiner Ausblick. LISP ist eine mächtige und „offene" Programmiersprache mit vielen Konzepten und Möglichkeiten. Sie haben sich einen großen Ausschnitt davon erarbeitet. Buchstabieren mit LISP-Konstrukten fällt Ihnen nicht schwer (APPLY, BEGIN, CLOSURE, DEFINE, EVAL, FALSE, GENSYM, ... , ZERO?). Sie durchschauen das Wurzelwerk von LISP: Die funktionale Wurzel mit ihrem nicht destruktiven Binden (Stichwort: Rekursion) und die imperative Wurzel mit ihrem destruktiven Zuweisen (Stichwort: Nebeneffekte). Beide zusammen führen zur objektgeprägten Konstruktion mit Klassen, Instanzen, Nachrichtenaustausch und vielfältigen Vererbungsmechanismen. Sie wissen wie Konstruktionsaufgaben zu spezifizieren und transparent zu dokumentieren sind. Was können wir in Zukunft von LISP erwarten? Oder LISPisch formuliert: Wie sieht CALL/CC für LISP aus? Eine sich konkret abzeichnende Erweiterung ist die Parallelverarbeitung (vgl. z.B. Harrison, 1989 oder Ito/Halstead, 1989).

367

14. Ausblick

Sie haben dafür einen von vielen erfolgversprechenden Ansatzpunkt kennengelernt: Das LET-Konstrukt (vgl. Halstaed, 1989). Es ist konsequent erweiterbar, wie z.B. der Dialekt QLISP (ßueue-based multiprocessing, vgl. Goldman/ Gabriel, 1988) zeigt: (QLET < p a r a l 1 e I > ( ( < v a r i a b l e _ 1 > ) ( < v a r i a b l e _ 2 > ) ( )

))

Wenn einen Wert ungleich NIL hat, dann erzeugt QLET für jedes einen Prozeß und fügt diese in die Schlange von schon bestehenden Prozessen ein, die auf ihre Ausführung warten. Diese Warteschlange wird entsprechend der bereitgestellten Hardware von mehreren Prozessoren gleichzeitig abgearbeitet. Der Ausführungsprozeß von Q L E T wartet dann so lange, bis alle von ihm erzeugten Prozesse fertig sind. Ist das der Fall, wird der QLET-Prozess wieder aktiviert, bindet die Variablen und evaluiert dann damit den . Hat den Wert NIL, verhält sich QLET wie das LET-Konstrukt. Während der Laufzeit kann man daher, z.B. abhängig vom Aufwand für das Erzeugen und Überwachen von neuen Prozessen, über die Parallelausführung entscheiden. Ein solcher „dynamischer Parallelismus" paßt nahtlos in das dynamische LISP, bei dem wir gewohnt sind, möglichst spät „Etwas" (z.B. Bindung oder Vererbung) festzulegen. Das Buch ist sicherlich (wie viele andere im Fachgebiet Informatik auch) eine Beschreibung, wie der Autor das LISP-Wissen gewonnen hat, das andere schon längst veröffentlicht haben. Trotz alledem wurde versucht, diese „Entdeckungsreise" aus eigenständiger Sicht zu zeigen. Ein Fachbuch wie dieses ist eigentlich niemals fertig: während man daran arbeitet, lernt man immer gerade genug, um seine Unzulänglichkeiten klarer zu sehen. Schließlich ist es jedoch, inclusive erkannter Unzulänglichkeiten, der Öffentlichkeit zu übergeben. Für diese Unzulänglichkeiten bitte ich Sie abschließend um Ihr Verständnis. End S e s s i o n

Anhang

A: Literaturverzeichnis /Hinweis: In der Literatur wird LISP nicht immer in Großbuchstaben geschrieben, obwohl es ein Akronym für LISI Processing ist. Die jeweilige Schreibweise ist hier beibehalten./ Abelson/Sussman/Sussman, 1985. Harold Abelson / Gerald Jay Sussman / Julie Sussman; Structure and Interpretation of Computer Programs, Cambridge, Massachusetts u.a. (The MIT Press/McGraw-Hill) 1985. Allen, 1978. John Allen; Anatomy of LISP, New York u.a. (McGraw-Hill) 1978. Allen, 1987. John R. Allen; The Death of Creativity, in: AI Expert, San Francisco 2(1987)2, p. 48 -61 Anderson u.a., 1984. John R. Anderson / Robert Farrell / Ron Sauers; Learning to Program in LISP, in: Cognitive Science, Norwood 8 (1984), pp. 87 - 129. Anderson u. a., 1987. John R. Anderson / Albert T. Corbett / Brian J. Reiser; Essential LISP, Reading, Massachusetts u.a. (Addison-Wesley) 1987. ANSI/IEEE Std. 830, 1984. American National Standard / Institute of Electrical and Electronics Engineers; Guide to Software Requirements Specifications, Standard 830, July 20, 1984. Backus, 1978. John Backus; Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs, Communications of the ACM, Vol. 21, No. 8, August 1978, pp. 613 - 641. Barr/Feigenbaum, 1982. Avron Barr / Edward A. Feigenbaum (Eds.); The Handbook of Artificial Intelligence, Volume II, Chapter VI: Programming Languages for AI, (HeurisTech Press/William Kaufmann) 1982. Beckstein, 1985. Clemens Beckstein; Integration objekt-orientierter Sprachmittel zur Wissensrepräsentation in LISP, Institut für Mathematische Maschinen

370

Anhang

und Datenverarbeitung, Friedrich-Alexander-Universität Erlagen-Niirnberg, Diplomarbeit im Fach Informatik 1985. Beichter u.a., 1981. F. Beichter / O. Buchegger / O. Herzog / N.E. Fuchs; SLAN-4, A Software Specification and Design Language, in: Floyd/Kopetz, 1981, S. 91 - 108. Belli/Bonin, 1987. Fevzi Belli / Hinrich Bonin; Qualitätsvorgaben im Hinblick auf Softwarefehler, in: M. Bartsch / D. Hildebrand (Hrsg.); Der EDV-Sachverständige, Workshop der GRVI, Schmitten 1986, Stuttgart (B.G.Teubner) 1987, S. 1 7 2 - 198. Belli/Bonin, 1989. Fevzi Belli / Hinrich Bonin; Probleme bei der Hinterlegung wissensbasierter Systeme bezüglich ihrer Dokumentation - Erstüberlegungen und Thesen - , in: D J . Hildebrand / T. Hoene (Hrsg.); Software - Hinterlegung in der Insolvenz, Workshop der DGIR, Stuttgart 1988, Stuttgart (B.G. Teubner) 1989, S. 2 9 - 5 1 . Berkeley/Bobrow, 1966. Edmund C. Berkeley / Daniel G. Bobrow (Editors); The Programming Language LISP: Its Operation and Applications, (The M.I.T. Press) 1966, First printing March 1964. Bitsch, 1989. Gerhard Bitsch; Wie man in LISP programmiert - Eine Einführung anhand von PC-Scheme 3.0, CHIP Wissen, Würzburg (Vogel Buchverlag) 1989. Bobrow, 1972. Daniel G. Bobrow; Requirements for Advanced Programming Systems for List Processing, Communications of ACM, Vol. 15, Number 7, July 1972, pp. 6 1 8 - 6 2 7 . Bobrow u.a., 1986. Daniel G. Bobrow / Kenneth Kahn / Gregor Kiczales / Larry Masinter / Mark Stefik / Frank Zdybel; Common Loops: Merging Lisp and Object-Oriented Programming, in: ACM Proceedings OOPSLA'86, SIGPLAN Notices, 21 (11) 1986, p. 17 - 29. Bobrow/Moon u.a., 1988. Daniel G. Bobrow / David Moon u.a.; Common Lisp Object System Specification, ANSI X3J13 Document 88-002R, American National Standards Institute, Washington, DC, Juni 1988 (veröffentlicht in: SIGPLAN Notices, Band 23, Special Issus, September 1988). Boehm, 1981. Barry W. Boehm; Software Engineering Economics, New Jersey 1981.

A: Literaturverzeichnis

371

Bonin, 1988. Hinrich Bonin; Die Planung komplexer Vorhaben der Verwaltungsautomation, Heidelberg (R.v.Decker & C.F. Müller) 1988 (Band 3 der Schriftenreihe „Verwaltungsinformatik"). Bonin, 1988a. Hinrich Bonin; Objekt-orientierte Programmierung mit LISP, Arbeitsbericht 1988/7 des Fachbereichs 2 (Systemanalyse) der Hochschule Bremerhaven (ISSN 0176-8158) Bonin, 1989. Hinrich Bonin; Objektorientierte Programmierung mit LISP, in: Handbuch der Modernen Datenverarbeitung (HMD), 26. Jahrgang, Heft 145, Januar 1989, S. 45 - 56. Brewka u.a., 1987. Gerhard Brewka / Franco di Primo / Eckehard Groß; BABYLON - Referenzhandbuch VI. 1/1, Institut für Angewandte Informationstechnik, Gesellschaft für Mathematik und Datenverarbeitung mbH, Bonn, 1987. Bromley/Lamson, 1987. Hank Bromley / Richard Lamson; LISP LORE: A Guide to Programming the LISP Machine, Second Edition, Boston, Dordrecht, Lancaster (Kluwer Academic Publishers) 1987. Brooks/Gabriel/Steele, 1982. Rodney A. Brooks / Richard P. Gabriel / Guy L. Steele Jr.; S-l Common Lisp Implementation, ACM LISP Conference New York, 1982, pp. 1 0 8 - 1 1 3 . Brooks/Gabriel, 1984. Rodney A. Brooks / Richard P. Gabriel; A Critique of Common Lisp, Conference record of ACM symposium on LISP and functional Programming: Proceedings Salt Lake City, Utha 1984, pp. 1 - 8. Brooks, 1987. Rodney A. Brooks; LISP - Programmieren in Common Lisp, München Wien (R. Oldenbourg Verlag) 1987 - Original 1985 "Programming in Common Lisp" (John Wiley). Bürge, 1975. William H. Bürge; Recursive Programming Techniques, Reading, Massachusetts u.a. (Addison-Wesley) 1975. Burstall u.a., 1980. R. M. Burstall / D. B. MacQueen / D. T. Sannells; HOPE: An Experimental Applicative Language, LISP Conference, Stanford/Redwood Est 1980, pp. 1 3 6 - 143. Calculemus, 1986. Firma Calculemus; Comments on TLC-LISP/86 Ver. 1.51, July 1986. Carte, 1973. James Allen Carte; Filtering Typographic Noise from Technical

372

Anhang

Literature, in: Technical Communication, Third Quarter, Vol. 20, pp. 12 - 13, 1973 (auch erschienen in: Harkins/Plung (Eds.); A Guide for Writing Better Technical Papers, New York (IEEE Press selected reprint series) 1981, pp. 1 5 4 - 155. Charniak u.a., 1987. Eugene Charniak / Christopher K. Riesbeck / Drew V. McDermott / James R. Meehan; Artificial Intelligence Programming, Second Edition, Hillsdale, New Jersey, Hove and London (Lawrence Erlbaum Associates) 1987. Clark/Green, 1977. Douglas W. Clark / C. Cordell Green; An Empirical Study of List Structure in Lisp, Communications of ACM, Vol. 20, No. 2, February 1977, pp. 7 8 - 8 7 . Claus, 1987. Volker Claus; Total unstrukturierte Programme, in: Der GMDSpiegel 4/87 (Gesellschaft für Mathematik und datenverarbeitung mbH, St. Augustin), S. 46 -50. Clinger, 1988. William Clinger; Semantics of Scheme, in: Byte, Febraury 1988, pp. 221 -227. CLOS 1988. Common LISP Object System, vgl. Bobrow / Moon u.a., 1988. Church, 1941. Alonzo Church; The Calculi of LAMBDA-Conversion; Princeton University Press, New Jersey, 1941. Curry u.a., 1958. H.B. Curry / R. Feys; Combinatory Logic, Vol. I, Studies in Logic and the Foundations of Mathematics, Amsterdam (North-Holland) 1958. Danicic, 1983. I. Danicic; Lisp Programming, Oxford London Edinburgh u.a. (Blackwell Scientific Publications), reprinted with corrections 1984. Darlington u.a., 1982. J. Darlington / P. Henderson, / D.A. Turner (Eds.); Functional programming and its applications - An advanced course, Cambridge, London u.a. (Cambridge University Press), auch als UMI Out-of-Print Books on Demand 1989. Denert, 1979. Ernst Denert; Software-Modularisierung, in: Informatik-Spektrum 2, 1979, S. 2 0 4 - 2 1 8 . DeRemer/Kron, 1976. F. L. De Remer / H. Kron; Programming-in-the-Large versus Programming-in-the-Small, in: Progammiersprachen, 4. Fachtagung der Gl, Erlangen, Informatik-Fachberichte 1, (Springer Verlag) 1976, pp. 80 - 89.

A: Literaturverzeichnis

373

Deutsch, 1978. L.P. Deutsch; Experience with Microprogrammed Interlisp System, in: Proc. 1 Ith Ann. Microprogramming Workshop, Nov. 1978, pp. 128 129. Dijkstra, 1968. Edsger W. Dijkstra; Go To Considered Harmful, in: Communications of the ACM, Volume 11 / Number 3 / March 1968, pp. 147 -148. DIN 66241, 1979. Deutsches Institut für Normung e.V.; Entscheidungstabelle Beschreibungsmittel, Berlin, Köln (Beuth Verlag). DIN 66261, 1984. Deutsches Institut für Normung e.V.; Sinnbilder nach NassiShneiderman und ihre Anwendung in Programmablaufplänen, Berlin, Köln (Beuth Verlag) DiPrimo/Christaller, 1983. Franco di Primo / Thomas Christaller; A Poor Man's Flavor System, Institut Dalle Molle ISSCO, Universite de Geneve, Working Paper No 47, May 1983. Domain, 1986. Domain/Common LISP User's Guide - A Concise Reference Manual, Order No. 008791 (Product developed by Lucid, Inc. of Menlo Park, California) 1986. Eisenbach, 1987. Susan Eisenbach (editor); Functional Programming: Languages, Tools and Architectures, Chichester (Ellis Horwood Limited) 1987. Eisenberg, 1990. Michael Eisenberg; Programming in Scheme, Cambridge, Massachusetts, London, England (MIT Press) paperback 1990 (1988 by The Scientific Press). Eggers/Zimmermann, 1984. Eggers / Zimmermann; LISP, Manuskript der Vorlesung WS 1981/82 und WS 1983/84, Technische Universität Berlin, Fachbereich Informatik. Esser/Feldmar, 1989. Rüdiger Esser / Elisabeth Feldmar; LISP - Fallbeispiele mit Anwendungen in der Künstlichen Intelligenz, Paul Schmitz (Hrsg.): „Reihe „Künstliche Intelligenz", Braunschweig Wiesbaden (Vieweg&Sohn) 1989. Felleisen/Friedman, 1986. Matthias Felleisen / Daniel P. Friedman; A Closer Look at Export and Import Statements, Computer Languages, Vol. 11, No. 1, 1986, pp. 29 - 37. FISCH, 1988. Flavor Interface to SCHeme, Konzeption und Konstruktion einer LISP-Umgebung für die objektgeprägte Modellsicht der Softwareentwicklung,

374

Anhang

Projektbericht des Projektes FLAVOR Fachbereich 2, Hochschule Bremerhaven 1988. Floyd/Kopetz, 1981. Christiane Floyd / Hermann Kopetz (Hrsg.); Software Engineering - Entwurf und Spezifikation, Stuttgart (B.G. Teubner) 1981. Foderaro/Sklower/Layer, 1983. John K. Foderaro / Keith L. Sklower / Kevin Layer; The FRANZ LISP Manual, June 1983 (University of California). Forgy, 1981. Charles L. Forgy; OPS5 User's Manual, Department of Computer Science, Carnegie-Mellon University Pittsburg, Pennsylvania 15213, 1981. Friedman, 1974. Daniel P. Friedmann; The 0-574-19165-8, Science Research Associates, 1974.

Little

LISPer,

ISBN:

Gabriel, 1985. Richard P. Gabriel; Performance and Evaluation of Lisp Systems, Cambridge, Massachusetts (The MIT Press), 1985 (Third printing 1986). Gabriel, 1988. Richard P. Gabriel; The Why of Y, in: LISP Pointers, Volume 2 Number 2 October-November-December 1988, p. 15 - 25. Gabriel/Masinter, 1982. Richard P. Gabriel / Larry M. Masinter; Performance of Lisp Systems, ACM LISP Conference New York, 1982, pp. 123 - 142. Gabriel/Pitman, 1988. Richard P. Gabriel / Kent M. Pitman; Endpaper: Technical Issues of Separation in Function Cells and Vaule Cells, in: LISP and Symbolic Computation, Vol. 1, Number 1, June 1988, pp. 81 - 101. Gnosis, 1984. Learning LISP, Engelwood Cliffs, New Jersey (Prentice-Hall) 1984 (Handbuch der Firma Gnosis zu LISP auf Apple II). Griss/Hearn, 1981. Martin L. Griss / Anthony C. Hearn; A Portable LISP Compiler, Software Practice and Experience, Vol. 11, 1981, pp. 541 - 605. Goldberg/Robson, 1983. Adele Goldberg / Dave Robson; Smalltalk-80: the language, Reading, Massachusetts u.a. (Addison-Wesley) 1983. Goldman/Gabriel, 1988. Ron Goldman / Richard P. Gabriel; Qlisp: Experience and New Directions, in: ACM/SIGPLAN PPEALS (Parallel Programming: Experience with Applications, Languages and systems) Volume 23, Number 9, September 1988, pp. I l l - 123.

A: Literaturverzeichnis

375

Gordon u.a., 1984. M. Gordon / R. Milner / C. Wadsworth / G. Cosineau / G. Huet / L. Paulson: The ML-Handbook, Version 5.1, Paris (INRIA), 1984. Gutierrez, 1982. Claudio Gutierrez; PROLOG Compared with LISP, ACM LISP Conference New York, 1982, pp. 143 - 149. Guttag, 1980. John Guttag; Notes on Type Abstraction (Version 2), IEEE Transactions on Software Engineering, Vol. SE-6, No. 1, January 1980, pp. 1 3 - 2 3 . Haesmer, 1984. Tony Hasemer; A Beginner's Guide to Lisp, Workingham, Bershire u.a.(Addison-Wesley), 1984. Hall, 1989. Patrick J. Hall; How to Solve it in LISP, Wilmslow, England (Sigma Press) 1989. Halstead, 1984. Robert H. Halstead, Jr.; Implementation of Multilisp: Lisp on a Multiprocessor, Conference record of ACM Symposium on LISP and Functional Programming: Proceedings Salt Lake City, Utha 1984, pp. 9 - 17. Halstead, 1989. Robert H. Halstead, Jr.; New Ideas in Parallel Lisp: Language Design, Implementation, and Programming Tools, in: Ito/Halstead, 1989, pp. 2-57. Hamann, 1982. Christian-Michael Hamann; Einführung in das Programmieren in LISP, Berlin New York (Walter de Gruyter) 1982. Handke, 1987. Jürgen Handke; Sprachverarbeitung mit LISP und PROLOG auf dem PC, Band 27: Programmieren von Mikrocomputern, Braunschweig, Wiesbaden (Vieweg&Sohn) 1987. Harrison, 1989. Williams Ludwell Harrison III; The Interprocedural Analysis and Automatic Parallelization of Scheme Programs, in: LISP And Symbolic Computation, Volume 2, Number 3/4, October 1989, pp. 179 - 396. Haugg/Omlor, 1987. Friedrich Haugg / Stefan Omlor; LISP auf PC's - Methoden und Techniken der Symbolischen Datenverarbeitung, München Wien (Carl Hanser Verlag) 1987. Hearn, 1969. Anthony C. Hearn; Standard LISP, in: ACM SIGPLAN Notices, Vol. 4, No. 9, 1969 (auch Technical report AIM-90, Artificial Intelligence Project, Stanford University). Henderson, 1980. Peter Henderson; FUNCTIONAL Programming - Applica-

376

Anhang

tion and Implementation, Englewood Cliffs, New Jersey (Prentice/Hall International) 1980. Hennessey, 1989. Wade L. Hennessey; COMMON LISP, New York, St.Louis San Francisco u.a. (McGraw-Hill) 1989. Henson, 1987. Martin C. Henson; Elements of Functional Languages; Computer Science Texts, Oxford, London u.a. (Blackwell Scientific) 1987. Hesse u.a., 1984. Wolfgang Hesse / Hans Keutgen / Alfred L. Luft / H. Dieter Rombach; Ein Begriffsystem für die Softwaretechnik, in: Informatik-Spektrum, Heft 7 1984, S. 200 - 223 (Arbeitskreis: "Begriffsbestimmung" der Fachgruppe „Software Engineering" im FB 2 der Gl) Hoare, 1975. C. A. R. Hoare; Recursive Data Structures, International Journal of Computer and Information Sciences, Vol. 4, No. 2, 1975, pp. 105 - 132. Hofstadter, 1983. Douglas R. Hofstadter; Metamagikum, in: Spektrum der Wissenschaft, April 1983 (S.14-19), Mai 1983 (S.13-19), Juni 1983 (S.10-13), Juli 1983 (S.6-10); Original in Scientific American 1983. Holtz, 1985. Frederick Holtz; LISP, the language of artificial intelligence (TAB Books, Inc.) 1985. Horowitz, 1984. Ellis Horowitz; Fundamentals of Programming Language, Second Edition, Berlin Heidelberg u.a. (Springer) 1984. Hughes, 1987. Sheila Hughes; TerminalBuch LISP, München Wien (R. Oldenbourg Verlag) 1987. Hußmann/Schefe/Fittschen, 1988. Michael Hußmann / Peter Scheie / Andreas Fittschen; Das LISP-Buch, Hamburg (McGraw-Hill) 1988. Ito/Halstead, 1989. Takayasu Ito / Robert H. Halstead, Jr. (Eds.); Parallel Lisp: Languages and Systems, Proceedings US/Japan Workshop on Parallel Lisp Sendai, Japan, June 5-8, 1989, Lecture Notes in Computer Science 441, Berlin Heidelberg u.a. (Springer-Verlag) 1989. Jackson, 1979. M.A. Jackson; Grundsätze des Programmentwurfs, 7. Auflage 1986, Darmstadt (Orginaltitel: Principles of Program Design; Übersetzung von R. Schaefer und G. Weber) Jones, 1982. Simon L. Peyton Jones; An Investigation of the Relative Efficien-

A: Literaturverzeichnis

377

cies of Combinatore and Lambda Expressions, LISP Conference New York 1982, pp. 1 5 0 - 158. Kaeblbling, 1988. Michael J. Kaelbling; Programming Languages Should NOT Have Comment Statements, in: SIGPLAN Notices, Vol. 23, No. 10, October 1988, pp. 59 - 60. Keene, 1989. Sonya E. Keene; Object-Oriented Programming in COMMON LISP: A Programmer's Guide to CLOS, Reading, Massachusetts u.a. (AddisonWesley) 1989. Keller, 1980. Robert M. Keller; Divide an CONCer: Data Structuring in Applicative Multiprocessing System, LISP Conference, Stanford/Redwood 1980, pp. 196 - 202. Kernighan/Plauger, 1978. B.W. Kernighan /P.J. Plauger; The Elements of Programming Style, New York (McGraw-Hill), Second Edition 1978. Kessler, 1988. Robert R. Kessler; LISP - Objects, and Symbolic Programming, Glenview, Illinois Boston London (Scott, Foresman and Compamny) 1988. Klaus/Liebscher, 1979. Georg Klaus / Heinz Liebscher (Hrsg.); Wörterbuch der Kybernetik, Frankfurt am Main (Fischer Taschenbuch Verlag), Orginal Berlin (Dietz Verlag) 1967, (4. überarbeitete Fassung 1976). Kolb, 1985. Dieter Kolb; Interlisp-D, Siemens AG, München, 17.07.85. Kreowski, 1981. H.-J. Kreowski; Algebraische Spezifikation von Softwaresystemen, in: Floyd/Kopetz, 1981, S. 46 - 74. Kruse, 1984. Robert L. Kruse; Data Structures and Program Design, Englewood Cliffs, New Jersey (Prentice/Hall International) 1984. Landin, 1964. P.J. Landin; The mechanical evaluation of expressions, in: The Computer Journal, Vol. 6, No. 4, 1964, pp. 308 - 320. Leckebusch, 1988. Johannes Leckebusch; XLisp - Die Programmiersprache der KI-Profis, München (Systhema Verlag) 1988. MacQueen/Sethi, 1982. D. B. MacQueen / Ravi Sethi; A semantic of types for applicative Languages, ACM LISP Conference New York, 1982, pp. 243 - 252.

378

Anhang

MacLennan, 1990. Bruce J. MacLennan; Functional Programming, Practice and Theory, Reading, Massachusetts (Addison-Wesley) 1990. Mandelbrot, 1977. Benoit B. Mandelbrot; Fractals: Form, Chance, and Dimension, San Franzisko (Freeman Press) 1977. Mandelbrot, 1983. Benoit B. Mandelbrot; The Fractal Geometry of Nature, New York (Freeman Press) 1983. Marti u.a., 1979. J.B. Marti / A.C. Hearn / M.L. Griss / C. Griss; Standard Lisp Report, ACM SIGPLAN Notices, Volume 14, Number 10, 1979. Mason, 1986. Ian A. Mason; The Semantics of Destructive LISP, Menlo Park (CSLI Lecture Notes Number 5) 1986. Mayer, 1988. Otto Mayer; Programmieren in Common LISP, Mannheim Wien Zürich (Bibliographisches Institut) 1988. Maurer, 1975. W. D. Maurer; The Programmer's Introduction to LISP, New York London (American Elsevier), Reissued 1975 (first 1972). McCarthy, 1960. John McCarthy; Recursive Function of Symbolic Expression and Their Computation by Machine, Part I, Comm. ACM 3,4 (April 1960),pp. 1 8 4 - 195. McCarthy u.a., 1966. John McCarthy / Paul W. Abrahams / Timothy P. Hart / Michael I. Levin; LISP 1.5 Programmer's Manual, Massachusetts (The M.I.T. Press), Second Edition , Second printing, January 1966. McCarthy, 1980. John McCarthy; LISP-Notes on Its Past and Future, Conference Record of the 1980 LISP Conference. McKay/Shapiro, 1980. Donald P. McKay / Stuart C. Shapiro; MULTI - A LISP Based Multiprocessing System, LISP Conference, Stanford/Redwood Est 1980, pp. 29 - 37. Miller, 1987. Richard K. Miller; Computers for Artificial Intelligence, A Technology Assessment and Forecast, second edition, SEAI Technical Publications (ISBN 0-89671-081-5) 1987. Minsky, 1975. M. Minsky; A Framework for Representing Knowledge, in: P.H. Winston (Ed.); The Psychology of Computer Vision, New York (McGraw-Hill) 1975.

A: Literaturverzeichnis

379

Model, 1980. Mitchell L. Model; Multiprocessing via Intercommunicating LISP Systems, LISP Conference, Stanford/Redwood 1980, pp. 188 - 195. Moon, 1986. David Moon; Object-Oriented Programming with Flavors, in: ACM Proceedings OOPSLA'86, SIGPLAN Notices, 21(11) 1986, p. 1-8. Moses, 1970. Joel Moses; The Function of FUNCTION in LISP or WHY the FUN ARG Problem Should be Called the Environment Problem, in: SIGSAM Bulletin, New York July 1970, pp. 13 - 27. Muchnick/Pleban, 1980. Steven S. Muchnick / Uwe F. Pleban; A Semantic Comparison of LISP and SCHEME, LISP Conference, Stanford/Redwood Est 1980, pp. 56 - 64. Mühlbacher, 1975. Jörg Mühlbacher; Datenstrukturen, München Wien (Carl Hanser) 1975. Müller, 1985. Dieter Müller; LISP - Eine elementare Einführung in die Programmierung nichtnumerischer Aufgaben, Mannheim Wien Zürich (Bibliographisches Institut) 1985. Narayanan/Sharkey, 1985. A. Narayanan / N. E. Sharkey; An Introduction to LISP, Chichester (Ellis Horwood) 1985. Nassi/Shneiderman, 1973. I. Nassi / B. Shneiderman; Flowchart Techniques for Structured Programming, in: SIGPLAN Notices 8 (1973) 8, p. 12- 26. Peitgen/Richter, 1986. H.-O. Peitgen / P.H. Richter; The Beauty of Fractals, Berlin Heidelberg New York (Springer Verlag) 1986. Péter, 1976. Rözsa Péter; Rekursive Funktionen in der Komputer-Theorie, Budapest (Akadémiai Kiadö) 1976. Pirsig, 1974. Robert M. Pirsig; Zen and the Art of Motorcycle Maintenance An Inquiry Into Values, Taschenbuchausgabe: Corgi Books, Deutsche Ausgabe: Zen und die Kunst ein Motorrad zu warten - Ein Versuch über Werte, übersetzt von Rudolf Hermstein, Frankfurt/Main (S.Fischer) 1976. Pitman, 1980. Kent M. Pitman; Special Forms in LISP, LISP Conference, Stanford/Redwood Est 1980, pp. 179 - 187. Pleszkun/Thazhuthaveetil,

1987. Andrew R. Pleszkun / Matthew J. Thazhutha-

380

Anhang

veetil; The Architecture of Lisp Machines, Computer, Vol. 20, No. 3, March 1987, pp. 3 5 - 4 4 . Pratt, 1979. Vaughan R. Pratt; A Mathematician's View of LISP, BYTE August 1979, pp. 162 - 168. Quayle u.a., 1984. Mary Ann Quayle / William Weil / Jeffrey Bonar / Alan Lesgolg; The Friendly Dandelion Primer, Tutoring Systems Gruop University of Pittsburgh 1984. Queinnec, 1984. Christian Queinnec; LISP, übersetzt ins Englisch von Tracy A. Lewis, London (Macmillan Publishers Ltd) 1984 (französische Orginal 1983, Eyrolles Paris). Queinnec/Chailloux, 1988. Christian Queinnec /Jérome Chailloux (Eds.); LISP Evolution and Standardization - Proceedings of the First International Workshop, 22-23 February 1988, Paris, France, Amsterdam (AFCET ISO) 1988. Rees, 1982. Jonathan A. Ross; T: A Dialect of Lisp - or, LAMBDA: The Ultimate Software Tool, LISP Conference New York, 1982, pp. 114 - 122. Rees/Clinger, 1986. Jonathan Rees / William Clinger (Editors); Revised 3 Report on the Algorithmic Language Scheme, AI Memo 848a, Artificial Intelligence Laboratory of the Massachusetts Institute of Technology, September 1986. Rogers, 1988. G.R. Rogers; COBOL-Handbuch, München Wien (R. Oldenbourg Verlag) 1988. Rome u.a., 1988. Erich Rome / Thomas Uthmann / Joachim Diederich; KIWorkstations: Überblick - Marktsituation - Entwicklungstrends, Bonn, Reading, Massachusetts u.a. (Addison-Wesley Deutschland) 1988. Sandewall, 1975. Erik Sandewall; Ideas about Management of LISP Data Bases, Int. Joint Conf. on Artificial Intelligence 1975, pp. 585 - 592. Sandewall, 1978. Erik Sandwall; Programming in an Interactive Environment: the „LISP" Experience, Computing Surveys, Vol. 10, No. 1 March 1978, pp 35-71. Schachter-Radig, 1987. Mina-Jaqueline Schachter-Radig, Entwicklungs- und Ablaufumgebungen für die Künstliche Intelligenz - Arbeitsplatzrechner für die Wissensverarbeitung, in: Informationstechnik it, 29. Jahrgang, Heft 5/1987, S. 334 - 349.

A: Literaturverzeichnis

381

Schefe, 1985. Peter Scheie; Informatik - Eine konstruktive Einführung - LISP. PROLOG und andere Konzepte der Programmierung, Mannheim Wien Zürich (Bibliographisches Institut) 1985. Schefe, 1986. Peter Schefe; Künstliche Intelligenz - Überblick und Grundlagen - Grundlegende Konzepte und Methoden zur Realisierung von Systemen der künstlichen Intelligenz, Mannheim Wien Zürich (Bibliographisches Institut) 1986. Scheid, 1985. Harald Scheid (Bearbeiter); DUDEN, Rechnen und Mathematik, 4. völlig neu bearbeitete Auflage, Mannheim Wien Zürich (Bibliographisches Institut) 1985. Schmitter, 1987. E. D. Schmitter; Praktische Einführung in LISP - LISP die Sprache der künstlichen Intelligenz, Holzkirchen (Hofacker) 1987. Schnupp/Floyd, 1979. Peter Schnupp / Christiane Floyd; Software - Programmentwicklung und Projektorganisation, 2. durchgesehene Auflage, Berlin New York (Walter de Gruyter) 1979 Schoffa, 1987. Georg Schoffa; Die Programmiersprache Lisp - Eine Einführung in die Sprache der künstlichen Intelligenz, München (Franzis) 1987. Shaw u.a., 1975. David E. Shaw /William R. Swartout / C. Cordeil Green; Inferring LISP Programs from Examples, International joint conference on artificial intelligence: Proceedings. Tbilisi, Georgia 4 (1975), pp. 260 - 267. Siklossy, 1976. Laurent Siklössy; Let's Talk Lisp, Englewood Cliffs, New Jersey (Prentice-Hall) 1976. Siemens, 1981. Siemens AG München; INTERLISP - Interaktives Programmiersystem BS2000 Benutzerhandbuch, Bestellnummer: U90015-J-Z17-1, Ausgabe September 1981, Version 4. Slade, 1987. Stephen Slade; The T Programming Language: A Dialect of LISP, Englewood Cliffs, New Jersey (Prentice-Hall, Inc.) 1987. Smith, 1988. Peter Smith; An Introduction to LISP, A Chartwell-Bratt Student Text, Lund (Studentlitteratur) 1988. Springer/Friedman, 1989. George Springer / Daniel P. Friedman; Scheme and The Art Of Programming, Cambridge, Massachusetts, London, England (MIT Press) 1989.

382

Anhang

Steele, 1976. Guy Lewis Steele Jr.; LAMBDA The Ultimate Declarative, Massachusetts Institute of Technology Artificial Intelligence Laboratory, AI Memo 379, November 1976. Steele, B., 1980. Barbara K. Steele; Strategies for Data Abstraction in LISP, LISP Conference, Stanford/redwood Est 1980, pp. 173 - 178. Steele u.a., 1984. Guy L. Steele Jr. / Scott E. Fahlman / Richard P. Gabriel / David A. Moon / Daniel L Weinreb; COMMON LISP The Language, (Digital Press) 1984. Stoyan, 1980. Herbert Stoyan; LISP - Anwendungsgebiete, Grundbegriffe, Geschichte, Berlin (Akademie-Verlag) 1980. Stoyan, 1986. Herbert Stoyan; Programmierstile und ihre Unterstützung durch sog. Expertensystem-Werkzeuge, in: Arno Schulz (Hrsg.); Die Zukunft der Informationssysteme - Lehren der 80er Jahre, Berlin Heidelberg New York u.a. (Springer-Verlag) 1986, S. 265 - 275. Stoyan u.a., 1987. Herbert Stoyan / Jerome Chailloux / John Fitch /Tim Krumnack / Eugen Neidl / Julian Padget; Towards a LISP Standard, in: Rundbrief des FA 1.2 Künstliche Intelligenz und Mustererkennung der Gesellschaft für Informatik e.V. Bonn, Nr. 44, Januar 1987. Stoyan/Görz, 1984. Herbert Stoyan / Günter Görz; LISP - Eine Einführung in die Programmierung, Berlin Heidelberg New York u.a. (Springer) 1984. Summers, 1977. Phillip D. Summers; A Methodology for LISP Program Construction from Examples, in: Journal of Association for Computing Machinery, Vol. 24, No. 1, January 1977, pp. 161 - 175. Tanimoto, 1987. Steven L. Tanimoto; The Elements Of Artificial Intelligence An Introduction Using LISP, Rockville, Maryland 1987 (Computer Science Press). Tatar, 1987. Deborah G. Tatar; A Programmers's Guide to COMMON LISP, Digital Equipment Corporation (Digital Press) 1987. Teitelman, 1975. Warren Teitelman / A. K. Hartley / J. W. Goodwin / D. C. Lewis / D. G. Bobrow / L. M. Masinter; INTERLISP Reference Manual, XEROX Palo Alto Research Center 1974, revised October 1974, revised December 1975.

A: Literaturverzeichnis

383

TLC-LISP, 1985. The Lisp Company Pob 487 Redwood Estates, CA 95044; TLC-LISP Documentation, Primer, Metaphysics and Reference Manual 1985. Touretzky, 1984. David S. Touretzky; LISP A Gentle Introduction to Symbolic Computation, New York u.a. (Harper&Row) 1984. Touretzky, 1986. David S. Touretzky; The Mathematics of Inheritance Systems, Research Notes in Artificial Intelligence, London (Pitman) 1986. Wagner, 1987. Jürgen Wagner; Einführung in die Programmiersprache LISP Ein Tutorial, Vaterstetten (IWT) 1987. Weinreb/Moon, 1981. Daniel Weinreb / David Moon; Lisp Machine Manual, Third Edition March 1981 (Massachusetts Institute of Technology, Cambridge). Weissman, 1967. Clark Weissman; LISP 1.5 Primer, Boston (PWS Publishers) 1967. Winston/Horn, 1984. Patrick Henry Winston / Berthold Klaus Paul Horn; LISP - Second Edition, Reading, Massachusetts (Addison-Wesley) 1984 (Deutsche Fassung 1987; Übersetzung von B. Gaertner u.a). Wirth, 1983. Nikiaus Wirth; Algorithmen und Datenstrukturen, Pascal-Version, 3. überarbeitete Auflage, Stuttgart (B.G. Teubner Verlag) 1983 (1. Auflage 1975). White, 1977. Jon L. White; LISP: Program Is Data - A Historical Perspective On MACLISP, Proceedings of the MACSYMA. Berkeley, Calif. 1977, pp. 1 8 0 - 189. Wong, 1985. William G. Wong; TLC-LISP - A fully featured LISP implementation, BYTE , October 1985, pp. 287 - 292. Wong, 1987. William G. Wong; PC Scheme: A Lexical LISP, MARCH 1987, BYTE, pp. 223 - 226. Yuasa/Hagiya, 1987 Taiichi Yuasa / Masami Hagiya; Introduction to Common Lisp, Boston Orland u.a. (Academic Press, Inc,), orginally published in Japanese 1986.

384

Anhang

B: L I S P - S y s t e m e

1. Einführung Unterschiedliche Probleme erfordern unterschiedliche Rechnerleistungen, kombiniert mit problemangepaßten LISP-Systemen. Ein Allzweckrechner mit einem Allzweck-LISP-System, bestehend aus LISP-Interpreter und/oder LlSP-Compiler, stellt stets ein Kompromiß dar, wobei im Einzelfall mehr oder weniger Nachteile in Kauf zu nehmen sind. Für die jeweilige LISP-Hard-/Software-Kombination bestehen zumindest vier Optimierungsaufgaben (vgl. z.B. Pleszkun/Thazhuthaveetil, 1987): o effizienter Funktionsaufruf o Verwalten der Umgebung („deep access" oder „shallow access", vgl. Abschnitt 8) o effizienter Zugriff auf Listenelemente bei ökonomischer Listenrepräsentation o Speicherplatzverwaltung („heap maintenance" mit zweckmäßigem"garbage collection") Evaluieren wir z.B.: (- FOO BAR), dann können die Werte von FOO und BAR vom Typ Integer oder Real sein, jeweils sind dann andere Operationen durchzuführen. Die Typprüfung zur Laufzeit ist möglichst effizient zu realisieren, da es in LISP-Konstruktionen nur so „wimmelt" von Funktionen. Spezielle LISPMaschinen (vgl. Abschnitt B:2) nutzen dabei Zusatzbits im Speicher („tagged memories") und prüfen damit hardwaremäßig den Typ. Der Rechner Symbolics 3600 verfügt z.B. über eine Hardware, die 34 Typen (Symbole, Listen, komplexe Zahlen etc.) unterstützt. Das einzelne Datenwort hat zusätzlich 6 „type tag"Bits, deren Auswertung gleichzeitig mit der eigentlichen Berechnung geschieht. Die klassische Listenabbildung mit CONS-Zellen aus zwei Zeigern (vgl. Abschnitte 2.1 und 5) ist nicht effizient in Bezug auf Speicherplatz und Zugriffszeit. Wenn es gilt, auf viele Listenelemente zuzugreifen, wenn wir also entlang der Liste „traversieren", dann ist die Adresse der Zelle, auf die wir als nächste zuzugreifen, in der Zelle enthalten, die gerade im Zugriff ist. Die Nachfolgeradresse kann erst an den Speicher geschickt werden, wenn der vorhergehende Zugriff abgeschlossen ist („addressing bottleneck"). Zusätzlich verbraucht diese klassische Abbildung viel Speicherplatz, da sowohl der CAR-Zeiger wie der CDR-Zeiger den gesamten Adressraum abdecken müssen und dazu entsprechend viele Bits benötigen. Dies ist nicht notwendig, wenn eine Liste kompakter abgebildet wird. Üblich sind sogenannte vektor-codierte und struktur-codierte Abbildungen.

B: LISP-Systeme

385

Das Konzept der Vektorcodierung geht zunächst von einer „linearen Liste" aus. Sie enthält keine Sublisten, d.h. keines ihrer Elemente ist wiederum eine Liste. Die einzelnen Elemente sind dann benachbart als Vektorfelder speicherbar. Ist ein Element jedoch wiederum eine Liste, dann wird dies als Ausnahme gekennzeichnet. Jedes Vektorfeld weist durch einen Zusatz („tag") aus, ob es ein Element enthält oder eine Adresse, die auf einen neuen Vektor verweist, oder unbenutzt ist oder das Ende (NIL) darstellt. Das Optimierungsproblem liegt in der fixen Länge eines Vektors (vgl. Abschnitt 6). Eine Lösung ist das sogenannte „CDR coding representation"-Schema, das LISP-Maschinen, wie z.B. der Rechner Symbolics 3600, nutzen. Hierbei verwendet man „CDR-codierte" Zellen, zusammengesetzt aus einem kleinen CDR-Teil und einem relativ großen CAR-Teil. Z.B. ist der CDR-Teil 2 Bits und der CAR-Teil 29 Bits. Die vier Möglichkeiten im CDR-Teil sind: CDR-normal, CDR-error, CDR-next und CDR-NIL. CDR-next und CDR-NIL approximieren die Vektorcodierung, CDR-normal entspricht der klassischen LISP-Zelle. Eine CDR-Codierung mit größerem CDR-Teil kann die Seitengröße der Speicherverwaltung des Betriebssystems berücksichtigen (vgl. z.B. Deutsch, 1978). Das Konzept der Strukturcodierung geht davon aus, daß in einer Zelle zusätzlich die Position innerhalb der Liste abgebildet ist. Die Listenstruktur ist als Knoteninformationen eines Baumes gespeichert. Die Listenelemente selbst sind dann als Menge von Tupeln: „Knotennummer - Wert" speicherbar. Das Suchen im Strukturbaum ist effizient gestaltbar, so daß der Zugriff auf Listenelemente im Durchschnitt wesentlich schneller als bei der klassischen Zweizeiger-Zelle ist. Welche Listenabbildung ist vorzuziehen? Vektorcodierung hat Speicherplatzvorteile, Strukturcodierung hat Zugriffsvorteile. Offensichtlich ist die Wahl davon abhängig, welche Listen und Zugriffe für das jeweilige LISP-Programm typisch sind. LISP selbst und nicht der Programmierer ist für die Speicherplatzverwaltung verantwortlich, insbesondere für die Bereitstellung (engl.: allocation) und Freigabe (engl.: deallocation) von CONS-Zellen im sogenannten „Heap"-Speicher (deutsch: Haufen). Die Freigabe im Sinne einer Speicherbereinigung (engl.: garbage collection) ist ein zweistufiges Verfahren mit „garbage"-Erkennung und „garbage"-Rückgabe. Das Ziel ist es, daß diese "garbage"-Bearbeitung keinesfalls kritische Ausführungen verzögert. Der Zeitpunkt der „garbage"-Bearbeitung und die „Heap"-Teilbereiche, die erfolgversprechend untersucht werden, sind zu optimieren (effiziente „Absterbeordnung"). Im Laufe der langen LISP-Entwicklung erzielte man wesentliche Verbesserungen bei den skizzierten Optimierungsaspekten: Funktionsaufruf, Umgebungsmanagement, Listenrepräsentation und Speicherplatzmanagement (zur Geschichte von LISP vgl. z.B. Stoyan, 1980). So ist McCarthy's LISP 7.5, quasi bis Mitte der 60iger Jahre der weltweite LISP-Standard, kaum mit LISP-Systemen, wie T oder Allegro LISP, vergleichbar. Die Ahnenreihe heutiger LISP-Systeme

386

Anhang

ist lang. Aus dem Stammvater LISP 1.5 entwickelte die Universität Stanford das LISP 1.6. Ebenfalls aus dem LISP 1.5 entstand am Artificial Intelligence Laboratory des Massachusetts Institute of Technology, Cambridge (kurz: MIT) der weit verbreitete Dialekt MacLISP. Die Universität von California, Irvine entwickelte das UCI LISP aus dem LISP 1.6. D. Bobrow und W. Teitelman integrierten in LISP eine mächtige Unterstützung des Programmierens; zunächst als BBN LISP später als InterLISP (vgl. Teitelman, 1975). Mit den LISP-Maschinen (vgl. Abschnitt B:2) entstanden zusätzlich zum MacLISP-Umfang speziell auf die Graphikfähigkeit ausgerichtete LlSP-Implementationen, z.B. ZetaLISP. Eine wesentliche Komponente von ZetaLISP ist das Flavor-System, das eine objektgeprägte Programmierung ermöglicht (vgl. Bonin, 1988a). Für die VAX-Rechner entstand aus dem MacLISP das Franz LISP, das sich auch am ZetaLISP orientierte. Mit NIL (New /mplementation of MacLisp) wurde die dynamische Bindungsstrategie durch die lexikalische ersetzt. Sie ist auch Kern für Implementationen wie z.B. Lucid (Common) LISP oder T (Scheme Implementation). In der Praxis stellt sich für jede Programmiersprache die Frage: Gibt es einen möglichst überall akzeptierten Standard? Für LISP lautet die Antwort „jein" (vgl. Queinnec/Chailloux, 1988). Es gibt eine Reihe von Bemühungen für LISP einen Standard zu definieren; zu nennen sind z.B: o Standard LISP (Stanford University, AI Projekt, vgl. Hearn, 1969, später Portable Standard LISP (PSL), vgl. Marti u.a., 1979) o Scheme (vgl. Reees/Clinger, 1986) o Common LISP (vgl. Steele u.a., 1984) o EuLISP (europäischer LISP-Standard, vgl. Stoyan u.a.,1987) In den 70iger Jahren reiften neue Konzepte, so daß die Standardbemühungen laufend überrollt wurden. Erst mit Common LISP konnten diese Verbesserungen, z.B. die lexikalische Bindungsstrategie oder generische Funktionen, im Standard integriert werden. Trotzdem bleibt Common LISP in wichtigen Punkten hinter Dialekten wie Scheme und TLC-LISP zurück. Z.B. hat Common LISP das schon bei MacLISP kritisierte Funktionen-Konzept. Seinen Funktionen fehlen die „first-class"-Eigenschaft (vgl. Abschnitt 2.2). Bei Common LISP gab man der Kompatibilität mit existierenden LISP-Dialekten besondere Priorität, um die große Menge vorhandener Anwendungssoftware leichter auf den Standard umstellen zu können. Beiden Bemühungen, ein neues (europäisches) LISP zu standardisiern, hat die Stringenz der Spezifikation vor der Kompatibilität mit vorhandenen LISPProgrammen (zunächst?) Priorität. Aus den Erfahrungen mit Common LISP hat man gelernt und spezifiziert den Standard nicht als monolithischen Block eines „großen" LISP-Systems. Die Spezifikation sieht verschiedene Leistungs-Schichten vor, z.B. (vgl. Stoyan u.a., 1987): Level 0: Ein „Pure LISP"-System im Umfange von Scheme.

B: LISP-Systeme

387

Level 1: Ein „mittleres" LISP im Umfange von PSL. Level 2: Ein „großes" LISP im Umfange von Common LISP.

2. Rechner für LISP-Systeme Wir skizzieren hier Rechner für LISP-Systeme ohne eine Bewertung in Bezug auf einzelne Aufgabenklassen (vgl. Abschnitt 12). Auch sind keine Leistungskennwerte angegeben. Dazu sei auf den LISP-speziiischen „Benchmark"Vorschlag von R. G. Gabriel verwiesen (vgl. Abschnitt 8.5; Gabriel, 1985). LISP-Systeme sind heute auf allen gebräuchlichen Rechnern verfügbar. Neben sogenannten „Symbolic Processors" (LISP-Maschinen) kann erfolgreich mit Mainframes, Workstations, Personalcomputern und Supercomputern in LISP Software konstruiert werden. Diese Rechnereinteilung in vier grobe Leistungsklassen benutzen wir, um die Entwicklung und spezielle Eigenschaften zu erörtern. Eine Kurzbeschreibung verbreiteter LISP-System enthält der anschließende Abschnitt B: 3, in dem auch auf die Rechnerklassen verwiesen wird. LISP-Maschinen

Mitte der 70iger Jahre entstand am MIT ein Processor, speziell ausgerichtet auf die Anforderungen der Symbolverarbeitung. Dieser Rechner wurde unter dem Namen „LISP-Maschine" bekannt. Das MIT vergab an die Firmen Symbolics, Inc. und an LISP Machine, Inc. die Lizenz zum Bau und Vertrieb dieses Prozessors. In Konkurrenz zum MIT-Projekt entwickelte man bei Xerox (Palo Alto Research Center, kurz: PARC) ebenfalls eine kommerziell verfügbare LISPMaschine. Im Jahre 1984 stellte Texas Instruments den „Explorer" vor, der auf den Arbeiten vom MIT (und PARC) fußt. Unter dem Begriff "Symbolic Processor" fallen auch die Ankündigungen der „SM4500" (Inference Maschine) und die „Melcom PSI machine" (Mitsubishi), die erste PROLOG-basierte Maschine. Bei den LISP-Maschinen war z.B. im Jahre 1987 Symbolics, Inc. der Marktführer (ca. 2000 Maschinen, vgl. Miller, 1987). Der Markterfolg ist vornehmlich auf die hervorragende Software-Entwicklungsumgebung zurückzuführen (vgl. z.B. Bromley/Lamson, 1987). Das Common LISP, entwickelt aus dem ursprünglichen ZetaLISP, ist durch ein leistungsfähiges FLAVOR-System ergänzt. Mit diesem ist ein dynamisches Windowsystem implementiert. Ein incrementeller Compiler erleichtert die EDIT-COMPILE-DEBUG-Abläufe. Eine Vielzahl von „Werkzeugen" aus der Disziplin „Künstliche Intelligenz (KI)" ist auf diesen LISP-Maschinen lauffähig, z.B. OPS5 (Verac), KEE (IntelliCorp) oder ART (Inference Corporation). Hervorzuheben ist die ausgezeichnete Graphik-Unterstützung, sowohl schwarz-weiß wie farbig. Der „Explorer" verfügt über Common LISP mit der FLAVOR-Erweiterung der ursprünglichen MIT LISP-Maschine. Ähnlich wie bei der Symbolics-Fami-

388

Anhang

lie sind auch für den „Explorer" der große Arbeitsspeicher (bis 16 Megabyte) und der große virtuelle Adressraum (bis 128 Megabyte) charakteristisch. Der „Explorer" läuft mit einem von Tl-implementierten UNIX System V. Netzanschlüsse zu IBM SNA und DECnet sind ebenso vorhanden wie eine hochauflösende Graphik. Die Xerox „1100-Familie" entstammt nicht direkt von der MIT LISPMaschine ab. Sie wurde parallel dazu von Xerox entwickelt. Obwohl ein Common LISP verfügbar ist, liegt der Schwerpunkt auf InterLISP-D, eine Software-Entwicklungsumgebung mit neuartiger Unterstützung für den Programmierer. Beispielsweise ist in InterLISP-D ein „Programmierassistent" integriert, der „Schreibfehler" erkennt und beim „Debugging" behilflich ist. Für die objektorientierte Programmierung gibt es (statt Flavors) Common LOOPS, eines der Ausgangssysteme für den Standard CLOS (Common LISP Object System, vgl. Abschnitt 10). Neben LISP ist PROLOG verfügbar und über den Anschluß zur VAX-Familie von Digital Equipment Corporation gibt es Schnittstellen zu FORTRAN, C und Pascal. Marktbekannte Kl-Software z.B. KEE (IntelliCorp) ist ebenso lauffähig, wie noch weniger bekannte Software, z.B. Epitool (Epitec AB, Sweden). Vom unteren Leistungsende erwächst diesen Maschinen Konkurrenz durch LISP-Prozessorkarten, die Personalcomputer zu LISP-Maschinen erweitern. Zu nennen sind hier der microExplorer von Texas Instruments und Maclvory von Symbolics. Beide erweitern den Rechner Apple Macintosh II zu einer leistungsstarken LISP-Maschine. Z.B. verfügt der Maclvory über den 40 Bit-IvoryLlSP-Chip und verwendet Symbolics Common LISP mit New Flavors, Windowsystem, Multitasking in einem Adressraum, Zmacs-Editor, Graphik-Funktionen, Netzwerk-Funktionen (incl. Zmail). Die LISP-Prozessorkarten transportieren die gelungene Software-Entwicklungsumgebung der großen LISP-Maschinen (z.B. Genera) auf Personalcomputer. Der notwendige PC-Ausbau verursacht jedoch Kosten in der Höhe einer gut ausgebauten Workstation. Mainframes

Sowohl für die IBM-Welt (LISP/VM, FRANZLISP, Le LISP etc.) als auch für die Siemens-BS2000-Welt sind leistungsfähige (Common) LISP- und InterLlSP-Systeme vorhanden (vgl. z.B. Siemens, 1981). Gravierende Nachteile gegenüber den LISP-Maschinen liegen in der geringen (oder sogar ganz fehlenden) Graphik-Unterstützung. Ihr Vorteil gegenüber diesen dezidierten Maschinen ist die problemlose Integration mit bestehenden Datenbanken und Anwendungspaketen. Trotz der leistungsfähigen Software-Entwicklungsumgebung, die z.B. InterLISP im Vergleich zu COBOL auch auf Mainframes bietet („Do What I Mean", Programmierassistent, Debugger, etc.), gibt es vergleichsweise sehr we-

B: LISP-Systeme

389

nig Implementationen. Der berechtigte Ruf, viel Kapazität des Mainframes zu benötigen, wirkt abschreckend. Workstations Hier sind mit dem Etikett „Workstation" Rechner bezeichnet, die mindestens 32 Bit Wortlänge haben und bei denen z.B. ein Betriebssystem wie UNIX (erfolgversprechend) eingesetzt werden kann (vgl. Rome u.a., 1988). Hierzu zählen z.B. (ohne Anspruch auf Vollständigkeit!): Apollo (DN4000), Data General (MV/ECLIPSE), Digital Equipment (VAX-Familie), Hewlett-Packard (HP9000), IBM (RT-Familie), NCR (Tower Familie), SUN (Sun-4/260). Für diese Rechner gibt es LISP-Systeme in großer Zahl (vgl. Abschnitt B:3). Ihr Vorteil gegenüber den dezidierten LISP-Maschinen liegt einerseits in den geringeren Einstiegskosten und andererseits in der einfachen Integration in bestehende Automationen. Im Gegensatz zu normalen, nicht mit LISP-Prozessorkarten erweiterten Personalcomputern können mit Workstations nützliche Aufgaben in LISP realisiert werden, insbesondere dann, wenn auch die Graphikoption einzusetzen ist. Personalcomputer Zur Klasse Personalcomputer sollen hier Rechner zählen, die im Regelfall mit dem Betriebssystem MS-DOS (PC-DOS) gefahren werden. Neben diesen IBM-PC/XT/AT-Rechnern gehören ebenfalls dazu die Rechner der Apple-Welt, insbesondere der MAC(intosh). Sowohl für die IBM-PCs (bzw. kompatiblen Rechner) als auch für die MACs gibt es mehrere Common LISP-Systeme, wie Abschnitt B:3 ausweist. Hauptproblem in der PC-Welt ist die Adressierbarkeit eines ausreichenden Arbeitsspeichers z.B., wenn die Intel-Chips 8088 / 8086 eingesetzt werden. Praxisrelevante Anwendungen können hier nur bedingt entwickelt und gefahren werden. Für Schulungs- und Probierzwecke sind PCs jedoch gut geeignet. Die zunehmende Verfügbarkeit von PCs mit den Mikroprozessoren vom Typ 80386 oder vom Typ MC68020 entschärft diese Restriktion. Supercomputer Für Supercomputer sind Rechner der Firma Cray ein Maßstab. Cray bietet das Cray LISP an. In Abweichung von den obigen Ausführungen soll hier doch eine

390

Anhang

Leistungsangabe erfolgen, weil Supercomputer heute ausschließlich aufgrund ihrer Rechengeschwindigkeit in Betracht kommen. Von dem Cray LISP wird berichtet (Robort J. Douglas, vgl. Miller, 1987), daß es 15 bis 30 mal schneller ist als LISP auf einer VAX 11/780 und noch 5 bis 15 mal schneller als auf einer Symbolics 3600.

3. Software In der folgenden tabellarischen Aufzählung ist für den jeweiligen Interpreter und/oder Compiler der Hersteller bzw. der Vertreiber genannt und die Rechnerklasse, für die das System (ursprünglich) konzipiert wurde. Zusätzlich sind Merkmale und Besonderheiten des Systems skizziert. Die Auswahl der Systeme ist mehr oder weniger zufällig. Berücksichtigt sind Systeme mit vielen Installationen. Die folgende Kurzübersicht erhebt keinen Anspruch auf vollständige Nennung der Marktführer. Die Systeme sind in alphabetischer Reihenfolge genannt. Allegro Common LISP Rechnerklasse: Workstation, PC Hersteller. Coral Software Corp., P.O. Box 307, Cambridge, MA 02142, USA Merkmale: Allegro Common LISP enthält eine Programmierumgebung abgestimmt auf die Macintosh-Welt. Das System ist ein inkrementeller Compiler. BYSO LISP Rechnerklasse: PC Hersteller. Levien Instrument Co., Sitlington Hill, P.O. Box 31, McDowell, VA 24458, USA. Merkmale: BYSO LISP ist an Common LISP ausgerichtet,hat jedoch starke Wurzeln im MacLISP und im klassischen LISP 1.5. Der Interpreter hat daher dynamische Bindungsstrategie. Der Compiler hat sowohl lexikalische als auch dynamische Bindung. Das CLOSURE-Konstrukt ermöglicht eine objektgeprägt Programmierung. Cambridge LISP Rechnerklasse: PC Bezugsquelle: Philgerma GmbH, Barerstraße 32, D-8000 München 2. Merkmale: Cambridge LISP basiert auf PSL (Portable Standard LISP). Exper LISP Rechnerklasse: PC Hersteller: ExpeiTelligence, Inc., 559 San Ysidro Road, Santa Barbara, CA 93108, USA. Merkmale: Exper LISP enthält eine Graphik, die unter Exper LOGO bekannt wurde. Diese Graphik ermöglicht 3D-Abbildungen. Neben ExperLISP gibt es vom gleichen Hersteller einen Common LISP Compiler. Franz LISP Rechnerklassen: Mainframe und Workstation Hersteller: Franz, Inc. 1141 Harbr Bay Parkway, Alameda, CA 94501, USA. Merkmale: Franz LISP ist ein sehr weit verbreiteter, klassischer LISP-Dialekt

B: LISP-Systeme

391

mit starker Common LISP Ähnlichkeit. Während Common LISP lexikalische Bindung hat, bindet Franz LISP dynamisch. Golden Common LISP (GC LISP) Rechnerklasse: PC Hersteller: Gold Hill Computers, Inc. 163 Harvard St., Cambridge, MA 02139, USA. Merkmale-. GC LISP 286/386 ist ein Common LISP für die PC-Welt. Mit HALO steht eine leistungsfähige Graphik-Toolbox zur Verfügung. Aufgrund des Common LISP-Umfanges ist GCLISP wesentlich größer und zum Teil nicht so schnell wie z.B. muLISP. IBUKI Common LISP Rechnerklasse: Workstation Hersteller: Ibuki Inc., 1447 N. Shoreline Blvd. Mountain View, CA 94043, USA Merkmale: Ibuki umfaßt keinen vollständigen Compiler, sondern ein Preprozessor für C Compiler. Der LISP-Code wird in C übersetzt. InterLISP-D Rechnerklasse: LISP-Maschine (Xerox) Hersteller: Xerox, Palo Alto Research Center, 3333 Coyote Hill Road, Palo Alto, California 94304, USA Merkmale: vgl. Abschnitt B:2 IQ-LISP / IQ-CLISP Rechnerklasse: PC Hersteller: Integral Quality, Inc. P.O. Box 31970, Seattle, WA 98103-0070, USA. Merkmale: IQ-LISP ist ähnlich zu UCI LISP, einem Dialekt von MacLISP. IQLISP unterstützt den 8087 Arithmetikprozessor. IQ-CLISP ist die Common LISP Version. Es verfügt über ein einfaches Window-System. Kyoto Common LISP Rechnerklasse: Workstation Hersteller: Kyoto Common LISP (KCL) wurde am „Research Institute for Mathematical Sciences, Kyoto University, Japan" entwickelt. Merkmale: KCL ist ein vollständiges Common LISP. Es hat bei geladenem Interpreter und Compiler einen Bedarf von 1.4 Megabyte Arbeitsspeicher. Der Kern von KCL ist in C geschrieben. KCL hat einen zweistufigen Compiler. Zunächst wird nach C umgesetzt und dann von C in den compilierten Code. Siemens vertreibt KCL für ihre MX-Serie. Le LISP Rechnerklassen: Mainframe (IBM unter VM/UTS), Workstation,PC Hersteller: Le LISP wurde im Europäischen KI-Zentrum INRIA, Frankreich entwickelt. Die Portierung auf PCs erfolgte durch Act Informatique. Merkmale: Eine große Untermenge von Common LISP ist abgedeckt. Das Konzept der virtuellen Maschine erleichtert eine Portierung zwischen den Rechnerklassen. Le LISP enthält ein Paket-Konzept, Stringverarbeitung, Vektoren und Konstrukte für nicht-lokale Kontrollstrukturen.

392

Anhang

Lucid Common LISP Rechnerklasse: Workstation Hersteller: Lucid, Inc. 707 Laurel Street, Menlo Park, CA 94025, USA. Merkmale: Lucid Common LISP ist eine Entwicklungsumgebung für den professionellen Produktionseinsatz. Neben dem vollen Common LISP Sprachumfang sind EMACS-Stil-Editor, Window-System und ein Flavor-System vorhanden. Dieses Produkt ist auch unter dem Namen des Workstation-Herstellers bekannt (vgl. z.B. für Apollo Computer DOMAIN, 1986). muLlSP Rechnerklasse: PC Hersteller: Soft Warehouse, Inc. POB 11174, Honolulu, Hawaii 96828; Microsoft, Inc. 10700 Northrup Way, Bellevue, WA 98004, USA. Merkmale: muLISP deckt ca. 400 der ca. 800 Common LISP Konstrukte ab. MuLISP hat den Ruf besonders schnell zu sein und wenig Speicherplatz zu benötigen. Aufgrund der Pseudocode-Compilation ist der Speicherbedarf relativ gering. Nil's LISP Rechnerklasse: PC Hersteller: A. Fittschen, Weusthoffstraße 6, D-2100 Hamburg 90. Merkmale: Nil's LISP basiert primär auf TLC-LISP und enthält Komponenten von UCI LISP. Ein Flavor-System ist verfügbar. Nil's LISP wird an der Universität Hamburg eingesetzt. PSL Rechnerklasse: Workstation Hersteller: University of Utah, Computer Science Department, Salt Lake City, UT 84112, USA. Merkmale: Versuch eines LISP-Standards vor dem Common LISP Standard. Starker Einfluß von FranzLISP und MacLISP. PC Scheme Rechnerklasse: PCs: MS-DOS Rechner Hersteller: Texas Instruments, Dallas, USA. (Bezugsquelle: The Knowledge Base, Hochberg la, D-8404 Wörth/Donau) Merkmale: Ein modernes (gestrafftes) LISP mit lexikalischer Bindungsstrategie (vgl. Abschnitt C). Unterstützt wird die objekt-orientierte Programmierung durch SCOOPS. Scheme wurde von Gerald Jay Sussman und Guy Lewis Steele Jr. am MIT für Experimentierzwecke mit Programmierparadigmen entworfen. Scheme verzichtet auf Kompatibilität mit klassischen LISP-Konzepten zugunsten eines klaren Konzeptes. Die Portierung von Common LISP Programmen ist im Regelfall unproblematisch. Power LISP Rechnerklasse: PC Hersteller: MicroProducts, Inc. 370W. Camino Gardens Boulevard, Boca Raton, Florida 33432, USA. Merkmale: Vollständige Implemetation von Interlisp (vgl. Teitelman, 1975). Enthält CLISP („conversational LISP"), das laut Hersteller eine LISP-Syntax ermöglicht, die mehr an konventionelle Programmiersprachen angepaßt ist.

B: LISP-Systeme

393

SoftWave LISP Rechnerklasse' PC Hersteller: SoftWave, Seattle, WA 98103-1607, USA Merkmale: Einfacher LISP-Interpreter, der sich am LISP 1.5 orientiert. Nur für Schulungszwecke geeignet. T Rechnerklasse: Workstation Hersteller. T Project, Yale Department of Computer Science, Box 2158, New Häven, CT 06520, USA Merkmale: T ist eine Scheme Implementation, wobei Scheme eine Untermenge von T bildet. T hat objektgeprägte Konstrukte und Makros eingebaut, verfügt über einen „optimierten" Compiler und unterstützt insbesondere die Fehlersuche (vgl. Slade, 1987). TLC-LISP Rechnerklasse: PC Hersteller: The LISP Company, Redwood Estates, CA 95044, USA (Bezugsquelle (mit Lieferproblemen): Calculemus (M. Voltenauer-Lagemann), Hans-Graessel-Weg 1, D-8000 München 70) Merkmale: TLC-LISP zeichnet sich durch eine klare Grundkonzeption aus, die die Realisierung von „first class"-Objekten zum Ziel hat. Es weicht daher in einigen Punkten von Common LISP ab. Versucht wird das ZetaLISP für PCs verfügbar zu machen. Integriert sind daher ein Klassensystem und ein PaketKonzept. Zusätzlich enthält TLC-LISP die von LOGO aus bekannte „Turtle Graphik" (vgl. Abschnitt 13.1). Der Lieferumfang umfaßt auch den Compiler und Interpreter in LISP-Quellecode. UC1LISP Rechnerklasse: PC (ursprünglich für PDP-10 entwickelt) Bezugsquelle: H. Peters, Eielkampsweg 50, D-2083 Halstenbeck. Merkmale: Eine Implementation des UCI LISP (vgl. Abschnitt B:l) für PCs in vollem Umfang der ursprünglichen PDP-10-Version. XLISP Rechnerklasse: PC Hersteller: David Betz, Public Domain Programm Merkmale: XLISP integriert Konzepte von Scheme und Common LISP (vgl. z.B. Leckebusch, 1988). Es enthält Konstrukte für die objekt-orientierte Programmierung. Eine Programmierumgebung fehlt bisher. ZetaLISP Rechnerklasse: LISP-Maschine Hersteller: Hersteller der jeweiligen LISP Maschine, Grundlage bildet die MIT LISP Maschine (vgl. Abschnitt B:2). Merkmale: Flavor-Konzept (vgl. Abschnitt 10)

394

Anhang

C: PC Scheme Übersicht Systemarchitektur:

Legende:

[1] Beim Aufruf von PCS wird die Datei SCHEME.INI evaluiert. [2] Bestehen noch nicht gesicherte Modifikationen des EDWIN-Puffers, dann ist die Sicherung nachholbar. [3] Mit DOS-CALL sind MS-DOS-Kommandos ausführbar. Das Beispiel zeigt den Einsatz des Editors WordStar. [4] (EDWIN) ruft einen Scheme Texteditor auf. Enthält der Puffer Eintragungen, dann werden diese zum Bearbeiten angezeigt. [5] Mit dem Drücken der Tasten Esc und danach Buchstaben O wird der gesamte Puffer evaluiert und nach Scheme verzweigt. [6] Tastenfolge Esc dann Ctrl und zusätzlich X bewirkt: Der nächste symbolische Ausdruck betrachtet von der aktuellen Cursorposition aus, d.h. häufig von „(" bis zur korrespondierenden „)", wird evaluiert und nach Scheme verzweigt. [7] Tastenfolge Ctrl und zusätzlich X danach Ctrl und zusätzlich S [für save] sichert den Puffer in einer Datei, deren Name EDWIN abfragt. [8] Tastenfolge Ctrl und zusätzlich X danach Ctrl und zusätzlich V [für visit file] liest eine Datei in den Puffer, deren Name EDWIN abfragt. [9] Einige EDWIN-Kommandos: Esc dann < ==> Cursor auf Anfang des Puffers setzen Esc dann > ==> Cursor auf Ende des Puffers setzen

C: PC Scheme Übersicht

395

Esc dann Ctrl und F ==> Cursor auf nächsten symbol. Ausdruck Esc dann Ctrl und B ==> Cursor einen symbol. Ausdruck zurück Ctrl und K ==> löscht Zeile Ctrl und Y ==> fügt aktuell Gelöschtes an Curs.Position ein Ctrl und X und / ==> teilt Bildschirm in EDWIN und Scheme oder hebt Teilung auf [10] Scheme-Konstrukte (nächster Abschnitt). Eingabekorrektur mit Ins, Del, , da letzte Eingabe im Puffer steht. [11] Tastenfolge Ctrl und Q führt zum Verlassen der Testhilfe Ausgewählte Scheme-Konstrukte: Darstellung der Konstrukte anhand von Beispielen in der verkürzten Notation: statt: eval> ==> ;Kommentar (vgl. Abschnitt 1.2) kurz: ==> ;Kommentar Konstruktoren: (CONS 'PROGRAMMIEREN ' ( I S T

SCHWIERIG)) ==> (PROGRAMMIEREN IST SCHWIERIG) ( L I S T (* 2 3) 7 (+ 4 4) 9) ==> (6 7 8 9) (APPEND ' ( A B) ' ( C D ) ) ==> (A B C D) b i n d e t F00 an den Wert 5 (DEFINE F00 ( + 2 3 ) ) ==> F00 (LAMBOA (X Y) ( - X Y ) ) ==> # anonyme F u n k t i o n F u n k t i o n s a p p l i k a t i on ((LAMBDA (X Y) ( - X Y ) ) 3 2) ==> 1 l o k a l e V a r i a b l e n X und Y (LET ( ( X 3) (Y 2 ) ) ( - X Y ) ) ==> 1 l o k a l e V a r i a b l e n X und Y (LET* ( ( X 2) (Y (* 3 X ) ) ) (+ X Y ) ) s e q u e n t i e l l gebunden. ==> 8 ; ; R e k u r s i v e r Bezug auf l o k a l e V a r i a b l e F00 (LETREC C C F00 (LAMBDA ( L ) (COND ((NULL? 1) 0) (T (+ 1 (F00 (CDR L ) ) ) ) ) ) ) ) (F00 ' ( A B ) ) ) ==> 2 (DEFINE KARL (LAMBDA (X Y) (* ( - X 4) Y ) ) ) ==> KARL (DEFINE (KARL X Y) (* ( - X 4) Y ) ) ==> KARL ¡ k ü r z e r e S c h r e i b w e i s e (KARL 2 3) ==> -6 (PUTPROP 'MARX "Berühmter Mann" 'DOKU) ==> "Berühmter Mann" (GETPROP 'MARX 'DOKU) ==> "Berühmter Mann" (DEFINE-STRUCTURE KONTO (NUMMER 0) (STAND 5 0 . 2 3 )

INHABER) ==> KONTO (DEFINE K l (MAKE-KONTO 'NUMMER 1 'INHABER 'OTTO)) ==> K l (KONTO-INHABER K l ) ==> OTTO ( S E T ! (KONTO-INHABER K l ) " W i l l i " ) ==> # (KONTO-INHABER K l ) ==> " W i l l i "

Selektoren und Mutatoren: (DEFINE MOTTO ' ( L I S P MACHT FREUDE)) ==> MOTTO (DEFINE MEIN-SYMBOL MOTTO) ==> MEIN-SYMBOL (CAR MOTTO) ==> L I S P (CDR MOTTO) ==> (MACHT FREUDE) (SET-CAR! MOTTO 'LERNEN) ==> (LERNEN MACHT FREUDE) MEIN-SYMBOL ==> (LERNEN MACHT FREUDE) ; N e b e n e f f e k t ! ! (SET-CDR! MOTTO ' ( I S T MÜHSAM)) ==> ( I S T MÜHSAM) MEIN-SYMBOL ==> (LERNEN IST MÜHSAM) ;Nebeneffekt!!

396 Prädikate

Anhang

und

Fallunterscheidung:

(DEFINE FOO *A) ==> F00 (DEFINE BAZ FOO) ==> BAZ (EQ? BAZ FOO) ==> #T ¡benennen dasselbe "Objekt" (EQ? '(A B) '(A B)) ==> () ¡leere Liste steht für "false" (EQ? (CONS 'A -B) (CONS 'A 'B)) ==> () ¡verschiedene C O N S - Z e l l e n (EQUAL? (CONS 'A 'B) (CONS A 'B)) ==> #T ¡vgl. A b s c h n i t t 2.1 (ZERO? (- 2 2)) ==> #T ¡prüft numerisch 0 (NULL? '()) ==> #T ¡prüft auf leere Liste, entspricht NOT (NOT (+ 2 3)) ==> () ¡Alles ungleich () ist wahr (NUMBER? (+ 3 4)) ==> #T (MEMBER? 'B '(A B C)) ==> (B C) benutzt EQUAL? (MEMQ (LIST 'B) '(A (B) C)) ==> () benutzt EQ? (PROC? (LAMBDA (X) (* X X))) ==> #T prüft ob Funktion Für Zahlen: < >- EVEN? ODD? (DEFINE A N Z A H L (LAMBDA (L) (COND ((NULL? L) 0) (T (+ 1 (ANZAHL (CDR L)))) ))) ==> A N Z A H L (ANZAHL '(ER SIE ES)) ==> 3 (AND 1 2 3) ==> 3 (AND 0 NIL (/ 1 0)) ==> () (OR NIL 2 (/ 1 0)) ==> 2

Sequenz und

¡1. Klausel ¡2. Klausel

"true", da ungleich () A b b r u c h nach NIL-Erkennung A b b r u c h nach (NOT NIL)

Iteration:

(DEFINE FOO (BEGIN '(A B) '(- 3 4) ( + 2 3))) ==> FOO FOO ==> 5 ¡BEGIN gibt letzten Wert (DEFINE FOO (BEGINO '(A B) '(- 3 4) (+ 2 3))) ==> FOO FOO ==> (A B) ¡BEGINO gibt ersten Wert

zurück zurück

(MAP ) ==> ¡wendet die Funktion auf jedes ; Element der Liste an und gibt die Ergebnisse als Liste aus. (MAP (LAMBDA (X) (+ 2 X)) '(1 2 3)) ==> (3 4 5) (DO (( < a n f a n g s w e r t > ) ... ) ( ... ) ¡Abbruchklausel ) ==> < v a l u e - s e x p r _ n > (DO ((I 1 (+ I 1))) ((> I 5) I) (DISPLAY " ") (DISPLAY I)) ==> 1 2 3 4 56

Input-/Output-Interface: (DEFINE M E I N E - A U S G A B E

(OPEN-OUTPUT-FILE

"C:\\TI\\SCHROTT.LSP")) ==> M E I N E - A U S G A B E (WRITE 'DI ES -1 ST-SEXPR-1 M E I N E - A U S G A B E ) ==> # < P 0 R T > (NEWLINE M E I N E - A U S G A B E ) ==> # < P 0 R T > (WRITE 'DI E S - I S T - S E X P R - 2 M E I N E - A U S G A B E ) ==> # < P 0 R T > ( C L O S E - O U T P U T - P O R T M E I N E - A U S G A B E ) ==> # (WRITELN (* 2 3) " ist mehr als" (- 7 2))==> ;Zei1envorschub 6"ist mehr als"5 () ¡Rückgabewert (DISPLAY "Mein Wert") ==> Mein Wert ¡ohne doppeltes Hochkomma (PRINT "Mein Wert") ==> ¡mit Vorschub und d o p p e l t e m Hochkomma "Mein Wert" () (PP ) ==> ... ¡Pretty Print von < v a l u e - s e x p r >

397

C: PC Scheme Übersicht (DEFINE MEINE-EINGABE

(OPEN-INPUT-FILE

"C:WTIWSCHROTT.LSP")) ==> MEINE-EINGABE (READ M E I N E - E I N G A B E ) ==> D I E S - I S T - S E X P R - 1 ( R E A D M E I N E - E I N G A B E ) = = > DI ES -1 S T - S E X P R - 2 (READ M E I N E - E I N G A B E ) ==> # ! E O F ;End of f i l e (CLOSE-INPUT-PORT MEINE-EINGABE) ==> # (PORT? 'CONSOLE) ==> #T ¡ T a s t a t u r / B i l d s c h i r m sind D e f a u l t - P o r t (READ-CHAR) ==> (READ-ATOM) ==> ( R E A D - L I N E ) = = > < z e i 1 e - v o n - t a s t a t u r > ; R ü c k g a b e w e r t ist S t r i n g (DEFINE M E I N - F E N S T E R (MAKE-WINDOW "Otto" #T)) ==> MEIN-FENSTER ;mit Label " O t t o " u n d R a n d ( W I N D O W - S E T - P O S I T I O N ! M E I N - F E N S T E R 2 3) ==> # ¡ l i n k e o b e r e E c k e in Z e i l e 2 u n d S p a l t e 3 ( W I N D O W - G E T - P O S I T I O N ! M E I N - F E N S T E R ) = = > (2 . 3) ( W I N D O W - S E T - S I Z E ! M E I N - F E N S T E R 10 4 0 ) ==> # ¡ G r ö ß e 10 Z e i l e n u n d 40 S p a l t e n p r o Z e i l e ( W I N D O W - G E T - S I Z E M E I N - F E N S T E R ) = = > (10 . 4 0 ) (WINDOW-CLEAR MEIN-FENSTER) ==> # ( W I N D O W - S E T - C U R S O R ! M E I N - F E N S T E R 6 12) ==> # ¡ s e t z t C u r s o r a u f Z e i l e 6 u n d S p a l t e 12 ( W I N D O W - G E T - C U R S O R M E I N - F E N S T E R ) = = > (6 . 12) (WINDOW-POPUP MEIN-FENSTER) = = > # < P 0 R T > ¡ s i c h e r t ü b e r d e c k t e D a t e n & g i b t M E I N - F E N S T E R aus (WINDOW-POPUP-DELETE MEIN-FENSTER) = = > # < P 0 R T > ¡ l ö s c h t M E I N - F E N S T E R und s t e l l t d e n l e t z t e n ; Z u s t a n d w i e d e r her (WINDOW? MEIN-FENSTER) ==> #T

Graphik'. Hinweis: Farben und Auflösung systemabhängig (CGA / EGA / VGA) [CGA ::= -160 () ¡ X - W E R T / Y - W E R T geben rechte obere Ecke an, Cursor ; stellt linke U n t e r e Ecke dar ( D R A W - F I L L E D - B O X - T O X - W E R T Y - W E R T ) = = > () ¡ z e i c h n e t S c h r a f f u r b o x ( D R A W - L I N E - T O X - W E R T Y - W E R T ) = = > () ¡vom C u r s o r z u m X - W E R T / Y - W E R T ( D R A W - P O I N T X - W E R T Y - W E R T ) = = > () ¡zeichnet Punkt (IS-POINT-ON? X-WERT Y-WERT) ==> #T ¡falls dort g e m a l t wurde ( P O S I T I O N - P E N X - W E R T Y - W E R T ) = = > () ¡ p o s i t i o n i e r t C u r s o r ( G E T - P E N - P O S I T I O N ) = = > (12 . 23) ¡(X-WERT . Y-WERT) ( S E T - P A L E T T E ! 0 2) = = > () ¡0 ::= V o r d e r g r u n d 2 ::= F a r b e ; Hintergrund wäre 1 ( S E T - P E N - C O L O R ! 2) = = > 2 ¡ F a r b e g e s e t z t , rot (GET-PEN-COLOR) ==> 2

398

Anhang

D: Schlagwörter A-Liste 143 Abarbeitungsaufwand 295 Abbruch-Bedingung 71 Ablaufsteuerung 46 abstrakter Datentyp 258 Abstraktion 219,288 Abstraktionsmittel 109 ACCESS 26,214 ACTIVE 274 Adressat 306 AFFIX-Empfehlungen 311 after daemon 297 Aktionsteil 58 Akumulator 75 ALGOL 239 Algorithmus 13 ALIAS-Name 318 Aliasname 30 ALL-CLASSVARS 296 ALL-METHODS 296 Allegro Common LISP 390 allocation 385 Alternative 11 AND 34,63 anonyme Funktion 22 APPEND 98 Applikation 19,21,211 APPLY 23 Arbeits technik 331 Argument 17, 20, 23 array 167 ASCII 235 ASCII->SYMBOL 235 Assembler 233 assignment 29 ASSOC 143 Assoziation 12,26 ASSQ 143 ASSV 143 Attribut 141,240 Aufgabenart 331 Aufgabengröße 331 Ausgeglichenheit: 173 Ausrufezeichen 25 Auswertungsblocker 14 AVL-Baum 173 A*-Verfahren 159

Backus-Naur-Form 47 balanced tree 173 BAR 27 Bauart 105 Baukasten-Konstruktionsprinzip 303, 308 Baumstruktur 92,94 Baustein 19 BAZ 27 BBN LISP 386 Bedienungskomplexität 335 Bedingte Schleife 71 Bedingungsteil 58 before daemon 297 BEGIN 55 BEGIN0 57 begrenzte ET 59 Begriffslexikon 323 Benchmark 387 Benennung 22 Benennungsoperator 12 Benutzerfreundlichkeit 335 Berechnungszeitpunkt 76 binary predicate 62 Binärbaum 94 Bindung 29,53 Bindungsstrategie 212 Blank 188 BNF-Metasprach-Gleichheit 47 bottom up approach 298 bottom-element 216 47 Buchungsprogramm 14 BYSO LISP 390 C...R 39 C++ 109 CADR 39 CALL-WITH-CURRENT-CONTINUATION 213,227 CALL/CC 227 Cambridge LISP 390 CAND 63 CAR 39, 48, 214 CASE 65,360 CDDDR 39

D: Schlagwörter CDR 39, 48, 214 CDR-Codierung 385 CGA 345 CGA-Modus 344 CHAR=? 189 CHAR? 188 character 4, 9, 188 CLASS COMPILED 284 CLASS INHERITED 285 class precedence list 288 CLASS-OF-OBJECT 278 CLASS? 277 CLASSVARS 274,296 CLOS 271,274,282,292,388 closure 30,212 CLOSURE-Konzept 30 COBOL 2, 13, 109, 258-259 Common LISP 3, 17, 22, 127, 320, 386 Common LISP Object System 271 Common LOOPS 271 compiler 251 COND 37, 48, 65 CONS 40,48 CONS-Zelle 42,384 continuation 227 control structure 45 COPY 140,245 COR 63 Cray 389 Ctrl 394 Curryfizieren 220 currying 221 Cursor 344 Datenlexikon 141 Datenreprasentation 344 Datenstruktur 258 DE 22 deallocation 385 decision table 58 deep access 212 default value 142, 261 DEFINE 12, 22-24, 26, 48 DEFINE-CLASS 274 DEFINE-INTEGRABLE 262 DEFINE-METHOD 274-275 DEFINE-STRUCTURE 261,344, 353 Definionsmenge 46 Definitionsumgebung 215

399 DEFSTRUCT 261 DEFUN 22,320 deklarative Methodenkombination 274 DELQ! 151 Denkrahmen 258,260 depth-first search 153 Destruktive Konstrukt 25 DIN66261 11,71 Diskriminator 43 dispatch function 354 dispatch procedure 344 DO 66-67, 107 Documentation string 320 Dokumentation 303, 305, 308 Dokumentationsrichtlinie 324 Dokumentationsstrategie 305 Dokumentnummer 325 dotted pair 40, 143 downward funarg 31 Do What I Mean 388 DRAW-LINE-TO 344-345 Durchschaubarkeit 335 dynamische Vererbung 269, 282 dynamische Bindung 31 EDWIN 6,394 Effizienz 251,280 EGA 344 Eigenschaftsliste 145, 147, 314 Eindeutigkeit 323 einfache Fall 86 einfache Vererbung 290 Eingabe-Schnittstelle 16 Einkapselung 280 Eintreffer-ET 59 elementare Konstrukte 108 Embryonal-Fall 85 encapsulation 280 Entscheidungstabelle 58 Entwicklungsfähigkeit 34 ENVIRONMENT-BINDINGS 224 ENVIRONMENT-PARENT 237, 244 EQ? 41-42, 129 EQUAL? 41-42 Ergonomie 335 Erkennungskonstrukt 113 Ersatzwert 142 Esc 394 ET 58, 62, 78-79, 91-92

400 EuLISP 386 EVAL 8, 15, 19, 26, 48 EVAL-Regel 1 9 EVAL-Regel 2 10 EVAL-Regel 3 11 EVAL-Regel 4 12 evaluation function 159 Evolutions-Konzept 333 EXPAND-MACRO 52, 69, 234, 236, 262, 266 EXPAND-MACRO-1 283 Exper LISP 390 EXPLODE 196 Explorer 387 Faktorisierung 284, 293, 296-297 Fallunterscheidung 37,396 Feld 267 Feldwert 261,267 Fernwirkung 280 First-class-Objekt 49 Fixpunktoperator 219 Flavor 109,271 Flavor-System 288 Flexibilität 335 flow of control 46 Fluchtsymbol 195,317 fluid binding 31 FOO 27 FOR 66 FOR-EACH 74 form 20 formaleParameter 18 FORTRAN 109 FP 212 Fragezeichen 43, 186 Fraktale 360 Fraktalfigur 360 Fraktallinie 361 Franz LISP 386, 390 FUNARG-Problem 30,31 Funktion 10,219 Funktion höherer Ordnung 74, 354 Funktionale Anforderung 333 Funktionsapplikation 311 Funktionsdefinition 22 Funktionsgeprägte Konstruktion 310 Funktionsname 312-313 Funktionsobjekt 21

Anhang garbage 49 garbage collection 384, 385 GENSYM 203 GETCV 296 GETPROP 147 GETTABLE-VARIABLES 275 Gleichheit 41, 143 Gleitkommazahl 41 Glossary 323 GO 112 Golden Common LISP 391 Graphik 397 Graphikmodus 344 Groß/Kleinschreibung 316 Grundprozedur 344 Handhabbarkeit 324,335 Heap 385 heap maintenance 384 Hierarchie von Konstrukten 44 higher-order function 74,213 HOPE 212 IBUKI Common LISP 391 Identifier 325 Identität 41, 143 IF 11,37 IMPLODE 196 Imperativgeprägte Konstruktion 310 imperativgeprägte Paradigma 258 Implementation 18,212 INCLUDE 265,269 infinite Sequenz 129 Initialisierungswert 276 INITTABLE-VARIABLES 275 Input-/Output-Interface 396 INSPECT 394 INSTANCE? 278 Instanzvariable 285 Instrument 331 INSTVARS 274 interface 337 InterLISP 386 InterLISP-D 391 Internmachen 202 IPL 2 IQ-CLISP 391 Irrelevanz-Zeichen 60

401

D: Schlagwörter Iteration

46, 67, 71, 73, 396

Jackson-Notation 50,67 Julia-Menge 360 Klammernzählen 6 Klassen-Instanz-Modell 271 Klassentyp 288 Klassenvariable 285 Kleinbuchstaben 195 Komfort 335 Komma 234 Kommentar 47,321 Kommentarzeile 307,319 Kommunikation 1,273 Komponenten-Flavor 291 Komposition 46, 109, 212, 226 Konsistenz 323 Konstante 15 Konstituenten 105 Konstrukt 18-19 Konstruktion 33, 41, 105, 305 Konstruktions-Dokumentation 305 Konstruktions-Prozeßdokumentation 305 Konstruktionsaspekte 34 konstruktionsbedingter Aspekt 309 Konstruktionsgröße 332-333 Konstruktions Vorschrift 105 Konstruktor 40,110,395 Kontrollstruktur 226 KoopA-ADV-Rahmenrichtlinien 308 Koordinatensystem 345 Kyoto Common LISP 391 LAMBDA 26, 48, 52, 56, 86, 175 LAMBDA-Abstraktion 311 LAMBDA-Variable 20, 22, 53 Lebenszyklus 34, 45, 330 Lesbarkeit 316 LET 52 LETREC 55,218 LET* 54 lexikalische Bindung 31,215,386 Lexikon 314-315 Le LISP 391 LIFO-Prinzip 58 Linguistik 105

LISP 1.5 385 LISP 1.6 386 LISP-Dialekte 3 LISP-Maschine 387 LlSP-Prozessorkarte 388 LIST 28,48 LIST->VECTOR 168 LIST-REF 96, 142 Liste 4 , 4 1 , 1 2 3 47 Literalatom 4 LOC 334 local precedence order 292 local slot 285 LOGO 345 LOOP 112 • Lucid Common LISP 392 Maclvory 388 MacLISP 320,386 MACRO 235,283 Mainframe 387-388 MAKE- 261 MAKE-INSTANCE 274-275 MAKE-STRING 192 MAKE-VECTOR 168 Makro 52, 233-234, 245, 261, 314, 348 Mannjahre 334 MAP 72,74 MAPCON 74 MAPLIST 74 mapping function 72 match variable 185, 195 Mehrfachverwendbarkeit 308 Mehrtreffer-ET 325 MEMBER-OF-CLASS? 278 message handling 274 message passing 279 Meta-Klasse 288 Methode 331 methods 273,296 microExplorer 388 MIT 2,386 MIXINS 274, 284, 291 MJ 334 ML 109,212 mnemotechnischer Aspekt 309 Modifizierbarkeit 323 Modul 45,337 Modularisierung 45,50

402

Anhang

muLISP 392 multiple values 17 multiple Vererbung 269, 288 Multiplizität 124, 145 Mustervariable 195 Mustervergleich 188, 195 Mutator 110,127,395 Nachfolgeknoten 282 Nachricht 273 Nachrichtenübermittlung 273 name 309-310 NAME->CLASS 296 Namenslänge 311 Namensraum 239 Nassi/Shneiderman 11, 71, 90, 344 Nebeneffekt 17-18, 149 Negation 63 New Flavors 293 NichtUnterscheidbarkeit 41 NIL 4, 37, 42, 386 NIL-Zeiger 128 Nil's LISP 392 nonterminal symbols 47 nonterminals 47 NOT 63 Notation 8 NTH 96 NULL? 63, 85, 216 47 Nutzungsflexibilität 335 Oberklasse 269 Objekt 49, 142 Objektgeprägte Konstruktion one-based 96 Operanden 36 Operatoren 36 OPS5 304 Optimierungsaufgabe 384 OPTIONS 274,276 OR 63 Orientierungspunkte 36 # 20 P-Liste 147 PAIR? 215 Paket 239 Paradigma 107, 258, 309

Parallelverarbeitung 366 PARC 387 Pascal 109 pattern matching 185 PC Scheme 392 PCS *PRIMOP-HANDLER 262 pending class 285 Personalcomputer 387,389 Pflichtenheft 308 Portable Standard LISP 386 POSITION-PEN-Konstrukt 345 Power LISP 392 PP-Konstrukt 317 Prädikat 41,110,263,396 Präfix-Notation 10,21 Precompiler 185 Pretty-Print 317 primary method 297 primitive 108,344 PRINT 8 PRINT-Name 185, 195 Prinzip 331 Prioritätenfolge 292 Problemart 333 Produktionssystem 304 Programmierassistent 388 Programmieren im Großen 45 Programmierparadigma 107 Programmierung im Kleinen 45 Propagieren 269,295 PROPLIST 147 Pseudocode 304 PSL 386,392 Punkt-Paar 40, 176 Pure LISP 3, 39, 100, 211, 212, 386 PUTPROP 147

310 QLET 367 QLISP 367 Qualität 34,335 QUASIQUOTE 233-234, 348, 355 Quellcodetext 334 QUOTE 14, 48, 233 quote mark 15 Quotierung 15 RANDOM 71 READ 8 READ-EVAL-PRINT-Zyklus 12, 25, 129, 195, 233

8-9,

D: Schlagwörter READ-Makro 233 READ-Phase 233 20 recognizer 113 referential transparency 212 Regel 58 Regelanzeigerteil 59 Reichweite 320 Reihenfolge 124 Reinschrift 305 Rekursion 66,361 Rekursions-Route 85 Rekursionstiefe 86 rekursive Problemlösungs-Methode 89 REMPROP 147 REPEAT 66 Repetition 66 Repräsentation 15 Restrekursion 86-87 RETURN 112 REVERSE 97 Robustheit 335 ROUND 74 RPLACA 127 RPLACD 127 RUNTIME 251 Rückgabewert 281 Rückübersetzung 251 #\SPACE 188 Schachtelung 212 Scheme 3,386 Scheme-Konstrukte 395 SCHEME.INI 394 Schildkröte 345 Schneeflocke 361 Schnittstelle 17-18, 20, 175, 337 SCOOPS 272, 274, 344, 346 SCOOPS.FSL 274 Selbstähnlichkeit 360 Selbstbezug 85,220 Selbstübergabe 220 SELECTQ 65 Selektion 46 Selektionswert 144 Selektor 39, 110, 263, 395 SELF 283 Semantik 85 Semikolon 47,319 SEND 272, 274, 276, 280, 346

403 SEND-IF-HANDLES 296 SEND-WITH-OBJECT 283 Sequenz 46, 50, 54, 226, 396 SET! 25,27,48,128,243 SET-CAR! 126,216 SET-CDR! 126 SET-VIDEO-MODE! 344 SETCV 296 SETQ 12 SETTABLE-VARIABLES 275 47-48 shallow access 212 shared slot 285 slots 147, 261, 273 SmallTalk 109,288 Softwarekonstruktion 303 SoftWave LISP 393 47 SORT! 245 Sortierbarkeit 316 Sozialverträglichkeit 34 Space 188 Spezialisierung 284, 293, 297 splicing 235 sprachliche Einheit 105 Stack 58,224 Standard LISP 386 statische Bindung 31 Stellungsparameter 22 Stern 186 Steuerungsstruktur 46 *THE-NON-PRINTING-OBJECT* 96 string 9, 184 STRING->SYMBOL 204 STRING->UNINTERNED-SYMBOL 203 STRING-APPEND 190 STRING-CI=? 177 STRING-LENGTH 188 STRING-NULL? 188 STRING-REF 188 STRING=? 188 STRING? 188 - 261 ? 261 Struktogramm 11, 71, 90, 346 Strukturcodierung 385 strukturierte Programmierung 319 Subklasse 297 Subliste 144 SUBSTRING 190

404 Supercomputer 387,389 Superklasse 269, 284, 288 47 symbolic expression 4 Symbolics 387,390 Symbolic Processor 387 symbolische Ausdruck 38 syntaktische Erleichterung 348 Syntax 5, 85, 233, 245 System 333 T 386 T-Klausel 38 tail-recursion 86 Term 20 terminal symbols 47 terminals 47 Terminologie 306 Texas Instruments 387 THE-ENVIRONMENT 214,241 Thunk 76, 176 Tiefensuche 153 Tiefensuche-Algorithmus 291 TLC-LISP 386,393 top down approach 298 TRACE 87 TRACE-BOTH 87 TRACE-ENTRY 87 Transparenz 309,319 Turtle-Graphik 345 #!UNASSIGNED 26 UCI LISP 386, 393 Umfangreduktion 307 Umgebung 26 UNQUOTE 233 unary function 46, 74 Unbenutzbarkeit 49 UNBIND 243 Ungleichheit 41 uninterned symbol 203, 208 UNQUOTE 234, 348, 355 UNQUOTE-SPLICING 233,355 UNTIL 66 until-Konstrukt 71 upward funarg 31 USER-GLOBAL-ENVIRONMENT 26 USER-INITIAL-ENVIRONMENT 26, 240

Anhang überschatten 284 Übertragbarkeit 34 value cell 147 Variable 21 VECTOR 168 VECTOR->LIST 168 VECTOR-LENGTH 168 VECTOR-REF 168 VECTOR-SET! 169 VECTOR? 169 Vektorcodierung 385 Vererbung 265 Vererbungsbaum 288 Vererbungsgraph 268,282 Vererbungsrichtung 268 Verifizierbarkeit 323 Verkettung 46 Verknüpfungsmittel 109 Verkrumpelung 360 Verkürzungsvorteil 318 VGA 344 Vollständigkeit 60,323 Vollzugsproblem 333 Vorgehensweise 331 Vorübersetzer 185 Werkzeug 331 Wert 17, 142 Wertemenge 46 Wertermittlung 54 Wertzelle 147 WHILE 66 while-Konstrukt 71 whopper 297 Widerspruchsfreiheit 323 Wiederholungs-Bedingung 71 Wiederverwendung 303 WITH-INPUT-FROM-FILE 176 WITH-OUTPUT-TO-FILE 176 WordStar 394 Workstation 387,389 WRITELN 44,50 Xerox XLISP Zahlatom

387 393 4, 42, 48

D: Schlagwörter 47 Zeichen 4,9 Zeichenkette 4, 184 ZEITMESSUNG 251 Zerlegungsgrad 114 zero-based 96 ZetaLISP 386,393 47 zirkuläre Liste 129 Zugriffsoperation 267 Zurückführbarkeit 324 Zweckausrichtung 308

Zwischenraum

188

' 233-234, 348, 355 { - } 47 [-.] 47 , 233, 348, 355 ::= 47 ; 47 @ 233,283 355

Programmierung komplexer Systeme Herausgegeben von Fevzi Belli und Hinrich Bonin Die Reihe beschäftigt sich mit Realisierungs- und Zuverlässigkeitsaspekten komplexer Software-Systeme. Den Schwerpunkt bilden konkrete Handlungsgrundsätze und implementierungsnahe Techniken fur Informations- und wissensbasierte Systeme. Dabei rücken Probleme bei der Modellierung, Analyse, Konzeption, Konstruktion und Validation komplexer Systeme in den Mittelpunkt, vor allem im Hinblick auf Programmierungsaspekte. Eine gemeinsame Eigenschaft der Einzelbände ist ihre Innovationskomponente. Diese Komponente wird auch durch die Einbeziehung von Werken interdisziplinären Charakters gewährleistet. Konkrete Anwendungssysteme, insbesondere aus technischen Bereichen einschließlich Büro- und Verwaltungautomation, vermitteln den Praxisbezug. Band 2 Günter Becker

Softwarezuverlässigkeit Quantitative Modelle und Nachweisverfahren

15,5 x 23 cm. IX, 162 Seiten. Mit 35 Abbildungen. 1989. Gebunden. ISBN 3-11-012227-8 Band 4 Horst Zuse

Software Complexity Measures and Methods

17 x 24 cm. XVI, 605 Seiten. Mit 498 Abbüdungen. 1991. Gebunden. ISBN 3-11-012226-X Band 5 Ulrich Hoffmann

Einführung in die systemnahe Programmierung Anwenderprogramme und Datenstrukturen

15,5 x 23 cm. XII, 371 Seiten. Mit 136 Abbüdungen. 1990. Paperback. ISBN 3-11-012466-1 In Vorbereitung Band 3 Günter Matthiessen

Logik für Software-Ingenieure 15,5 x 23 cm. Ca. 270 Seiten. 1991. Paperback. ISBN 3-11-012228-6

WALTER D E GRUYTER • BERLIN • NEW YORK Genthiner Straße 13, D-1000 Berlin 30, Tel.:(030)26005-0, Fax:260-05-251

Studien zur Wirtschaftsinformatik Herausgegeben von Karl Kurbel, Uwe Pape, Hans-Jochen Schneider Die Wirtschaftsinformatik steht als interdisziplinäres Fachgebiet zwischen den Wirtschaftswissenschaften, insbesondere der Betriebswirtschaftslehre, und der Informatik. Sie behandelt die Entwicklung und den Einsatz computergestützter Anwendungssysteme in Unternehmen und Verwaltungen. Neben Systemen der operativen Ebene gehören dazu Planungssysteme und Entscheidungsunterstützungssysteme ebenso wie inner- und zwischenbetriebliche Informations-und Kommunikationssysteme. Ziel dieser Buchreihe ist es, den „State of the art" darzustellen, die Forschung zu fordern, die Lehre zu unterstützen und zu einer Strukturierung des Wissensgebiets beizutragen. Die Bände der Reihe wenden sich an Wissenschaftler, Entwickler, Anwender und an Studenten der Wirtschaftswissenschaften, der Informatik und der Wirtschaftsinformatik. Band 1 Detlef Karras/Lutz Kredel/Uwe Pape

Entwicklungsumgebungen für Expertensysteme Vergleichende Darstellung ausgewählter Systeme 15,5 x 23 cm. X, 217 Seiten. Mit einigen Abbildungen. 1987. Gebunden. ISBN 3-11-011294-9 Band 2 Lutz Kredel

Wirtschaftlichkeit von Bürokommunikationssystemen 15,5 x 23 cm. XVI, 368 Seiten. Mit 48 Abbüdungen. 1988. Gebunden. ISBN 3-11-011767-3 Band 3 Karl Kurbel/Peter Mertens/August-Wilhelm Scheer (Herausgeber)

Interaktive betriebswirtschaftliche Informationsund Steuerungssysteme 15,5 x 23 cm. VI, 354 Seiten. Mit 115 Abbüdungen. 1989. Gebunden. ISBN 3-11-012100-X Band 4 Peter Pfeiffer

Technologische Grundlage, Strategie und Organisation des Informationsmanagements 15,5 x 23 cm. XV, 337 Seiten. Mit 91 Abbüdungen. 1990. Gebunden. ISBN 3-11-012362-2

WALTER DE GRUYTER • BERLIN • NEW YORK Genthiner Straße 13, D-1000 Berlin 30, Tel.: (0 30) 2 60 05-0, Fax: 2 60-05-2 51