218 43 25MB
German Pages 420 [424] Year 1991
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