239 88 17MB
German Pages 270 [272] Year 1977
de Gruyter Lehrbuch Giloi * Programmieren in APL
Wolfgang K. Giloi
Programmieren in APL
w DE
G
Walter de Gruyter • Berlin • New York 1977
Dr. Wolfgang K. Giloi Professor am Department of Computer Science der University of Minnesota in Minneapolis/USA
Für Bettina,
Claus und Eva
Mit 17 Abbildungen, 19 Tabellen und zahlreichen Übungsaufgaben
CIP-Kurztitelaufnahme
der Deutschen
Bibliothek
Giloi, Wolfgang Programmieren in APL. 1. Aufl. - Berlin, New York: de Gruyter 1977 (de Gruyter Lehrbuch) ISBN 3-11-05724-7
© Copyright 1976 by Walter de Gruyter & Co., vormals G. J. Göschen'sche Verlagshandlung, J. Guttentag, Verlagsbuchhandlung Georg Reimer, Karl J. Trübner, Veit & Comp., Berlin 30. Alle Rechte, insbesondere das Recht der Vervielfältigung und Verbreitung sowie der Übersetzung, vorbehalten. Kein Teil des Werkes darf in irgendeiner Form (durch Photokopie, Mikrofilm oder ein anderes Verfahren) ohne schriftliche Genehmigung des Verlages reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden. Printed in Germany. Satz: Walter de Gruyter, Berlin. - Druck: Karl Gerike, Berlin. - Bindearbeiten: Dieter Mikolai, Berlin. - Printed in Germany
Vorwort
Dieses Buch unterscheidet sich in verschiedener Hinsicht von der bisherigen Literatur über APL. Es führt in die Programmiersprache ein, ist dabei aber mehr als eine reine Programmieranleitung. Selbstverständlich werden die syntaktischen Grundelemente der Sprache, die Definition und Anwendung ihrer Operatoren, die Prozedurkonstruktionen usw. aufgezeigt, und das Programmieren in APL wird exemplarisch vorgeführt. Darüber hinaus wird aber auch gezeigt, welche speziellen Konzepte den Konstruktionen von APL zugrunde liegen und welchen Platz diese Sprache im Spektrum der höheren Programmiersprachen einnimmt. Das Buch ist daher vor allem von Interesse für Informatiker, studierende wie „praktizierende", darüber hinaus aber auch für Ingenieure, die sich für APL als einer Sprache für technisch-wissenschaftliche Anwendung — insbesondere in den Bereichen des rechnerunterstützten Entwerfens — interessieren, für Wissenschaftler, die APL als eine Sprache für das rechnerunterstützte Lehren und Lernen anwenden wollen, und nicht zuletzt auch für die Programmierer von betrieblichen Informationssystemen, für deren Realisierung APL in steigendem Maße herangezogen wird. Der Text stützt sich auf Vorlesungen über APL, die der Verfasser in den Jahren 1970—1976 gehalten hat, sowie auf die Erfahrungen, die er selbst als Anwender auf den Gebieten des Entwurfs logischer Systeme, der Beschreibung und Simulation von Computer-Algorithmen und Computer-Komponenten, des Computer Graphics und der digitalen Signalverarbeitung gewonnen hat. Der Lehrbuch-Charakter äußert sich zum einen in den sorgfältig ausgewählten Aufgaben und den Lösungen dieser Aufgaben, die den einzelnen Kapiteln beigefugt wurden: Einen Programmierstil kann man nicht nur beschreiben, man muß ihn auch vorführen. Er äußert sich zum anderen aber auch in der ausführlichen Darstellung der zugrunde liegenden Konzepte. Einige Betrachtungen sind hier neu und nicht in der bisherigen Literatur zu finden. So führen wir APL primär als eine Operatorensprache ein, deren Objekte geordnete Mengen sind. Durch diese Anschauungsweise klärt sich die auf den ersten Anschein so willkürlich und verwirrend erscheinende Fülle der Operatoren. Erstmalig wird auch explizit aufgezeigt, von welcher Natur der Prozedurmechanismus in APL ist, welche Möglichkeiten der Parameterübergabe bestehen, worin sich die innere Informationsstruktur von APL entscheidend von der anderer Sprachen unterscheidet, und welches die besonderen Stärken, aber auch die besonderen Schwächen und Gefahren dieser Sprache sind. Und erstmalig wird auch dargestellt, wie der Benutzer die Sprache durch eigene Kontrollstrukturen ergänzen kann, so daß dadurch die Methoden des strukturierten Programmierens auch auf APL anwendbar werden.
VI
Vorwort
Wir hoffen, damit ein Lehrbuch geschaffen zu haben, das auch zum Selbststudium geeignet ist und das nicht nur die Kenntnis einer der interessantesten Programmiersprachen vermittelt, sondern auch zu einem erweiterten Verständnis der Vielfalt höherer Programmiersprachen beitragen will. Selbstverständlich kann es auch immer dem APL-Programmierer als Nachschlagewerk dienen. Bei den Programmierbeispielen und -aufgaben haben wir der Versuchung widerstanden, durch exotische Kombinationen der von Natur aus schon sehr mächtigen APL-Operatoren möglichst komplizierte Operationen durchzufuhren und dadurch den Leser zu verunsichern. Auf der anderen Seite wird der Leser aber auch selten die Beispiele finden, die zum Standard in vielen Büchern über das Programmieren geworden sind. Dies liegt einmal daran, daß viele schon relativ komplexe Programme in anderen Sprachen in APL durch eine Elementaroperation oder durch eine einfache Kombination von wenigen Elementaroperationen ausgeführt werden können. Es liegt zum anderen aber auch daran, daß die wirklichen Aufgaben in der Programmierung kaum im Auffinden des größten gemeinsamen Teilers zweier ganzer Zahlen oder ähnlichem bestehen. Dementsprechend konzentrieren sich unsere Beispiele auf Textverarbeitung, Bildverarbeitung, schnelle Fouriertransformation, symbolisches Differenzieren, Aufbau von Datenstrukturen und manchem anderem. Mögen diese Beispiele den Leser zum eigenen Experimentieren anregen (insbesondere die Mächtigkeit des BERECHNEN-Operators eröffnet ein weites Feld fur ein Experimentieren in APL). Meinem Mitarbeiter, Herrn Dipl. Inf. Helmut Berg, bin ich zu großem Dank für das sorgfältige Lesen des Manuskripts und die vielen fruchtbaren Diskussionen verbunden. Ebenso danke ich Herrn dipl. math. Horst Seyferth fur die Hilfe beim Korrekturlesen sowie dem Verlag fur die ausgezeichnete Zusammenarbeit. Minneapolis, Frühjahr ] 976
Wolfgang K. Giloi
Inhalt
1. Einleitung 1.1 Was ist APL? 1.2 Ziel dieses Buches 1.3 Metasprachliche Notationen
11 11 14 17
2. Zeichensatz, Namen, Datentypen und Konstanten 2.1 Der APL-Zeichensatz 2.1.1 Buchstaben 2.1.2 Ziffern 2.1.3 Operationszeichen und Begrenzerzeichen 2.1.4 Anweisungszeichen 2.1.5 Sonderzeichen 2.2 Namen 2.3 Datentypen 2.4 Konstanten
18 18 18 18 19 20 20 21 22 24
3. Variablen, Datenstrukturen, Ausdrücke, Darstellungen 3.1 Sprach-Objekte, Informationsbindung und Wertebereiche 3.2 Datenstrukturen 3.3 Ordnung und Struktur von Mengen in APL 3.4 Indizierung von Variablen und Operatoren 3.5 Ausdrücke 3.6 Darstellungen Aufgaben zu Kapitel 3
25 25 27 30 32 35 39 41
4. Elementarfunktionen: Skalar-Operatoren, Ein/Ausgabe-Operatoren und Abfrage-Operatoren 4.1 Vorbemerkungen 4.2 Monadische Skalar-Operatoren 4.3 Dyadische Skalar-Operatoren 4.4 Ein/Ausgabe-Operatoren und Abfrage-Operatoren 4.4.1 Eingabe eines Ausdrucks 4.4.2 Ausgabe des Wertes eines Ausdrucks 4.4.3 Abfragefunktionen Aufgaben zu Kapitel 4
43 43 47 49 54 54 56 56 58
5. Elementarfunktionen: Struktur-Operationen 5.1 Feld-Operatoren
61 61
VIII
Inhalt
5.1.1 Reduktion 5.1.2 Verallgemeinertes Inneres Produkt 5.1.3 Äußeres Produkt 5.1.4 Beispiele für Innere und Äußere Produkte 5.1.5 Matrizen-Inversion und Matrizen Division 5.2 Generator-Operatoren 5.2.1 Indexgenerator 5.2.2 Zufallszahlen-Generator 5.3 Selektions-Operatoren 5.3.1 Indizierung 5.3.2 Aufreihung 5.3.3 Strukturieren 5.3.4 Katenation und Lamination 5.3.5 Entnehmen 5.3.6 Entfernen 5.3.7 Komprimieren 5.3.8 Expandieren 5.3.9 Umkehrung 5.3.10 Rotation 5.3.11 Monadische Transposition 5.3.12 Dyadische Transposition 5.4 Relations-Operatoren 5.4.1 Indexmengenbildung 5.4.2 Zugehörigkeit 5.4.3 Aufwärtssortieren 5.4.4 Abwärtssortieren 5.5 Konversions-Operatoren 5.5.1 Entschlüsseln 5.5.2 Verschlüsseln 5.5.3 Berechnen 5.5.4 Formatieren Aufgaben zu Kapitel 5
62 66 69 70 72 74 74 75 76 76 79 79 80 85 87 87 89 90 90 92 93 96 96 97 98 99 99 100 102 104 104 106
6. Anweisungen 6.1 Rechenanweisung und Ausführungsanweisung 6.2 Deklarationen 6.3 Zuweisungen und Zuweisungsketten 6.4 Eingabeanweisungen und Ausgabeanweisungen 6.5 Betriebsarten-Umschaltung und Definitionsmodus 6.6 Sprunganweisungen 6.7 Stop-Anweisung und Spur-Anweisung 6.8 Unterbrechungs-Anweisungen Aufgaben zu Kapitel 6
109 109 111 114 115 120 121 127 129 130
Inhalt
IX
7. Prozeduren 7.1 Prozedur-Typen 7.2 Parameterübergabe 7.3 Die Syntax der Funktionsdefinition 7.4 Funktions-Schachtelungen und der Gültigkeitsbereich von Namen 7.5 Rekursiver Funktionsaufruf 7.6 Der Status-Indikator 7.7 Das Editieren von Funktionen 7.7.1 Ausschreiben einer Funktions-Definition 7.7.2 Das Editieren von definierten Funktionen 7.7.3 Das Editieren von Programmzeilen 7.8 Fehlerdiagnose und Fehlerkorrektur Aufgaben zur Kapitel 7
133 133 135 140 144 147 151 155 155 159 160 163 167
8. Die Verwaltung des Arbeitsbereichs, Systemkommandos und Systemfunktionen 8.1 Der aktive Arbeitsbereich und seine Verwaltung 8.2 Der Arbeitsbereich CONTINUE und die Beendigung einer Sitzung 8.3 Bibliotheken und das Kopieren von Objekten 8.4 Kommando-Klassen und Fehlerdiagnosen 8.5 Systemfunktionen Aufgaben zu Kapitel 8
171 171 178 180 184 187 193
9. Dateien 9.1 Felder als Datenstruktur 9.2 Das Listenverwaltungs-System von APLSV 9.3 Die Programmierung eines Systems zur Listenverwaltung Aufgaben zu Kapitel 9
195 195 200 203 206
10. Strukturiertes Programmieren in APL 10.1 Die Struktur von APL-Programmen 10.2 Die Optimierung von APL-Programmen 10.3 Eine moderne Kontrollstruktur für APL 10.3.1 REPEAT-UNTIL 10.3.2 FOR 10.3.3 Die DAHL-Konstruktion 10.3.4 IF-ELSE und CASE 10.3.5 Initialisierung und Benutzung der Kontroll-Konstruktionen 10.4 Coroutinen in APL Aufgaben zu Kapitel 10
210 210 216 219 219 220 221 223 225 226 233
x
Inhalt
Anhang A. 1 Der APL-Zeichensatz und seine mnemonische Ersetzung A.2 Lösung der Programmierungsaufgaben
239 239 240
Literatur
265
Sachregister
267
„Willst Du in's Unendliche schreiten, gehe nur im Endlichen nach allen Seiten." J. W. von Goethe
1. Einleitung
1.1 Was ist APL? APL steht als Abkürzung für A Programming Language und bezeichnet eine Sprache, die von Kenneth E. Iverson definiert und 1962 in seinem gleichnamigen Buch erstmalig veröffentlicht wurde [17]. Allerdings handelte es sich bei APL, so wie es dort eingeführt wurde, trotz des im Titel des Buchs erhobenen Anspruchs noch nicht wirklich um eine Programmiersprache, sondern zunächst um ein formales System, gegeben in der Form einer Operatorensprache. Erst in den folgenden Jahren wurde dann APL zu einer Programmiersprache ausgebaut. Diese Entwicklung führte zu APLN360, ein System, das 1966 verfügbar wurde, zunächst auf einer experimentellen Basis und später als offizielles IBM-Programmprodukt. Inzwischen hat sich APL in den USA und Kanada als eine der hauptsächlich verwendeten Programmiersprachen (nach FORTRAN, COBOL und PL/1) durchgesetzt. APL-Übersetzer gibt es für die Rechnerfamilien aller namhaften Hersteller. APL ist die vorwiegend verwendete Sprache in allen großen Timesharing-Netzen, die in den USA als Dienstleistungsunternehmen das Land überziehen. Neuere APL-Systeme wie APL*PLUS der Scientific Time Sharing Corporation and APLSV der IBM haben einige der Schwächen beseitigt, die dem Einsatz von APL als universeller Programmiersprache entgegenstanden. APL als formales System wurde von Iverson mit dem Anspruch eingeführt, gewisse Defekte der Vektor-Algebra zu beheben. Zu diesen Defekten zählt Iverson vor allem eine unsystematische Notation, aber auch die Tatsache, daß die gewohnte algebraische Notation nicht algorithmisch ist, daß Zuweisungen und Verzweigungsanweisungen fehlen [18], Mit diesem Anspruch steht Iverson allerdings ziemlich allein da. Mathematische Systeme sind ihrer Natur nach statisch; und der für Computer-Algorithmen so zentrale Begriff des Ablaufs fällt in eine andere Kategorie. Als außerordentlich tragfähig hat sich hingegen der von Iverson eingeführte Feldkalkül erwiesen, der zum Teil in einer Erweiterung der Definitionsbereiche von Operatoren der gewöhnlichen Algebra auf Felder beliebigen Rangs, zum Teil auch in einer Verallgemeinerung von Operationen der Vektor- und Matrix-AI-
12
1. Einleitung
gebra besteht. Diese Operationen haben sich allgemein als ein sehr wirkungsvolles und mächtiges Werkzeug in technisch-wissenschaftlichen Anwendungsbereichen bewährt. Speziell in der Informatik hat sich der APL-Formalismus als eine der vielseitigsten und leistungsfähigsten Systembeschreibungssprachen erwiesen, in der sich Verhalten und Struktur von Computer-Hardware, Mikroprogrammen, Betriebssystemen, usw. auf sehr prägnante Weise formal beschreiben läßt. Darüber hinaus läßt sich auf dem APL-Kalkül eine geschlossene Entwurfsmethodik für logische Systeme aufbauen [14], die nicht nur die bisher geübten Verfahren stärker systematisiert, sondern auch manche Einsichten vermittelt, die durch andere Methoden kaum zu erlangen sind. Die Fülle der verschiedenen Feldoperatoren und die scheinbare Willkürlichkeit mancher Definitionen mögen den Leser zunächst verwirren. Man gewinnt aber sehr schnell ein klares Bild, wenn man von dem Grundmodell ausgeht, wonach es sich bei den Objekten der APL-Feldoperatoren um geordnete Mengen handelt [24]. Die Operatoren selbst lassen sich dann zu den bekannten Mengenoperationen in Zusammenhang bringen und damit auf natürliche Weise kategorisieren. Von diesem Modell ausgehend, entdeckt man sehr schnell Operationen für Vereinigung und Durchschnitt, für die Untermengenbildung, für die Zerlegung und Ordnung aufgrund bestimmter Relationen und für spezielle kartesische Produkte. Eine Variable ist in APL wie in anderen Programmiersprachen ein (Name, Wert)-Paar. Im Gegensatz zu anderen Sprachen ist der Wert im allgemeinen aber keine skalare Größe, sondern wiederum als ein Paar (Ordnungsfunktion, Daten) gegeben (s. Abschnitt 3.1). Dementsprechend finden wir in APL neben den Operatoren, die neue Daten (und damit auch neue Strukturen) erzeugen, auch Operatoren, die nur neue Strukturen durch Umordnung bestehender Datenmengen bilden, etwas, was man in den üblichen anderen Programmiersprachen nicht findet. Merkwürdigerweise findet sich eine solche klärende Kategorisierung in der bestehenden APL-Literatur höchstens in Ansätzen und in der Lehrbuchliteratur gar nicht. Die übliche Einteilung der APL-Operatoren in skalare, zusammengesetzte und gemischte Funktionen ist nichtssagend. Wir glauben daher, mit der in diesem Buch erstmalig gegebenen Klassifizierung nach funktionellen Gesichtspunkten einen Beitrag zum besseren Verständnis des formalen Systems von APL leisten zu können. Das Konzept der geordneten Menge als elementarer Variablen mag sich in Zukunft als ein tragfähiges Prinzip erweisen, durch das die bestehende Diskrepanz zwischen den jetzt gegebenen technischen Möglichkeiten der Großintegration von Computer-Schaltkreisen und dem diesen nicht mehr angepaßten von Neumann'schen Grundprinzip der Rechnerorganisation beseitigt werden kann. Das geniale Prinzip des von Neumann-Rechners garantiert größtmögliche Flexibilität; es steht jedoch einer Anhebung der Komplexität der Elementaroperationen in
1.1 Was ist APL?
13
einem Rechner im Wege. Diese Anhebung ist aber eine absolute Notwendigkeit, wenn man von den erreichten Fortschritten der Großintegration von Schaltkreisen wirklich Gebrauch machen will. Auf diesem Gebiet läßt sich damit ein Einfluß von APL auf die Rechnerarchitektur erwarten [12]. Alle diese Tatsachen begründen bereits den Wert von APL als formales System, unabhängig von einer Realisierung dieses Systems als Programmiersprache. In der Tat handelt Iversons Buch von dem formalen System und seinen Anwendungen für Aufgaben der Informatik, während wesentliche Konzepte einer Programmiersprache (wie zum Beispiel das Prozedurkonzept) zunächst noch fehlen. Man merkt dann auch der später in der Form der „definierten Funktion" eingeführten APL-Prozedur an, daß APL zunächst ganz offenbar nicht als Programmiersprache, sondern als formales System konzipiert worden war und die spätere Entwicklung zu einer Programmiersprache in einigen Punkten dadurch behindert wurde, daß durch die formale Sprache APL bereits ein starres syntaktisches Gerüst gegeben war, mit dessen Möglichkeiten man vorlieb nehmen mußte. Hervorzuheben ist die Konsequenz, mit der APL von Anfang an als Dialogsystem definiert wurde, zu einer Zeit, als die Möglichkeiten der Teilnehmersysteme sich erst sehr vage abzuzeichnen begannen. Sehr entscheidend für den Erfolg von APL als Dialogsprache hat die große Einfachheit und Klarheit seiner Syntax beigetragen, die es dem APL-Novizen erlaubt, diese Sprache durch ihren Gebrauch zu erlernen, ohne erst eine Fülle von komplizierten und mehr oder weniger willkürlich festgelegten syntaktischen Festlegungen erlernt zu haben. Leider müssen wir von dem Lob der APL-Syntax (die in einigen Zügen moderner als die anderer Sprachen ist) die Programmfluß-Kontrollanweisungen ausnehmen, die praktisch denen von FORTRAN entsprechen. Wir werden aber zeigen, wie der Benutzer selbst diesen Nachteil weitgehend beheben kann. APL-Programme werden interpretiert. Dies erlaubt eine sehr einfache Syntax, bei der insbesondere das Fehlen von Namen-Attribut-Deklarationen und daraus resultierend die extreme Dynamisierung der Datenstrukturen der Sprache hervorzuheben ist. APL kommt damit einer in jüngster Zeit unter dem Begriff ,data independence' erhobenen Forderung mehr als andere höhere Programmiersprachen entgegen. ,Data independence' soll dem Programmierer die Möglichkeit bieten, sich nur um abstraktere Informationseinheiten kümmern zu müssen, während es dem System überlassen ist, die dafür geeigneten internen Darstellungen selbst zu finden. Darüberhinaus ermöglicht eine interpretierende Programmausführung eine Transparenz, die die compilierende Programmausführung nicht bieten kann. In APL kann der Benutzer während einer Programm-Suspension Auskünfte über den momentanen Programm-Status, den Wert der Variablen, usw. einholen, ja er kann Ausdrücke oder Anweisungen eingeben, die dann sofort mit den Augenblickswerten der darin angeführten Variablen ausgewertet bzw. ausgeführt
14
1. Einleitung
werden. Programm-Suspensionen werden vom System beim Erkennen von Laufzeit-Fehlern herbeigeführt oder aber auch vom Benutzer selbst, sei es durch vorprogrammierte Stop-Anweisungen oder durch manuelle Unterbrechung. Es ist dem APL-Programmierer sogar möglich, Zeichenketten — also Daten — in sein Programm einzuführen, die ausführbare Anweisungen repräsentieren, diese dann vom Programm manipulieren und anschließend in Anweisungen umwandeln zu lassen. Dies ist, wie in diesem Buch gezeigt werden wird, eines der mächtigsten Instrumente der Sprache. Der Preis für alle diese unbestreitbaren Vorteile ist die Langsamkeit der Programmausführung, eine zwangsläufige Folge der interpretierenden Arbeitsweise. Leser, die das Privileg haben, APL benutzen zu können, werden sehr bald feststellen, daß das Arbeiten mit APL Spaß macht. Wir sind nicht sicher, ob eine solche Aussage immer als Empfehlung verstanden wird, da es offensichtlich auch Informatiker gibt, die der Meinung sind, daß eine Programmiersprache eine zu ernste Sache sei, um Spaß machen zu dürfen. Wir wollen es dem Leser überlassen, sich nach der Lektüre dieses Buchs sein eigenes Urteil darüber zu bilden, ob APL neben offenkundigen Schwächen nicht doch auch richtungsweisende Beiträge zu dem Evolutionsprozeß höherer Programmiersprachen leistet. Unsere praktischen Erfahrungen mit APL erstrecken sich auf die Systeme A P L \ 3 6 0 , APL*CYBER, APLUM und York APL. Die genannten Systeme unterscheiden sich in gewissen Zügen. Wir betrachten A P L \ 3 6 0 bzw. APLSV als Bezugssystem. Auf gewisse Unterschiede in anderen Systemen werden wir gegebenenfalls hinweisen.
1.2 Ziel dieses Buches Dieses Buch will nicht in das Programmieren schlechthin einführen, sondern in eine bestimmte Sprache und das Programmieren in dieser Sprache. Wir setzen voraus, daß der Leser bereits über allgemeine Programmierkenntnisse in einer höheren Programmiersprache (z. B. der ALGOL-Familie) verfügt. Gute Einführungen in das Programmieren gibt es zur Genüge. Als hervorragende einführende Texte der deutschsprachlichen Literatur seien hier die Bücher von Wirth [27] und von Rechenberg [24] genannt. Entsprechend unserer Zielsetzung werden wir auch nicht mit einer Darstellung der Arbeitsweise von Rechenanlagen beginnen oder mit einer Erklärung der Grundbegriffe von Algorithmen und Programmen. APL wäre unseres Erachtens nach als Medium für eine erste Einführung in die Grundprinzipien der Programmierung auch wenig geeignet - zuviele Eigenschaften dieser Sprache sind APL-spezifisch und weichen in zu starkem Maße von den anerkannten Prinzipien anderer höherer Programmiersprachen ab. Jede Sprache der ALGOLFamilie (in die wir PL/1 einschließen) ist hier besser geeignet (und die Sprache unserer Wahl wäre zu diesem Zwecke PASCAL).
1.2 Ziel dieses Buches
15
Dieses Buch will mehr sein als nur eine Einführung in das Programmieren in einer bestimmten Sprache. Die Darstellung einer Programmiersprache kann auf zwei Arten geschehen. Die erste besteht in einer rein phänomenologischen oder „naiven" Beschreibung (das Wort naiv ist hier nicht abwertend gemeint), die zweite in einer komparativen und systematisierenden Beschreibung, die auch die kritische Wertung einschließt. Die erste Art erschöpft sich in der Präsentation eines Kompendiums von Definitionen, Aussagen und Fakten. Es ist dies die angemessene Form für ein Programmier-Handbuch. Die zweite Art der Darstellung muß natürlich zunächst auch Definitionen, Aussagen und Fakten präsentieren; sie wird aber darüberhinaus ständig bemüht sein, die zugrunde liegenden Konstruktionsprinzipien transparent werden zu lassen, zu ordnen und zu werten. Die bisher erschienenen Buch-Darstellungen von APL sind von der ersten Art, während dieser Text sich um die zweite Art der Darstellung bemüht. Wertungen über Programmiersprachen abzugeben ist ein riskantes Unterfangen. Obwohl die Informatik als teils mathematisch, teils ingenieurwissenschaftlich orientierte Disziplin überwiegend objektiv begründbar ist und damit insgesamt ein wenig geeignetes Objekt für modische Strömungen, Meinungen und Schulen, ist das Gebiet der Programmiersprachen eine gewisse Ausnahme. Hier finden sich durchaus in gewissem Grade solche modischen Strömungen, Meinungen und Schulen; und die Heftigkeit der Auseinandersetzung, die mitunter um gewisse Sprachen gefuhrt wird, zeigt deutlich, daß es hier nicht nur um rein sachlich formulierbare Meinungsverschiedenheiten geht. APL gehört zu den „kontroversen" Sprachen, und das zwingt uns, auf diesen Punkt einzugehen. Man kann in aller Sachlichkeit auf einige gravierende Schwächen von APL hinweisen — ebenso, wie es einige besondere Stärken dieser Sprache gibt. Man kann aber auch lesen, daß APL „eine offene Einladung für clevere Tricks ist" oder „zum schlampigen Denken erzieht". Wenn man über APL sachlich urteilt, so muß man berücksichtigen, daß eine interpretative Sprache in manchem anderen Gesetzen unterliegt als eine Compiler-Sprache. Dies scheint mitunter von Kritikern, deren Erfahrungsbereich sich auf Compilersprachen stützt, etwas übersehen zu werden. Die legitime Frage, die man hier allenfalls stellen kann, wäre die nach dem Wert interpretativer Sprachen. Nicht alle Eigenheiten von APL resultieren jedoch aus diesem Unterschied, und es gibt auch offenkundige Schwächen von APL die weniger prinzipielle, sondern vielmehr historische Gründe haben. Wir werden aber auch zeigen, wie man einige dieser Schwächen im Rahmen der bestehenden Syntax weitgehend mildern kann (auch hierin unterscheidet sich dieses Buch von den bisherigen BuchVeröffentlichungen über APL). Der kritische Vergleich von APL mit anderen Programmiersprachen macht es notwendig, auf die Kriterien einzugehen, die eine Programmiersprache kennzeichnen.
1. Einleitung
16
Solche Gesichtspunkte (zwischen denen natürlich Abhängigkeiten bestehen) sind — Die syntaktischen Elemente der Sprache — Datentypen und Datenstrukturen — Die elementaren Operatoren — Mechanismus und Zeitpunkt der Informations-Bindung — Der Deklarations-Mechanismus — Der Mechanismus der Speicherplatz-Zuweisung — Die Prozedur-Arten sowie der Mechanismus ihres Aufrufs und der ParameterÜbergabe. — Der Gültigkeitsbereich von Namen und die Strukturierung der Sprache — Die Struktur von Programmfluß-Kontrollanweisungen — Die Einbettung in ein Betriebssystem Wir h o f f e n , daß es unsere Darstellung versteht, A P L auch über die unmittelbare Anwendung hinaus für Informatiker interessant werden zu lassen, als eine wichtige Linie im Spektrum der höheren Programmiersprachen. Wichtige Eigenschaften der Sprache sowie die Anwendung der Struktur-Operatoren werden durch die im T e x t angeführten Beispiele erläutert und verdeutlicht. Darüberhinaus schließen wir von Kapitel 3 an jedes Kapitel mit einer Sammlung von sorgfältig ausgewählten Aufgaben ab, für die wir im Anhang Lösungen angeben. Wir haben dabei der Versuchung widerstanden, der bisher in der APL-Buchliteratur geübten Praxis folgend vorwiegend numerische Aufgaben zu stellen, in denen die verschiedensten APL-Operatoren in den exotischsten Kombinationen vorkommen, um dann den Leser raten zu lassen, was wohl das Ergebnis sein wird. Wir sehen A P L nicht vorwiegend als das Ausdrucksmittel für „clevere T r i c k s " , sondern werden uns bemühen, im Rahmen der durch die APL-Syntax gegebenen Möglichkeiten einen vernünftigen Programmierstil vorzuführen. Dabei sollen weniger die in jeder Einführung in das Programmieren zu findenden Standard-Aufgaben im Vordergrund stehen (wie oft wird in praktischen Anwendungen der größte gemeinsame Teiler von zwei Zahlen gebraucht?), sondern „real-world applications". Insbesondere werden wir uns dabei auf Programme zur Symbolmanipulation, zum Aufbau von Datenstrukturen, zur Verbesserung der APL-Programmstruktur, usw. konzentrieren. Numerische Beispiele haben vorwiegend den Zweck, die Anwendung der ungebräuchlicheren, komplexen Struktur- und Konversions-Operatoren zu illustrieren. Die Aufgaben stellen damit eine wesentliche Ergänzung zum T e x t dar und zeigen dem Leser Methoden und Möglichkeiten der APL-Anwendung auf, die er in der bisher erschienenen Literatur zum Teil kaum finden wird.
1.3 Metasprachliche Notationen
17
1.3 Metasprachliche Notationen Wir verzichten darauf, eine geschlossene Beschreibung der APL-Syntax, z. B. in Backus-Naur-Form (BNF), zu geben. Eine solche formale Syntaxbeschreibung ist zwar für den Implementierer einer Sprache von Bedeutung, nicht jedoch für den Benutzer, und überdies recht ermüdent zu lesen. Gewisse syntaktische Konstruktionen lassen sich jedoch mit einigen wenigen formalen Mitteln wesentlich prägnanter und klarer erklären als mit Worten, und daher werden wir uns einiger metasprachlicher Notationen bedienen, die im folgenden erklärt werden. Von der BNF werden wir das übliche Produktions-Symbol :: = als Kürzel für „ist erklärt durch" verwenden sowie die spitzen Klammern als Begrenzer einer metasprachlichen verbalen Bezeichnung. Geschweifte Klammern bezeichnen eine mögliche Wiederholung, wobei ein unterer Index die Mindestzahl und ein oberer Index die Höchstzahl des Auftretens der innerhalb der geschweiften Klammern bezeichneten Konstruktion angibt. } Q bedeutet zum Beispiel, daß die betreffende Konstruktion einmal oder gar nicht vorkommt. Die APL-Operatoren werden nach einem möglichst einheitlichen Schema beschrieben, das eine Angabe von Definitions- und Wertebereich der Funktionen, der Einschränkungen bezüglich der Struktur der Operanden (einschließlich etwaiger Konformitätsbedingungen) und die Angabe der resultierenden Struktur einschließt. Da das Gleichheitszeichen in APL eine Aussage bezeichnet, die wahr oder falsch sein kann, fuhren wir das Symbol -«-»- als metasprachliches Zeichen für die allgemeingültige Aussage der Gleichheit ein, um auf diese Weise Identitäten zwischen Ausdrücken oder einschränkende Bedingungen für den Wert von Ausdrücken bezeichnen zu können. Alle APL-Symbole haben Vorrang vor den metasprachlichen Zeichen, d. h. Ausdrücke, die links oder rechts von diesem Zeichen stehen, bilden eine abgeschlossene Einheit. Die Angabe des Definitionsbereichs einer Elementarfunktion legt die bestehenden Einschränkungen bezüglich des Datentyps der Elemente der Operanden fest. Daher führen wir bei zweistelligen Operatoren, d. h. Funktionen der Art f: A X B R, als Definitionsbereich nicht das Kreuzprodukt an, sondern die Definitionsbereiche der Operanden A und B einzeln. Diese Definitionsbereiche werden mit D A und D B bezeichnet, der Wertebereich mit W. Bei ihrer Gleichsetzung mit bestimmten ausgezeichneten Mengen verwenden wir ebenfalls das Zeichen
2. Zeichensatz, Namen, Datentypen und Konstanten
2.1 Der A P L - Z e i c h e n s a t z Wie jede Sprache, so hat auch die Programmiersprache APL ihr Alphabet, d. h. eine bestimmte Zeichenmenge, auf der sie definiert ist. Dieser Zeichensatz kann gegliedert werden in (1) (2) (3) (4) (5)
Buchstaben Ziffern Operationszeichen und Begrenzerzeichen Anweisungszeichen Sonderzeichen.
Wir verzichten an dieser Stelle darauf, das vollständige APL-Alphabet aufzulisten, da die Bedeutung vieler Zeichen doch erst im Laufe des Textes erklärt werden kann.
2.1.1 Buchstaben
APL-Buchstaben sind die 26 Großbuchstaben der lateinischen Schrift und das Zeichen A, ohne oder mit Unterstreichung. Auf Kleinbuchstaben wird verzichtet, da diese nicht zusätzlich zu den Großbuchstaben und den sonstigen Symbolen (deren Zahl die der Buchstaben übersteigt) auf einer normalen Tastatur eines Fernschreibers oder einer elektrischen Schreibmaschine unterzubringen sind. Stattdessen wird zu dem Ausweg gegriffen, die Zahl der Buchstaben durch die Möglichkeit des Unterstreichens zu verdoppeln. Buchstaben haben keine a priori Bedeutung; sie werden benutzt, um Namen, Marken und Systemkommandos zu bilden, bzw. allgemein Zeichenmengen als Objekte von Operationen.
2.1.2 Ziffern
Die Ziffern des APL-Alphabets sind die 10 Ziffern des dezimalen Zahlensystems. Ziffern werden benutzt, um Zahlen, Namen, Marken und Zeichenmengen zu bilden. Die beiden Ziffern 0 und 1 dienen ferner als Symbole für die Wahrheitswerte „falsch" und „wahr" von booleschen Variablen.
2.1 Der APL-Zeichensatz
19
2.1.3 Operationszeichen und Begrenzerzeichen Die Programmiersprache APL kennt keine Schlüsselwort-Anweisungen (wohl enthält das Programmiersystem APL\360 oder APL*PLUS oder APL*CYBER, usw. Schlüsselwort-Anweisungen in Form von Systemkommandos). Das bedeutet, daß für die große Fülle von APL-Operatoren spezielle Symbole zur Verfügung stehen müssen. Der APL-Zeichensatz enthält dafür etwa 40 spezielle Zeichen. Da diese Zahl noch nicht ausreicht, um alle möglichen Operatoren zu kennzeichnen, wird von zwei Möglichkeiten der Erweiterung des Symbol-Vorrats Gebrauch gemacht, nämlich (1) (2)
dem Übereinanderschreiben von 2 Zeichen (,overstriking') dem Aneinanderreihen mehrerer Zeichen.
Die Tatsache, daß gewisse Symbole durch Übereinanderschreiben zu bilden sind, bringt das Problem mit sich, daß dazu der Wagen der Terminal-Schreibmaschine zurückgesetzt werden muß (,backspacing'). Dies ist bei elektrischen Schreibmaschinen zwar möglich, nicht jedoch bei Fernschreiber-Terminals. Eine weitere Schwierigkeit entsteht dadurch, daß nur bei der Kugelkopf-Schreibmaschine (wie sie z. B. im IBM 2740-Terminal verwendet wird) ein Wechsel vom normalen ASCII-Zeichensatz zum APL-Zeichensatz möglich ist. Bei anderen Geräten m u ß man sich daher für einen der beiden Zeichensätze entscheiden. Da man in der Regel in einem Time-sharing-System verschiedene Programmiersprachen benutzen will, fällt diese Entscheidung meistens zugunsten des ASCII-Zeichensatzes aus. Soll daneben dann auch noch APL benutzt werden, so bleibt aus den genannten Gründen nur der Ausweg, die APL Symbole durch sinnvoll gewählte Kombinationen der vorhandenen ASCII-Zeichen zu ersetzen. Eine solche Alternative wird in der im Anhang I gegebenen Liste des APL-Zeichensatzes angegeben. In diesem Schema, das im APL*CYBER-System verwandt wird, wird jedes APL-Operatorsymbol (einschließlich der Begrenzer- und Sonderzeichen) durch eine aus drei Zeichen bestehende Zeichenkette ersetzt. Das erste Zeichen ist das Dollarzeichen und dient der Abgrenzung gegenüber Namen und allgemeinen Zeichenketten, die beiden folgenden Buchstaben geben eine mnemonische „Codierung" des APLSymbols. Ein solches Vorgehen kann natürlich nur ein Notbehelf sein. Operatorsymbole sind naturgemäß auch Begrenzerzeichen für die Zeichenketten, die Variablennamen repräsentieren. Weitere Begrenzerzeichen sind: — das Minuszeichen — Trennzeichen — Klammern. In APL wird zwischen dem Minuszeichen zur Kennzeichnung negativer Zahlen und dem Subtraktions-Operator unterschieden. Dies geschieht durch Hochstellen
20
2. Zeichensatz, Namen, Datentypen und Konstanten
des Minuszeichens, wie z. B. in der negativen, gebrochenen Dezimalzahl "123.456, während das Subtraktions-Symbol die uns gewohnte Form hat. Trennzeichen sind: der Dezimalpunkt, der auch als Trennzeichen zwischen zwei Operator-Symbolen in sogenannten Verbund-Operatoren (.composite functions') auftritt; der Doppelpunkt als Trennzeichen nach Marken; das Semikolon als Trennzeichen zwischen Indizierungs-Ausdrücken oder zwischen Zahlen und Zeichenketten in Ausgabe-Strings und das Leerzeichen. Runde Klammern können in A P L zum Klammern von Ausdrücken verwandt werden; eckige Klammern zeigen die Indizierung von Feldern an. Apostrophe schließen Zeichenketten ein.
2.1.4 Anweisungszeichen Anweisungszeichen in A P L sind: — das Zuweisungs-Symbol — die Sprunganweisung — die Ein/Ausgabeanweisungen — das Kommentar-Zeichen — die Modus-Umschaltung
- S
für den 1-Ursprung für den O-Ursprung.
Damit wird die Ordnung positiver (nichtnegativer) ganzer Zahlen auf die Menge S übertragen. Wir können stattdessen auch sagen, daß die Menge in einen Vektor geordnet wird, den wir den Datenvektor nennen, und bezeichnen diese Ordnung als kanonische Ordnung einer Datenmenge. Datenmengen werden in APL grundsätzlich in der kanonischen Ordnung eingegeben und gespeichert. 2. Schritt: Es wird eine Struktur auf S eingeführt durch Anordnung der Elemente von S in der Form eines r-dimensionalen rechteckigen Feldes. Genauer
3.3 Ordnung und Struktur von Mengen in APL
31
gesagt genügt es zu diesem Zweck, eine der beiden zusätzlichen Abbildungen einzuführen Oy: °o
:
N r -> N
für den 1-Ursprung
Nq -> N 0
für den O-Ursprung .
Wir nennen die Positionen in den r-Tupeln von N* bzw. NJ, die Koordinaten der Struktur und r den Rang. Bei der Strukturierung wird für den Wert in jeder Koordinaten der Struktur ein Maximalwert festgelegt, den wir die Dimension der Koordinaten nennen. Der Vektor aller Maximalwerte heißt der Dimensionsvektor des Feldes. Die Struktur eines Feldes ist durch den Dimensionsvektor vollständig bestimmt; um das Feld selbst (als geordnete Datenmenge) zu bestimmen, müssen die AbbildungenCTjbzw. o 2 bekannt sein. Wir betrachten zunächst den Fall des 1-Ursprungs. Sei (n 1 , n 2 ,..., n r ) e N r , sei n e N ein Element, das (n x , n 2 ,..., n r ) durch die Abbildung zugeordnet wird, und sei D = (d x , d 2 ,..., d r ) der Dimensionsvektor der Struktur. Dann ist in APL die Abbildung (rij, n 2 ,..., n r ) B- n definiert durch r-l
r
n = n r + 2 (nj - 1) II d j . i—1 j=i+l Im Falle des O-Ursprungs lautet der Dimensionsvektor D = (d 0 , d x ,..., dr.j), und die Zuordnung eines r-Tupels (n 0 , nj,..., nr_!) e Nr0 zu einem Wert n e N 0 ist gegeben durch die Funktion r-2
r-l
n = n r + 2 (rij - 1) IT d j . i=0 j=i Die durch diese Funktionen gegebene Ordnung wird row major Order genannt. Der Leser wird bemerken, daß durch die angegebenen Funktionen a y bzw. o 0 als bijektive Abbildungen spezifiziert werden. Dies setzt die Gleichheit der Kardinalität von Struktur und zugehörigem Datenvektor voraus, d. h. es muß gelten r
pDV = N = n dj (für den 1-Ursprung), wenn pDV die Länge des Datenvektors i=l bezeichnet. Ist diese Gleichheit nicht gegeben, so wird sie vom APL-System künstlich hergestellt, und zwar im Falle p DV > N dadurch, daß DV auf einen Datenvektor DV', der nur die N ersten Elemente von DV (in der gegebenen Ordnung) enthält, verkürzt wird, und im Falle N > pDV dadurch, daß DV zu einem Datenvektor DV" mit p DV" = N verlängert wird. Die Verlängerung geschieht durch periodisches Anfügen der Elemente von DV (in der gegebenen Ordnung), bis die Länge N erreicht ist.
32
3. Variablen, Datenstrukturen, Ausdrücke, Darstellungen
Die Struktur-Operatoren von APL (s. Kapitel 5) können neue Strukturen mit neuen Werten oder auch nur neue Strukturen mit gegebenen Werten erzeugen. Systemintern besteht der Unterschied darin, daß im zweiten Falle kein neuer Datenvektor erzeugt wird, sondern daß die neue Struktur mit den Elementen eines bestehenden Datenvektors aufgefüllt wird. Dies bedeutet, daß in solchen Fällen die Abbildungen a t bzw.CT0modifiziert werden müssen. Diese Modifikation hängt von der Operation ab, durch die die neue Struktur erzeugt wird und kann wegen der Kompliziertheit der meisten Struktur-Operatoren recht kompliziert sein. Marchai [22] hat gezeigt, daß durch eine rekursive Anwendung der oben angegebenen Funktionen in Kombination mit der Transposition (Abschnitt 5.3.12) die modifizierten Abbildungen a x bzw.CT0fiir alle APL-StrukturOperatoren erhalten werden können. Natürlich ist dies ein Problem, das nur fiir die Implementierung von APL von Interesse ist, nicht jedoch für seine Benutzung. Unsere Diskussion soll lediglich noch einmal klarstellen, warum der Wert einer Strukturvariablen in APL aus zwei Größen besteht, gegeben durch das Paar (Dimensionsvektor, Datenvektor). Es ist daher nur logisch, daß APL dem Benutzer zwei spezielle Funktionen zur Verfugung stellt, durch deren Anwendung auf eine Variable er nur den Dimensionsvektor oder nur den Datenvaktor des momentanen Werts der Variablen erfragen kann (während er beim Aufruf der Variablen selbst den gesamten Wert als strukturierte Datenmenge erhält). Es sind dies der Dimensions-Operator (monadisches RHO) und der Aufreih-Operator (monadisches KOMMA). Ferner gibt es den StrukturierungsOperator (dyadisches RHO), durch den eine Struktur deklariert werden kann. Strenggenommen erzeugt dieser Operator keine neue Struktur, sondern nur den in der Deklaration angegebenen Dimensionsvektor und Datenvektor. Da ein Datenvektor jedoch auch eine strukturierte Größe ist, kann man diesen Operator auch als einen strukturverändernden Operator auffassen, der eine gegebene Struktur (den Datenvektor) in die deklarierte Struktur umformt.
3.4 Indizierung von Variablen und Operatoren Durch Kombination der beiden im vorhergehenden Abschnitt angegebenen Schritte, nämlich der Ordnung und Strukturierung einer Datenmenge, erhalten wir die beiden bijektiven Abbildungen (je nach gewähltem Ursprung) V
Nr+S
bzw.
I0:
NQ
S.
Diese Abbildungen heißen Indizierung. In APL wird das Indizierungs-r-Tupel in der syntaktischen Form einer Indexliste angegeben, die dann als Operator in Postfix-Notation (es ist dies der einzige Fall einer Postfix-Notation in APL) auf eine Variable angewandt wird. Die Indexliste besteht aus r IndizierungsAusdrücken, die durch Semikolon getrennt werden müssen. Der Wert des i-ten
3.4 Indizierung von Variablen und Operatoren
33
Indizierungs-Ausdrucks gibt den Wert der i-ten Koordinaten im Indizierungs-rTupel an. Ist eine Indexangabe für eine bestimmte Koordinate leer, so wird vom System dafür der Vektor aller Elemente-Indizes längs der entsprechenden Koordinaten ausgewählt (in APL-Notation ist dies für die i-te Koordinate der Vektor \ dj). Auf diese Weise kann man zum Beispiel eine gesamte Zeile oder eine gesamte Spalte einer Matrix bezeichnen, oder allgemein ein Feld, das Teil eines anderen Feldes von höherem Rang ist 3 . Im Rahmen der Vorstellung von APL-Strukturen als geordneten Mengen entspricht die Indizierungs-Operation einer Untermengen-Bildung. Beispiele: V[3] ist das dritte Element eines Vektors V (wir nehmen den 1-Ursprung an). A[3;4] ist das Element in der dritten Zeile (1. Koordinate = Spaltenrichtung) und der vierten Spalte (2. Koordinate = Zeilenrichtung) einer Matrix A. A[;4] bezeichnet die gesamte vierte Spalte, da die Zeilenangabe leer ist, während entsprechend A[3;] die gesamte dritte Zeile angibt. B [ 2 ; ; ] bezeichnet schließlich die zweite Untermatrix eines Feldes B vom Rang 3 (das wir uns als einen Vektor von Matrizen vorstellen (vergl. Abb. 3-1). Man beachte, daß eine leere Indexangabe als Leerzeichen oder aber auch als gar kein Zeichen geschrieben werden kann. Die Werte der Ausdrücke eines Indizierungs-r-Tupels können ihrerseits Felder von Indexzahlen sein. Dadurch kann entlang jeder Koordinaten eine beliebige Zahl von Elementen durch die Indizierung ausgewählt werden. Diese Auswahl geschieht so, daß ein Baum durchlaufen wird, bei dem der Abstand von der Wurzel gleich der Ordnungszahl der Koordinaten ist. Die terminalen Knoten sind damit immer die Elemente entlang der Zeilenrichtung. Die Allgemeinheit der Indizierungsoperation bringt es mit sich, daß die Kardinalität des Resultats kleiner (durch Auswahl) oder größer (durch Mehrfachverwendung von Elementen) als die der Ausgangs-Struktur sein kann. In APL können nicht nur Variablen indiziert werden, sondern auch gewisse Operatoren. Die syntaktische Form ist dabei die gleiche wie bei der Indizierung von Variablen; d. h. es wird das Operatorsymbol angegeben, gefolgt von der Indexangabe in eckigen Klammern. Die Indexangabe besteht jetzt aber nur aus einem einzigen Ausdruck, dessen Wert ein Skalar ist, der die Koordinate angibt, längs der die Operation ausgeführt werden soll. Die Indizierung von Operatoren wurde in APL eingeführt, um die Anwendung von Operationen, die ursprünglich nur als Vektoroperationen definiert wurden, auf beliebige mehrdimensionale Felder zu erweitern. Dabei ist die Operation ihrer Natur nach nach wie vor eine Vektoroperation, die aber jetzt auf alle Vektoren von Komponenten eines mehrdimensionalen Feldes längs einer bestimmten 3
Leser, die mit PL/I vertraut sind, werden die Analogie zur „cross section"-Bildung erkennen.
34
3. Variablen, Datenstrukturen, Ausdrücke, Darstellungen
Koordinatenrichtung angewandt wird. Ist das Feld zum Beispiel eine Hypermatrix vom Rang 3 (vergl. Abb. 3-1), so sind dies entlang der ersten Koordinaten alle Vektoren, deren Komponenten die Elemente gleicher Position in allen Untermatrizen sind, entlang der zweiten Koordinaten alle Spaltenvektoren in den einzelnen Untermatrizen, und entlang der dritten (und letzten) Koordinaten alle Zeilenvektoren in allen Untermatrizen. Die syntaktische Form einer indizierten Operation ist damit { [](l. Operand). Der (skalare) Wert K des Indizierungs-Ausdrucks gibt den Koordinatenindex an. Für ein Feld vom Rang ppA gilt Ke [1: ppA ], wenn alle Zählungen mit 1 beginnen (dies ist der normale Modus), oder alternativ K e [0: (pp A) - 1 ], wenn alle Zählungen mit 0 beginnen. Wird keine bestimmte Zählweise vom Benutzer spezifiziert, so beginnen alle Indizierungen mit Eins. Wünscht der Benutzer eine Zählweise, die mit Null beginnt, so kann er dies durch ein Systemkommando (s. Kapitel 8.1) )ORIGIN 0 deklarieren. Das System antwortet auf dieses Kommando mit der Angabe WAS 1 , falls der Zählursprung bislang Eins war (war er bereits Null, so lautet die Antwort entsprechend: WAS 0). Der Zählursprung gilt auch für den INDEXGENERATOR. Dieser Operator, dessen Symbol das griechische Iota ist und dessen Operand eine skalare Zahl sein muß („monadisches IOTA"), erzeugt einen Vektor, dessen Komponenten die ersten N Ordinalzahlen, beginnend mit dem gewählten Ursprung, sind. Im 1-Ursprung gilt somit xN •*-> [1 : N ] , im O-Ursprung gilt xN [0 :N - 1 ] , xl liefert den geltenden Ursprung als Wert. Fehlt bei einem indizierbaren Operator die Indexangabe, so führt das APLSystem die Operation in der Regel entlang der letzten Koordinate, d. h. entlang der Zeilenrichtung aus. Deshalb kann bei indizierbaren Funktionen die Indexangabe grundsätzlich entfallen, wenn der Operator auf einen Vektor angewandt wird. Indizierbare Operationen in APL sind (s. Kapitel 5): -
die die die die die
Reduktion Katenation/Lamination Kompression Expansion Umkehrung/Rotation
f/ , / \ 4>
(f/) (—) (/) (\) (e) .
3.5 Ausdrücke
35
Es sei noch darauf hingewiesen, daß manche APL-Implementierungen auch die in Klammern angegebenen Alternativoperatoren enthalten. Diese führen im Prinzip die gleiche Operation aus; beim Fehlen einer Indexangabe wird aber jetzt die Operation entlang der ersten Koordinaten ausgeführt. (Beispiel: Bei einer Matrix bedeutet (pA)[K] Rang und Dimension von R: ppR «-»• ppA ;
pR^((K-l)tpA),(+/B),K+pA
Definition: Die Operation wählt aus allen Untervektoren von A entlang der K-ten Koordinaten alle Komponenten aus, für die die Elemente gleicher Position in B Eins sind, und unterdrückt alle Komponenten, für die die Elemente gleicher Position in B Null sind. Das Ergebnis hängt vom Ursprung ab. Ist entweder A oder B ein Skalar oder ein Feld mit nur einer Komponenten, so wird es auf alle Komponenten des anderen Arguments angewandt. Beispiele: (10 p 1 0)/i 10 1 3 5 7 9 0 0 1 0 1 1 0 0 1 0 0 1 1 1 1/'COMPUTERSPRACHE' MUTSACHE 1 0 0 1/2 3 4 p 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' AD EH IL MP QT UX 0 1/2 3 4 p 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' LENGTH ERROR 0 1 / [ 1 ] 2 3 4 p 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' MNOP QRST UVWX 0 1 0 / [ 2 ] 2 3 4 p 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' EFGH QRST 0/a 5 (Leeres Resultat) 15
SYNTAX ERROR l/i 5 1 2 3 4 5
89
5.3 Selektions-Operatoren 5.3.8 Expandieren (expand)
Schreibweise: R
B \ [K] A
Definitionsbereich für B: D b - B ; ppB -*-»• 1
(B ist ein boolescher Vektor)
Konformitätsbedingung: (+/B)^(pA)[K] Rang und Dimension von R: p p R ++ ppA ; p R - M - ( ( K - l ) + p A ) , ( p B ) , K + p A Definition: Das Resultat ist ein Feld R, für das gilt B / [ K ] R A. Die Positionen von R, die durch ( ~ B ) / [ K ] R bestimmt werden, enthalten Füllelemente (0 für numerische W e r t e , ' ' für Zeichen). Beispiele:
1 0 1 \2 2 p i 4 1 0 2 3 0 4
1 0 1 \[1] 2 4 p
'ABCDEFGH'
ABCD EFGH 1 3
2 4
0 0
0 0
5 7
6
1 0 3
2 0 4
5 0 7
6
1 0 1 \[1] 2 2 2 p i 8
8 1 0 1 \[2]
2 2 2 p i 8
0
8
1 0 1 \ 3 4 p i LENGTH ERROR
12
90
5. Elementarfunktionen: Struktur-Operationen
5.3.9 Umkehrung (reverse) Schreibweise: R-[K] A oder
R + e A
Rang und Dimension von R: ppR
ppA ; p R
pA
Definition: Das Resultat wird erhalten durch Umkehrung der Reihenfolge der Elemente in allen Vektoren von A entlang der K-ten Koordinaten. Das Resultat hängt vom deklarierten Ursprung ab. R
A (kein K): Die Vektoren entlang der letzten Koordinaten werden umgeordnet
R -[1] 2 3 p x 6 4 5 6 1 2 3 )ORIGIN 0 WAS 1 e 2 3 p i 6 3 4 5 0 1 2 [l] 2 3 p i 6 2 1 0 5 4 3 5.3.10 Rotation (rotation) Schreibweise: R -«- B 0 1 nach links und für B < 0 + + 1 nach rechts. Ist A ein Feld mit 2< pp A 1, und ist B ein Skalar, so werden alle Vektoren von A entlang der angegebenen Koordinaten zyklisch rotiert, und zwar auf den Ursprung des Koordinatensystems zu, wenn B positiv ist, und vom Ursprung weg, wenn B negativ ist (die Rotation in Spaltenrichtung erfolgt bei positivem B also aufwärts). Ist B ein Feld, dann werden die einzelnen Vektoren von A entlang der K-ten Koordinaten individuell zyklisch rotiert, so wie es jeweils das korrespondierende Element von B angibt. Das Ergebnis hängt vom deklarierten Ursprung ab. R -«- B(J)A: Die Vektoren entlang der letzten Koordinaten werden rotiert. R -- 1
(B ist ein Vektor)
Konformitätsbedingung: pB -*-> ppA Rang und Dimension von R: ppR ^
r / B ; p R [ I ] -w- L/(B=I)/pA , V I e i
PP
R
Definition: Wir betrachten zwei Fälle 1) A / ( i p p A ) e B
1 12
(die Elemente von B sind voneinander verschieden)
B ist der inverse Permutationsvektor: Die B [ I ] -te Komponente von p R ist die I-te Komponente von A, oder (pR)[B]
^ p A .
Zur Durchführung der Transposition ist folgendes Schema nützlich. 1. Schreibe die Elemente von pA an; 2. Schreibe darunter die Elemente von B; 3. Schreibe in eine dritte Zeile die Elemente von p A, aber positionsmäßig so umgeordnet, wie es die korrespondierenden Zahlen in B angeben. 12
Bezüglich des APL-Operators e s. Abschnitt 5.4.2
94
5. Elementarfunktionen: Struktur-Operationen
Beispiel: R - f - 3 1 2 ( S | 4 5 6 p i 120 pA: 4 - ^ 5 6 B: 3 / i } < 2 pR: 5 6 4 Damit güt: R [ J ; K ; I ]
-M-A[I;J;K]
Nachdem die Umordnung der Indizes festgestellt wurde, wird das Resultat dadurch erhalten, daß man alle Indizes von A durchläuft, für jedes Element von A die Umordnung der Indexzahlen vornimmt und das Element dann in R gemäß der neuen Indizierung anordnet. Ist A eine Matrix, so gilt 2 1 § A «-»• § A (Vertauschen von Zeilen und Spalten). 2) Ov/O-pp A ) e B ) A (A/(i|7B)eB) 1 (einige Elemente von B sind gleich, aber alle Elemente von [1: T/B] kommen in B vor). Wir zerlegen die Index-Menge i p B von B in Äquivalenzklassen von Indexzahlen, für die jeweils die zugehörigen Elemente von B gleich sind. Dies kann z. B. dadurch erfolgen, daß wir für I = 1 , 2 , . . . , T/B solange mit Hilfe der Operation KI -i3)ii 4 14 3n4 RANK ERROR (linkes Argument muß ein Vektor sein) 'EXOTISCH' \ 4 5 p 'SELEKTIONSOPERATOREN' 6 19 19 4 5 3 9 6 3 9 19 9 4 3 9 1 9
3 2
5.4.2 Zugehörigkeit (membership) Schreibweise: R --RuA , Wertebereich: W
B
Rang und Dimension von R: ppR
ppB ; p R -m- pB
Definition: Das Resultat ist ein boolesches Feld von der gleichen Dimension wie das Argument B. Eine 1 in diesem Feld zeigt an, daß das korrespondierende Element von B in A enthalten ist; eine 0 zeigt an, daß das korrespondierende Element von B nicht in A enthalten ist. Es gilt damit R v/B°.=,A . Für numerische Werte von A und B gilt die Gleichheit innerhalb der durch den FUZZ-Parameter gegebenen Abweichung
5. Elementarfunktionen: Struktur-Operationen
98
Beispiele:
!
2
3
, C A T C H 22'
£
0 0 0 (2 3 4 p 1 24) e ! 1 5 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
5.4.3 Aufwärtssortieren (grade up) Schreibweise: R-«- 4,A Definitionsbereich: D -«-> R ; p A
1
(A ist ein Vektor)
Wertebereich: W -«-> i p A Rang und Dimension von R: p p R «-»• ppA ; pA «-> pA Definition: Das Resultat ist ein Vektor von Indexzahlen von A, so geordnet, d a ß A [ R ] eine Teilordnung bildet; und zwar ordnet A [ R ] die Elemente von A nach aufsteigenden Werten um, wobei die relative Ordnung gleicher Elemente erhalten bleibt. Alle Vergleiche sind im Rahmen des systeminternen Rundungsfehlers exakt. Der FUZZParameter hat keinen Einfluß auf die Operation. Das Resultat hängt vom deklarierten Ursprung ab. Das Prinzip der skalaren Erweiterung gilt nicht. Beispiele:
5 6
3
4
5
1
3
25
Q
1 2 A 5
5 5
7 1 A[R] 4 5
2
3
8
5
5
6.3
•
4
-g 5
1
3>14E2
3
6.5
6 6.5
4
6.3
99
5.5 Konversions-Operatoren 5.4.4 Abwärtssortieren (grade down) Schreibweise: R
f A
Definitionsbereich: ; pA
1
(A ist ein Vektor)
Wertebereich: W-w-ipA Rang und Dimension von R: ppR
pp A ; pR - " H p B ) (vergl. Abschnitt 5.1.2). Rang und Dimension von R: ppR
0f ~2+(ppB)+ppA ; p R * - > ( ~ H p B ) , l + pA
Definition: Das Resultat ist der Wert, der sich ergibt, wenn die Elemente des rechten Arguments entlang der ersten Koordinaten als Ziffern in einem Zahlensystem interpretiert werden, dessen Basiswerte durch die Zeilen des linken Arguments gegeben sind. Wir betrachten ausführlicher die folgenden Fälle (1) Die Argumente sind Skalare oder Vektoren. Sei B ein Skalar oder ein Vektor, in dem alle Elemente gleich sind: B[1 ] -*->...-*->• B [ p B ] b. Dann ist das Resultat das Polynom in b mit den Koeffizienten aus A. Beispiel:
8 x 2 4 6 3 1331 -*->• (2x8*3)+(4x8*2)+(6x8*l)+(3x8*0)
Gilt a / ( A > 0 ) , ( A < B ) -«-> 1 , dann ist das Resultat die Dezimalzahl, die dem Wert A, interpretiert als Zahl zur Basis B, äquivalent ist. Beispiel:
1 0 l
i
3 9+-* 1 3 9 .
Sei B ein Vektor, dessen Elemente verschieden sind. Dann gilt R Beispiel:
+/A[I] x ( x / U B )
A+. x x / H B ; I e i ( p A ) [ l ] .
24 60, 60 i 1 2 3 3723 ^ > ( l x 6 0 x 6 0 ) + ( 2 x 6 0 ) + ( 3 x l )
+ Neutrales Element von x (3723 ist die Zeit von 1 Stunde, 2 Minuten, 3 Sekunden, gemessen in Sekunden).
101
5.5 Konversions-Operatoren
Beachte: Der Wert B [ l ] findet keine Verwendung als Basiszahl. Wir könnten deshalb statt der Zahl 24 (Konversionsfaktor zwischen Tag und Stunden) auch jeden anderen Wert einsetzen, wie z. B. in 0 60 60 i 1 2 3 3723 (2) Das linke Argument ist ein Skalar oder ein Vektor, das rechte Argument ist ein Feld mit 2 > ppA 1. Die Elemente der Vektoren aus A entlang der ersten Koordinaten werden als Ziffern in einem Zahlensystem interpretiert, dessen Basiswerte durch B gegeben sind. (3) Das linke Argument ist eine Matrix, das rechte Argument ein Feld mit 2 > pp A 1. Wie Fall (2), nur daß jetzt die Zeilen von B verschiedene Zahlensysteme repräsentieren. Jeder Vektor aus A wird in allen durch die Zeilen von B angegebenen Zahlensystemen dargestellt. (4) Beide Argumente sind Hypermatrizen. Die Fortsetzung des bisher erläuterten Schemas ist klar vorgezeichnet (wegen der Verwandtschaft zum Inneren Produkt s. auch Tab. 5-2)
Beispiele: 0 5 2 x 27 48 (3 1 p 763 430.47 208 118.97 43 28.07
3 2 2 p
i5
24 35
10 5 2) i 3 3 p 7 4 1 6 2.3 1 3 7.47 0 110 30 6
2 2 2 21 1 0 1 1 11 2i 1 0 1 1 11
2 2 i l 0 1 1 LENGTH ERROR 0 3 12 i 3 2 3.25
135.25
16 i 61770
F --(pB),pA Definition: Das Verschlüsseln ist die Umkehrung des Entschlüsseins. Die Zahlen des Arguments A werden in ein Zahlensystem konvertiert, dessen Basiszahlen die Elemente von B entlang der ersten Koordinaten sind. Die erhaltenen Koeffizienten füllen die erste Koordinate des Resultats. Wir betrachten ausfuhrlicher die folgenden Fälle: (1) B ist ein Vektor und A ein Skalar Das Resultat ist so beschaffen, daß gilt A
+ / R [ I ] x ( x / H B ) -M- R [ I ] + . x ( x / H B ) .
Beispiel: 24 60 60 T 3723 « - » - 1 2 3 3723 « - * ( l x 6 0 x 6 0 ) + ( 2 x 6 0 ) + ( 3 x l )
+ Neutrales Element von x Für B [ l ] ... -m- B [ P B ] b gilt, daß die vordersten Ziffern des Resultats abgeschnitten werden, wenn (pB) < Ll+b ® A -«-> 1 ist. Beispiel: ( 4 p 2 ) j 11 «-> 1 0 1 1 ,
aber ( 2 p 2 ) T 1 1 ^ 1
1.
Dieses Abschneiden kann in seiner Auswirkung gemildert werden, indem Null als erstes Element des Basiszahlenvektors B eingesetzt wird. Diese Null verursacht, daß bei der Berechnung des ersten Koeffizienten im Resultatvektor der gesamte Rest der Zahl A aufgebraucht wird.
103
5.5 Konversions-Operatoren
Beispiele: (0,2p 2)T 11
1 1 ; 0 60 T 3723
62 3
Falls dieses Schema zu einem negativen Element im Resultat führt, erfolgt wegen dieser Überschreitung des Wertebereichs eine DOMAIN ERROR-Meldung. (2) B ist ein Vektor, und A ist ein Vektor Jede Spalte des Resultats ist die Darstellung des entsprechenden Werts von A in einem Zahlensystem mit dem Basiszahlenvektor B. (3) B ist eine Matrix, und A ist ein Vektor R [ ; I ; J ] ist die Darstellung von A [ J ] im Zahlensystem mit dem Basiszahlenvektor B[;I], Die Fortsetzung dieses Schemas ist damit klar vorgezeichnet. Beispiele: 10 10 10 T 123 1 2 3 0 10 T 123 12 3 10 0 12 3 0 DOMAIN 0 9 5 0 3 0.14 0 3 0.1 1 (4
0 0
0 0
0 0
0
1
1
0 0
0 0
0 0
1
0
1
0 0 1
1 1 1 1 1 1
7 7 1
1 3 1
5 7 1
0 10
T
123
"10 T "123 ERROR 12 T 113
(Negatives Ergebnis) (Quotient und Rest von 113 T 12)
1 T 3.14 (Ganzzahliger Teil und Dezimalbruch) 0.3 2
T
3
3 p 10 8 2)
T
(Gebrochene Basiszahlen) 7 11 15
104
5. Elementarfunktionen: Struktur-Operationen
5.5.3 Berechnen (execute, evaluate) Schreibweise: ( i = i überschrieben mit o) Definitionsbereich: D
A ; ppA
1
Definition: A ist eine Zeichenkette, von der wir voraussetzen, daß sie eine ausführbare Anweisung 1 3 repräsentiert. Der Operator verursacht die Ausfuhrung der Anweisung. Damit können Programme geschrieben werden, die APL-Anweisungen vor ihrer Übersetzung und Berechnung modifizieren (durch Zeichen-Manipulation), und es können außer Werten auch Namen an Funktionen übergeben werden. Beispiel: A -«- 'B' 10+', A,'+- 3'
(Weitere Beispiele werden in den folgenden Kapiteln gegeben)
13 B 3
5.5.4 Formatieren (format) Schreibweise: R
TA
(T = T überschrieben mit o)
Definitionsbereich: D+->-R;l>ppA*->l Werte bereich: W -*-> A Rang und Dimension von R: ppR 1+ppA ; p R (pA),Z Z ist die Zahl der Zeichen, die notwendig ist, um in allen Zeilen von R den Dezimalpunkt an die gleiche Stelle zu bringen. 13
In manchen Systemen (z. B. APL*CYBER) muß es ein ausführbarer Ausdruck sein.
105
5.5 Konversions-Operatoren Definition:
Das Resultat ist die Darstellung der Elemente von A in Form von entsprechenden Zeichenketten, wobei durch Auffüllen mit Leerzeichen dafür gesorgt wird, daß alle Zahlen im gleichen Format dargestellt werden (s. Dimension von R). Beispiele: • -UA)=(+/A°.>A)ii
p
1 Zum Zweck der Ausgabe — und nur zu diesem Zweck — können mehrere Ausdrücke zu einer Zeile konkateniert werden. Das Konkatenationssymbol ist dabei
A
118
6. Anweisungen
das Semikolon. Dies fuhrt dazu, daß die Werte der einzelnen Ausdrücke in der Reihenfolge ihres Austretens von links nach rechts ohne Zwischenräume ausgegeben werden. Der Sinn dieser Einrichtung ist hauptsächlich, gemischt Textzeilen und numerische Werte ausgeben zu können. Dadurch können zum Beispiel Ergebnisse in Form von ganzen Sätzen ausgegeben werden, Fehlermeldungen ausgewählt werden, usw. Beispiele: !X;'IST DIE FAKULTAET VON'jX^O 5 120 IST DIE FAKULTAET VON 5 'DIE KREISFLAECHE MIT RADIUS ';X;' I S T ' ; o ( X ^ [ ] ) * 2 2.5 DIE KREISFLAECHE MIT RADIUS 2.5 IST 19.63495408
Man beachte, daß Zwischenräume (Leerzeichen) als Teil der Zeichenkette eingegeben werden müssen. Repräsentiert ein Ausdruck eine Matrix, so beginnt die Ausgabe seines Werts auf einer neuen Zeile. Beispiele: '(2 3) p ' ; X ; ' ERGIBT DIE MATRIX: 1
2 3 pXf-D
2
(2 3) p 1 2 ERGIBT DIE MATRIX: 1 2 1 2 1 2 'DER WERT IST'; (~1 0 l = x [ ] ) / [ l ] 3 7 p'NEGATIV NULL POSITIV' 6t2-4*3 DER WERT IST NEGATIV Wird zur Ausgabe ein Ausdruck der Dummy-Variablen • zugewiesen und anschließend • vom Benutzer aufgerufen, so interpretiert das System diesen Aufruf als Eingabeanweisung. Wird die daraufhin vom System an den Benutzer gerichtete Aufforderung zur Eingabe ([] 0 von diesem mit einem Wagenrücklauf beantwortet, so wird sie vom System wiederholt.
6.4 Eingabeanweisungen und Ausgabeanweisungen
119
Beispiel: • -e 6 v 2 - 4 * 3 "0.09677419355
•
-V
Der Benutzer kann — wie im Beispiel gezeigt — aus diesem circulus vitiosus entkommen, indem er als ,escape'-Anweisung das Symbol (d. h. die Sprunganweisung ohne Angabe eines Sprungziels) eingibt. Die ,escape'-Anweisung für den Abbruch einer durch Q angeforderten Zeichenketten-Eingabe besteht in dem Zeichen (J, das gebildet wird durch Übereinanderschreiben der Buchstaben O, U und T (OUT). Ein Ausdruck, der vom Benutzer in Beantwortung einer Eingabe-Anforderung eingegeben wird, kann seinerseits wieder eine Eingabe-Anforderung enthalten, d. h. die Eingabe-Anweisung kann in beliebiger Tiefe rekursiv sein. Eine solche Rekursion kann auf jeder Ebene durch die escape-Anweisung abgebrochen werden. Eine durch Q repräsentierte Eingabe-Anweisung kann natürlich nicht rekursiv sein, da ein in der Eingabe auftretendes Q lediglich als ein Zeichen interpretiert wird. Wird auf die Eingabe-Aufforderung []: hin ein System-Kommando (s. Kapitel 8) eingegeben, so hängt die Reaktion des Systems von der Art des Kommandos ab. Diese Fälle werden in Abschnitt 8.4 besprochen. Was geschieht, wenn die durch das Semikolon erfolgte Konkatenation mehrerer Ausdrücke einer Variablen zugewiesen werden (ein unzulässiger Fall)? In den uns zugänglichen Systemen (APL\360, APL*CYBER) wird dann keine Fehlermeldung ausgegeben, sondern es wird der am weitesten links stehende Ausdruck berechnet und zugewiesen, während alle weiteren Ausdrücke unbeachtet bleiben. Beispiel: R « - [ > ' D I E KREISFLAECHE MIT RADIUS 'iX;'IST«;o(X«-[])*2 2.5 DIE KREISFLAECHE MIT RADIUS 2.5 IST 19.6349540849 R DIE KREISFLAECHE MIT RADIUS Zu den Ausgabe-Anweisungen ist auch die Anweisung zur Ausgabe einer Funktionsdefinition (function display) zu zählen. Wir besprechen diesen Fall in Abschnitt 7.7.
120
6. Anweisungen
6.5 Betriebsarten-Umschaltung und Definitionsmodus Zu Beginn, d. h. wenn der Benutzer in das APL-System eintritt, befindet sich dieses im Ausführungsmodus. In diesem Modus ist jede abgeschlossene Zeile ein ausführbares Programmstück, das auch unverzüglich ausgeführt wird. Komplexere Programme mit Schleifen, Unterprogramm-Aufrufen, usw. lassen sich auf diese Weise natürlich nicht aufbauen. Selbstverständlich kann man in APL aber auch reguläre Programme schreiben (sonst würde APL das Attribut „Höhere Programmiersprache" nicht verdienen). Im Gegensatz zu Programmen für Stapelverarbeitungs-Anlagen locht man ein APL-Programm aber nicht auf Lochkarten ab, die dann dem Operateur der Rechenanlage zur Ausführung übergeben werden, sondern man gibt ein Programm über die Terminal-Schreibmaschine ein. Damit soll keineswegs vorgeschlagen werden (wie es manche Autoren allerdings tun), daß man sich ohne eine Vor-Formulierung des Programms auf dem Papier an das Terminal setzt und nun darauflos statements eintippt. Vor Beginn einer,terminal session' sollte man das Gerüst des zu entwickelnden Programms bereits auf dem Papier ausgearbeitet haben, sei es in Form eines Flußdiagramms, sei es in Form eines Programm-Skeletts, formuliert zunächst mit Hilfe von Meta-Anweisungen, die in der Folge dann noch in einzelne Programmschritte aufzulösen sind. Durch den Teilnehmerbetrieb kann man es sich aber leisten, die eigentliche Formulierung der einzelnen APL-Anweisungen bei der Eingabe vorzunehmen, umsomehr, als APL sehr gute Hilfen zum Programm-Editieren bietet (s. Kapitel 8). Die Betriebsart, in der solche Programme erzeugt werden können, heißt Definitionsmodus (function definition mode). Die Umschaltung vom Ausführungsmodus in den Definitionsmodus geschieht durch ein spezielles Kommando, das in der Eingabe des Zeichens V („del") besteht. Die Wiederholung desselben Zeichens setzt das System wieder in den Ausführungsmodus zurück. Innerhalb des Definitionsmodus werden Funktionen (= Prozeduren) vom Benutzer definiert (= geschrieben). Daher der N a m e f u n c t i o n definition mode. Eine definierte Funktion besteht aus dem Funktionskopf und dem Funktionskörper. Beide Teile müssen nach bestimmten syntaktischen Regeln aufgebaut sein, die wir im folgenden Kapitel näher besprechen. An dieser Stelle soll nur soviel gesagt werden, daß der Funktionskopf aus einer Zeile besteht, in der das erste Zeichen die Umschaltanweisung V ist. Der Rest des Kopfes besteht aus einer Deklaration des Funktionstyps sowie (falls gegeben) der lokalen Parameter. Der Funktionskörper besteht aus einer Folge von Anweisungen — je eine in einer Zeile. Die einzelnen Zeilen des Körpers werden automatisch vom System durchnumeriert. Die jeweilige Zeilennummer kann als Sprungmarke in Sprunganweisungen verwandt werden. Daneben dürfen aber auch mnemonische Markennamen vom Programmierer eingeführt werden. Die letzte Zeile des Funktionskörpers wird in der Regel mit der Rückschaltanweisung V abgeschlossen.
6.6 Sprunganweisungen
121
Im Gegensatz zum Ausfuhrungsmodus wird im Definitionsmodus eine eingegebene Anweisung nicht sofort interpretiert und ausgeführt.
6.6 Sprunganweisungen Sprunganweisungen haben die syntaktische Form • R[2] r R [ 2 ] IST GLEICH ZEILENNUMMER IM DREIECK V SA PASCAL-«-4 PASCAL 3 PASCAL [ 4 ] (Aktivitäten des Benutzers während der Suspendierung) ->• 4 (Sprunganweisung zur Fortsetzung des Programms) TA PASCAL«- 3 4 PASCAL [ 3 ] 1 1 PASCAL [ 4 ] 3 PASCAL [ 3 ] 1 2 1 PASCAL [ 4 ] 3 PASCAL [ 3 ] 1 3 3 1 PASCAL [ 4 ] 0 TA PASCAL 0 • PASCAL 3 1 3 3 1
, [1]
6.8 Unterbrechungs-Anweisungen Die Suspendierung einer Programmausführung kann vom Benutzer oder vom System verursacht werden. Der Benutzer kann die Suspendierung an vorherbestimmten Stellen durch eine Stop-Anweisung verursachen, oder er kann die Programmausfuhrung manuell an einer nicht definierten Stelle durch Drücken einer bestimmten Taste auf der Tastatur seiner Datenstation, der sogenannten ATTENTION-Taste, auslösen. Das Drücken der ATTENTION-Taste unterbricht, was immer sich zu diesem Zeitpunkt gerade in der Ausführung befindet. Die Suspendierung einer Programmausfuhrung fuhrt immer dazu, daß das System sich während der Unterbrechung im Ausfuhrungsmodus befindet und daß die Eingabe-Tastatur für den Benutzer entriegelt wird. Alles das, was man normalerweise im Ausführungsmodus tun kann, kann nun auch während der Unterbrechung getan werden, mit Ausnahme einiger Einschränkungen beim Editieren von Funktionen. Diese Einschränkungen werden in Abschnitt 7.8 besprochen.
130
6. Anweisungen
Der Benutzer kann sich während einer Suspendierung vom System Auskunft darüber geben lassen, an welcher Stelle die Unterbrechung erfolgt ist (s. Abschnitt 7.6). Eine Sprunganweisung zu der Zeile, vor deren Bearbeitung die Funktionsausführung unterbrochen wurde, läßt das Programm an der gleichen Stelle fortfahren. Eine Sprunganweisung zu einer beliebigen anderen Stelle führt dazu, daß die Programmausfuhrung von dieser Stelle aus fortgesetzt wird. Das System unterbricht die Ausfuhrung eines Programms immer dann, wenn es einen Fehler erkennt. In diesem Falle wird zunächst eine Fehlermeldung ausgegeben (s. Abschnitt 7.8), gefolgt von der Angabe der Unterbrechungsstelle (Name der in der Ausführung befindlichen Funktion, gefolgt von von der Nummer (in eckigen Klammern) der ersten nicht mehr ausgeführten Zeile), gefolgt von einem Ausdruck der Anweisung, die den Fehler enthält.
Aufgaben zu Kapitel 6 1. Schreiben Sie eine APL-Funktion STATISTIK, die als Argument einen Zahlenvektor V hat. Die Funktion sei monadisch und ohne explizites Resultat. Ihre Ausfuhrung soll zur Berechnung und zur Ausgabe von Mittelwert, Streuung, Größtwert, Kleinstwert und dem Bereich der Komponenten von V fuhren. Die Ausgabe soll in Textform erfolgen, d. h. durch Sätze der Art: „Der Mittelwert ist...", usw. 2. Schreiben Sie eine Funktion mit explizitem Resultat und dem Aufruf N BITREVERS Z , die als Argument Z eine Ganzzahl empfängt, diese in eine Binärzahl umwandelt, die Binärzahl umkehrt und das Resultat in eine Dezimalzahl zurückwandelt. Unter der Umkehrung einer Binärzahl verstehen wir eine Operation, bei der das erste Bit mit dem letzten vertauscht wird, das zweiterste mit dem zweitletzten, usw. (bei ungerader Stellenzahl wird das mittlere Bit mit sich selbst vertauscht). N ist der Maximalwert, den eine Zahl Z annehmen darf; die Länge der Binärzahl richtet sich demnach nach N. Ist das Argument Z nicht ganzzahlig, so soll eine Fehlermeldung erfolgen. (BITREVERS ist eine wichtige Operation bei der schnellen Fouriertransformation.) 3. Schreiben Sie eine Funktion ohne explizites Resultat mit dem Aufruf A ZUWEISE ''. Die Funktion des Programms soll darin bestehen, eine globale Variable mit dem Namen (Name) anzulegen und dieser den Wert von A zuzuweisen. Frage: Was geschieht, wenn der Name des formalen Parameters für A als Name der globalen Variablen gewählt wird?
Aufgaben zu Kapitel 6
131
4. Schreiben Sie zwei Funktionen mit explizitem Resultat. Die erste, NC Z , soll eine Ganzzahl Z in eine entsprechende Zeichenkette umwandeln. Die zweite Funktion, CN ' Z ' , soll eine als Zeichenkette deklarierte Ganzzahl Z in eine echte Zahl umwandeln. Die Operatoren j> (BERECHNEN) und t ( F O R M A T ) dürfen dabei nicht verwendet werden. 5. Wie wirken die folgenden Sprunganweisungen? B ist dabei ein Ausdruck, dessen Wert eine boolesche Größe ist. A) -* B/MARKE BpMARKE B-t-MARKE MARKE x i B MARKE f i B B) ( ( B * 0 ) , B=0)/MARKE1, M A R K E 2 BMARKE1, M A R K E 2 C) Was geschieht bei der Anweisung: ->(B 1 (JjMARKE 1 , M A R K E 2 ) x i B 2 ? D) Wie wirkt die Anweisung: -»• 0 x i ~ B ? 6. Schreiben Sie eine Funktion U IOTA N, die als Resultat den Vektor der N ersten Ordnungszahlen (Indexzahlen) liefert und zwar beginnend vom beliebig wählbaren Ursprung U (für U=1 bzw. U=0 ist IOTA also in der Wirkung mit der monadischen Elementarfunktion i identisch). Das Programm soll N auf Ganzzahligkeit prüfen und gegebenenfalls eine Fehlermeldung ausgeben. Der APL-Operator i darf in der Funktion nicht vorkommen! 7. Schreiben Sie eine niladische Funktion mit explizitem Resultat L I S T E , die eine Liste von Wörtern anlegt, die der Benutzer eingibt, und zwar in der Form einer Matrix, bei der in jeder Zeile genau ein Wort steht. Da eine solche Matrix rechteckig sein muß, füllt das Programm jedes eingegebene Wort vor dem Eintrag in die Matrix mit Leerzeichen auf die geforderte Länge auf. Die Zeilendimension der Matrix soll, wenn immer notwendig, adaptiv so erweitert werden, daß sie am Ende gleich der Länge des längsten eingegebenen Worts ist. Die Funktionsausführung wird dadurch abgeschlossen, daß der Benutzer anstelle eines Wortes das Zeichen eingibt. 8. Schreiben Sie eine Funktion mit dem Aufruf A E U K L I D B, die als Wert den größten gemeinsamen Teiler von A und B liefert. 9. Es sei eine Funktion mit dem Aufruf Z SUCHE L I S T E zu schreiben. L I S T E sei dabei eine Liste von Namen, gegeben in Form einer Zeichen-Matrix (s. Aufgabe 7), und Z ist ein Zeichenvektor der Länge p Z < p L I S T E [ 2 ] . Die Funktion soll als Wert die Indexzahlen derjenigen Zeilen von L I S T E liefern, deren p Z ersten Zeichen mit Z übereinstimmen. 10. Schreiben Sie eine Funktion ALPHASORT, die ihrerseits die Funktion L I S T E (s. Aufgabe 8) aufruft und anschließend die durch L I S T E interaktiv
132
6. Anweisungen
erzeugte Liste von Namen alphabetisch sortiert. Es soll eine möglichst einfache, APL-gerechte Lösung gefunden werden. Dabei ist sicherzustellen, daß kein Name mehr als L Zeichen lang ist. 11. Schreiben Sie eine APL-Funktion mit explizitem Resultat und dem Aufruf L FILL N , die als rechtes Argument eine Ganzzahl mit nicht mehr als L Ziffern empfängt und sie durch „fuhrende" Nullen auf die Länge L auffüllt. Das Resultat soll als Zeichenkette vorliegen. Ist die Anzahl der Ziffern von N größer als L, so soll eine Fehlermeldung erfolgen. 12. Schreiben Sie eine APL-Funktion DHZ, die eine (natürliche) Dezimalzahl beliebiger Stellenzahl in eine Hexadezimalzahl umwandelt. Dabei sollen die Buchstaben A,B,C D E,F die Ziffern der Wertigkeit 10,11,12,13,14,15 darstellen. Das Resultat ist daher zwangsläufig ein Zeichenvektor. Schreiben Sie eine Funktion HD Z , die umgekehrt eine als Zeichenkette gegebene Hexadezimalzahl in eine (echte) Dezimalzahl umwandelt.
7. Prozeduren
7.1 Prozedur-Typen In den höheren Programmiersprachen spielt das Prozedurkonzept eine zentrale Rolle. Prozeduren erlauben die Zerlegung von großen Programmen in kleinere, in sich abgeschlossene Teile. Durch die rekursive Anwendung dieses Prinzips werden umfangreiche Programme strukturiert und damit überschaubar. Der Gültigkeitsbereich von Variablen wird kontrollierbar. Das Schreiben, Testen und Verstehen von Programmen wird durch eine solche Strukturierung sehr erleichtert. Man unterscheidet in den höheren Programmiersprachen zwischen internen und externen Prozeduren. Interne Prozeduren werden innerhalb des Hauptprogramms formuliert, d. h. alle Prozedurdefinitionen werden als ein Programm geschrieben und übersetzt. Interne Prozeduren findet man in allen Sprachen mit Blockstruktur} Ihr bekanntester Vertreter ist ALGOL. Externe Prozeduren hingegen werden als getrennte Programme, unabhängig voneinander und vom Hauptprogramm, geschrieben und übersetzt. Der bekannteste Vertreter der Sprachen, die nur externe Prozeduren zulassen, ist FORTRAN. PL/1 hat sowohl interne als auch externe Prozeduren. Die Prozeduren in APL sind externe Prozeduren. Man unterscheidet ferner zwischen Prozeduren vom Funktionstyp (functiontype procedures) und Prozeduren vom Anweisungstyp (statement-type procedures)2. Bei den Prozeduren vom Funktionstyp wird beim Aufruf der Prozedur ein Wert berechnet und dem Prozedurnamen zugewiesen. Solche Funktionsaufrufe können damit — ähnlich den Variablen — auch Bestandteil eines Ausdrucks sein. Bei den Prozeduren vom Anweisungstyp erfolgt keine Wertzuweisung zu dem Prozedurnamen. Werden explizite Werte berechnet, so werden diese den Ausgabeparametern zugewiesen, deren Namen zusammen mit denen der Eingabeparameter beim Prozeduraufruf in einer Parameterliste anzugeben sind. Die Namen der Ausgabeparameter können genau wie andere Variable als Teil eines Ausdrucks auftreten, nicht jedoch der Prozeduraufruf selbst. In einer Prozedur können ferner Variablen auftreten, die nicht nur innerhalb der Prozedur Gültigkeit haben, 1
2
Dies ist natürlich nicht das einzige Kriterium für eine Sprache mit Blockstruktur. Zusätzlich zu internen Prozeduren muß eine solche Sprache über BEGIN ... E N D Blocks verfügen, die eine ähnliche Funktion wie die internen Prozeduren haben (so z. B. den, einen Gültigkeitsbereich von Namen zu definieren), aber nicht wie jene aufgerufen werden können. In der Literatur auch (weniger präzise) Funktionsprozeduren und Anweisungsprozeduren genannt.
134
7. Prozeduren
sondern in dem environment (der „Umgebung"), in das die Prozedur „eingebettet" ist. (Eine solche Prozedur-Umgebung wird durch das aufrufende Programm gebildet). Man unterscheidet damit zwischen Größen, deren Gültigkeit auf die Prozedur beschränkt ist, die also lokal bezüglich der Prozedur sind und nichtlokale Größen, deren Gültigkeitsbereich sich nicht nur auf die Prozedur selbst, sondern auf eine weitere Umgebung erstreckt. Ist diese Umgebung das environment des Hauptprogramms, so nennt man die darin definierten Größen global. Im mathematischen Sinne sind nicht-lokale Größen, die im Prozedur-Körper verwendet werden, ebenfalls Parameter der Prozedur. Im Sprachgebrauch der Programmierung werden jedoch üblicherweise nur die in der Parameterliste des Prozedur-Kopfes angegebenen Eingabe- und Ausgabeparameter als Prozedur-Parameter bezeichnet. Wirth [27] bezeichnet daher die Parameter im üblichen Sinne als expltite Parameter und die innerhalb einer Prozedur auftretenden nicht-lokalen Größen als implizite Parameter. Die Variablen des environments können durch die Prozedurausführung in ihrem Wert verändert werden. Verändert eine Prozedur auf diese Weise das environment des aufrufenden Programms, so spricht man von Nebenwirkungen (side effects). Im Extremfalle mag die Wirkung einer Prozedur vom Anweisungstyp allein in der Erzeugung von Nebenwirkungen bestehen. Da die Implikationen von Nebenwirkungen bei komplexeren Programmen oft jedoch schwer zu überschauen sind, ist größte Zurückhaltung und Vorsicht gegenüber diesem Prinzip geboten. Die Unterscheidung zwischen Prozeduren vom Funktionstyp und Prozeduren vom Anweisungstyp geschieht üblicherweise durch die syntaktische Form des Aufrufs. So wird in FORTRAN und PL/I eine Prozedur vom Funktionstyp durch das Auftreten ihres Namens in einem Ausdruck aufgerufen, während eine Prozedur vom Anweisungstyp - dort auch subroutine genannt - durch ein Call Statement aufgerufen wird. Natürlich findet man auch gewisse syntaktische Unterschiede zwischen den beiden Typen innerhalb der Prozedur-Körper. In Abschnitt 6.2 haben wir die sechs möglichen syntaktischen Formen einer Prozedurdeklaration in APL angegeben (vergl. Tab. 6-1). Wie sind nun diese sechs Typen von definierten Funktionen in die allgemeine Klassifizierung von Prozeduren in höheren Programmiersprachen einzuordnen? Eine Antwort auf diese Frage findet sich merkwürdigerweise in der uns zugänglichen APL-Literatur nicht, obwohl die Antwort sich aus den obigen Betrachtungen eigentlich von selbst ergibt. Funktionen mit explizitem Resultat entsprechen, wie schon das Attribut „mit explizitem Resultat (= Wert)" sagt, der Definition der Prozedur vom Funktionstyp, wobei es eine Eigenheit von APL ist, daß die Anzahl der explizit angebbaren Parameter nicht mehr als zwei sein kann. Damit kann dieser Funktionstyp auch als Bestandteil eines Ausdrucks aufgerufen werden. Zusätzlich kann eine .Funktion mit explizitem Resultat' aber auch globale Variable des environments beeinflussen, so daß wir hier eigentlich eine Mischform vor-
7.2 Parameterübergabe
135
liegen haben; es sei denn, der Programmierer verzichtet strikt auf die Verwendung globaler Variablen im Funktionskörper. .Funktionen ohne explizitem Resultat' können nur durch die Erzeugung von Nebenwirkungen im environment des aufrufenden Programms wirksam werden 1 . Da dem Funktionsnamen kein Wert zugewiesen wird, ist diese Form eher als Prozedur vom Anweisungstyp anzusprechen. Explizite Ausgangsparameter gibt es jedoch nicht; die Zahl der expliziten Eingangsparameter ist auf maximal zwei begrenzt. Die Zahl der impliziten Parameter ist selbstverständlich unbegrenzt. Eine niladische Funktion kann nur implizite Eingangsparameter haben. Wir betrachten hierzu ein einfaches Beispiel. Wir nehmen an, daß im Laufe einer Programm-Abarbeitung den Variablen A,B,C,I und J, wie im folgenden angedeutet, Werte zugeordnet wurden. Schließlich wird eine Funktion VADD aufgerufen, die wie folgt definiert sei
[1] [2] [3] [4]
VR X VADD Y R - N erreicht wird, soll das weitere Hochzählen abgebrochen und der vorhergehende Wert neu gesetzt werden.
V R •«- QBASE N;REST [1]
R -«- 4 p 0
[2]
R [ l ] -REST +• N - R [ l ] *2)/END 1 NEGATIV
q AUSSPRUNG WENN REST
142
7. Prozeduren
[5] [6] [7] [8] [9]
->• ADJUST REST R TEST A U F ABGLEICH DURCH R [ 4 ] R [ 2 ] - - C O N T x t S > l r GOTO CONT WENN EIN KOEFFIZIENT VORHANDEN R -«- '1' p SETZT '1' FUER FEHLENDEN KOEFFIZIENTEN CONT: D I F x i S < p , X p GOTO DIF WENN EXPONENT VORHANDEN
[13] [14] [15]
0 p AUSSPRUNG WENN KEIN EXPONENT VORHANDEN DIF: E - Z [ J ] ' hat (I und J sind Indexzahlen, und das Symbol für die APLSprunganweisung soll den Übergang anzeigen). 4. Eingabe eines Ausdrucks als Argument einer Funktion Skizzieren Sie eine Methode, die es erlaubt, die Bedingung für die Beendigung des Durchlaufs einer REPEAT-Schleife in einer definierten Funktion erst beim Aufruf der Funktion festzulegen. (Hinweis: Die Bedingung kann damit nicht als Wert übergeben werden, sondern muß als (auswertbarer) Ausdruck übergeben werden.) 5. Battleship Zu schreiben sei ein Programm für das „Battleship"-Spiel. In diesem Spiel verfügt jeder der beiden Spieler über ein Quadrat von 1 0 X 1 0 Feldern, in dem er 4 Schiffe positionieren kann: einen Flugzeugträger (5 Felder lang), ein Schlachtschiff (4 Felder lang), einen Zerstörer (3 Felder lang) und ein
7. Prozeduren
170
Torpedoboot (2 Felder lang). Die Schiffe können in horizontaler oder vertikaler Ausrichtung angeordnet werden. Jeder Spieler versucht, die Schiffe des Gegenspielers zu versenken. Um ein Schiff zu versenken, müssen alle vom Schiff belegten Rasterfelder getroffen sein. Der Computer soll seinem menschlichen Gegenspieler den ersten Schuß überlassen. Ist ein Schuß ein Treffer, so darf der Spieler, der am Zuge ist, erneut schießen. Ist ein Schuß kein Treffer, so kommt der Opponent zum Zuge. Die Versenkung eines Schiffes muß dem anderen Spieler mitgeteilt werden. Das Programm wird aus zwei Hauptteilen bestehen. Das erste Teilprogramm, das zunächst aufgerufen wird, besorgt für den Computer die Aufstellung seiner Schiffe (diese wird dem Gegenspieler natürlich nicht mitgeteilt). Der Computer wird sich dazu eine 10 X 10-Matrix anlegen, in die er die Positionen seiner Schiffe einträgt, z. B. wie in dem folgenden Bild (durch die Zahlenwerte sind die einzelnen Schiffe leicht zu unterscheiden). Natürlich darf sich die Position zweier Schiffe nicht überschneiden. 0 0 0 0 0 0 0 0 0 0
0 0 0 4 4 4 4 4 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 2
0 3 0 0 0 0 0 0 0 2
0 3 0 0 0 1 1 0 0 2
0 3 0 0 0 0 0 0 0 0
0 3 0 0 0 0 0 0 0 0
Das zweite Teilprogramm sorgt für den Spielablauf. Der Computer soll dabei eine sinnvolle Strategie bei der Plazierung seiner Schüsse verfolgen. Wegen des relativ komplizierten logischen Ablaufs soll dieses Programm unbedingt zunächst durch ein Flußdiagramm dargestellt werden. Außerdem soll es in sinnvoller Weise durch Zerlegung in Teilprogramme strukturiert werden. Man überlege sich bereits beim Aufstellen des Flußdiagramms, welche Größen als Träger von Botschaften über den augenblicklichen Spielzustand zwischen den einzelnen Modulen des Spielprogramms benötigt werden. Ferner soll dafür gesorgt werden, daß Fehleingaben durch den menschlichen Gegenspieler nach Möglichkeit nicht zu Fehlermeldungen (mit Unterbrechung der Programmausführung) führen, sondern einfach übergangen werden.
8. Die Verwaltung des Arbeitsbereiches, Systemkommandos und Systemfunktionen
8.1 Der aktive Arbeitsbereich und seine Verwaltung
Jedem Benutzer wird beim Eintritt in den Dialog mit dem APL-System von diesem zunächst ein Arbeitsbereich (workspace) im Speicher zugewiesen, in dem alle vom Benutzer im Laufe einer „Sitzung" an der Datenstation kreierten Daten, Funktionen und Programme gespeichert werden. Dieser Speicherbereich wird deshalb der aktive Arbeitsbereich des Benutzers genannt. Beendet der Benutzer die Sitzung, dann geht alle im aktiven Arbeitsbereich gespeicherte Information verloren. Der Benutzer hat allerdings die Möglichkeit, sich eine Bibliothek (library) von weiteren, inaktiven Arbeitsbereichen anzulegen, in die er Daten, Funktionen und Programme vor Beendigung der Sitzung retten kann. Ferner steht ihm noch ein spezieller aktiver Arbeitsbereich zusätzlich zur Verfügung, der CONTINUE-Bereich (workspace CONTINUE) genannt werden soll. Die Größe des aktiven Arbeitsbereichs ist je nach Implementierung verschieden. Ein typischer Wert ist ein Speicherbereich von 32 K Bytes (IBM APLY360). Andere Systeme weisen zunächst einen kleineren Grund-Arbeitsbereich zu und vergrößern diesen automatisch in bestimmten Quanten immer dann, wenn der bisherige Bereich nicht mehr ausreicht. Bei Maschinen mit Seiten-Adressierung (virtuellem Speicher) besteht der Arbeitsbereich naturgemäß aus einer ganzzahligen Zahl von Seiten. Der aktive Arbeitsbereich ist nicht nur der temporäre Speicher für alles, was der Benutzer im Laufe seines Dialogs mit dem System eingibt, und alles, was das System als Ergebnisse produziert, sondern er bildet auch das environment für die Programme des Benutzers. Das bedeutet zum Beispiel, daß der Gültigkeitsbereich der Variablen der Programme, die im „Tischrechner-Modus" ausgeführt werden, bzw. der globalen Variablen eines Hauptprogramms der aktive Arbeitsbereich ist. Ebenso ist die Lebensdauer dieser Variablen zunächst durch die Lebensdauer des aktiven Arbeitsbereichs gegeben. Allerdings kann der Benutzer durch bestimmte Aktionen die Lebensdauer einer Variablen oder einer definierten Funktion verlängern oder auch verkürzen. Der zunächst noch unbeschriebene Arbeitsbereich (clear workspace), den das System dem Benutzer bei der ersten Kontaktaufnahme zur Verfügung stellt, bildet ein normalisiertes environment. In diesem normalisierten environment gelten die folgenden Festlegungen.
172
8. Die Verwaltung des Arbeitsbereiches, Systemkommandos und Systemfunktionen
(1) Zeilenbreite: Die Zeilenbreite der Ein/Ausgabe auf der Schreibmaschine (dem Fernschreiber) beträgt 120 Stellen. (2) Zählursprung: Der Ursprung für alle Operatoren, die Ordnungszahlen als Argumente oder Resultate haben, ist der 1-Ursprung. (3) Zahlendarstellung: Zahlen werden mit maximal 10 signifikanten Stellen ausgeschrieben. Intern werden sie mit bis zu 17 signifikanten Stellen gespeichert. (4) FUZZ-Parameter: Der Unschärfe-Parameter (FUZZ) für Vergleichsoperationen ist mit etwa 1.0E~13 festgesetzt. (5) SEED-Parameter: Bei der Erzeugung von Zufallszahlen ist für den dazu benutzten Algorithmus intern ein Parameter zu spezifizieren. Dieser Wert wird SEED genannt. Er ändert sich von einem Anfangswert aus mit jedem Aufruf des Operators? (monadisch oder dyadisch). Der Anfangswert von SEED ist in APL\360 16807. (6) Der Arbeitsbereich enthält keine Objekte (Variablen oder Funktionen). Die Status-Indikator-Liste ist leer und der Arbeitsbereich-Name ist CLEAR. Der Benutzer verwaltet seinen Arbeitsbereich mit Hilfe von Systemkommandos. Durch Systemkommandos kann er sich auch Auskunft über den aktuellen Status seines Arbeitsbereichs und der Bibliotheken verschaffen. Ein Systemkommando beginnt mit einer rechtsseitigen runden Klammer, der ein Schlüsselwort folgt. Ein Systemkommando kann parameterfrei sein, oder es kann als rechtsseitiges Argument eine Parameterliste folgen. Neuere APL-Implementierungen ergänzen bzw. ersetzen die Systemkommandos durch Systemfunktionen (s. Abschnitt 8.5). Durch das Systemkommando )CLEAR kann der Benutzer zu jedem Zeitpunkt der Urzustand seines aktiven Arbeitsbereichs wiederherstellen. Nach diesem Kommando sind alle Eintragungen gelöscht und das environment ist normalisiert. Der Benutzer kann das durch den aktiven Arbeitsbereich gebildete environment seiner Programme ändern. Dazu stehen ihm die in Tab. 8-1 aufgeführten Systemkommandos zur Verfugung. Durch die Aktivitäten des Benutzers wird sich der Arbeitsbereich nach einiger Zeit mit Variablen und definierten Funktionen gefüllt haben. Die Namen dieser Objekte werden vom System in einer Namensliste (symbol table) gefuhrt. APL\ 360 reserviert im normalisierten Arbeitsbereich Platz für 256 Namen. Der Benutzer kann die Größe der Namensliste auf jeden anderen Wert N im Bereich 26 < N < 4241 bringen, indem er nach dem Löschen des Arbeitsbereichs das Kommando )SYMBOL N gibt.
173
8.1 Der aktive Arbeitsbereich und seine Verwaltung Tab. 8-1. Systemkommandos zur Veränderung des environment 1
AUFRUF
)WIDTH )ORIGIN < Ursprung) )DIGIT )SYMBOL < Liste von Objektnamen) . Ein Objektname kann ein identifier einer Variablen, einer Funktion, oder einer anderen Gruppe sein. Das Objekt nennt man den referent des identifiers (Namens). In der Liste von Objektnamen können auch identifiers stehen, die keinen (oder noch keinen) referent haben. Wir wollen der Einfachheit halber aber von Objekten statt von identifiers reden und sagen, daß durch das )GROUPKommando die in der Liste benannten Objekte zu einer Menge (Gruppe) zusammengefaßt werden, die durch den Gruppennamen identifizierbar ist. Ein Objekt kann mehreren Gruppen angehören, und Gruppen können direkt oder indirekt sich selbst referieren. Die somit aufgebaute Datenstruktur ist ein (allgemeiner) Graph. Beispiel: X ^ Y ^ Z ^ T - e 'DATEN' )GROUP Gl X Y G2 )GROUP G2 Z T )SAVE ARBEITSBEREICH SAVED 15.17.29 09/08/75 )CLEAR CLEAR WS )COPY ARBEITSBEREICH G2 SAVED 15.17.29 09/08/75 )VARS Z T )COPY ARBEITSBEREICH Gl )VARS X Y Z T )GRPS Gl
178
8. Die Verwaltung des Arbeitsbereiches, Systemkommandos und Systemfunktionen
In diesem Beispiel treten zwei Systemkommandos auf, die noch nicht besprochen wurden: )COPY und )GRPS. )COPY wird im folgenden Abschnitt behandelt. )GRPS veranlaßt das System, die Liste der im aktiven Arbeitsbereich existenten Namen von Gruppen auszugeben. )GRPS kann ebenso wie )VARS oder )FNS mit einem Anfangsbuchstaben als Parameter aufgerufen werden, was dann die dort beschriebene Wirkung hat. Eine Anfrage nach den Objekten einer Gruppe erfolgt durch das Kommando )GRP (Gruppenname> {(Anfangsbuchstabe)} Q . Das System schreibt daraufhin eine Liste der Objektnamen in alphabetischer Ordnung aus, beginnend — wenn angegeben — mit dem spezifizierten Anfangsbuchstaben. Beispiel:
A C
)GROUP X A )GRP X B C D E )GRP X C D E
B C
D
E
8.2 Der Arbeitsbereich C O N T I N U E und die Beendigung einer Sitzung Ein Arbeitsbereich mit dem Namen CONTINUE wird vom System für jeden Benutzer automatisch dann eingerichtet, wenn die Verbindung zwischen dem Computer und der Datenstation des Benutzers zusammenbricht oder wenn der Operateur der Rechenanlage aus irgendeinem Grunde die Verbindung unterbricht. Das System rettet dann den Inhalt des aktiven Arbeitsbereichs in den Bereich CONTINUE. Dadurch geht dem Benutzer die zum Zeitpunkt der Unterbrechung im aktiven Arbeitsbereich gespeicherte Information nicht verloren. Befindet sich das System zum Zeitpunkt der Unterbrechung im Funktions-Definitionsmodus, so schaltet es sich zunächst in den Ausführungsmodus zurück. Befindet sich gerade eine Anweisung in der Ausfuhrung, so ist die Wirkung dieselbe, als ob der Benutzer die ATTENTION-Taste gedrückt und anschließend das Systemkommando )CONTINUE aufgerufen hätte. Fällt der Computer aus, dann geht der aktive Arbeitsbereich in der Regel allerdings verloren.
8.2 Der Arbeitsbereich CONTINUE und die Beendigung einer Sitzung
179
Der Benutzer kann aber ebenfalls den Arbeitsbereich CONTINUE einrichten. Dies geschieht durch die Kommandos )SAVE CONTINUE, )CONTINUE, oder )CONTINUE HOLD. )SAVE CONTINUE legt eine Kopie von CONTINUE in der Bibliothek an. Dazu muß CONTINUE nicht zunächst in den aktiven Arbeitsbereich gebracht werden. CONTINUE kann auch in allen anderen Systemkommandos, die einen Arbeitsbereich referieren, wie z. B. )LOAD, )DROP, )COPY, )PCOPY, usw. als Referenz auftreten. CONTINUE kann damit dem Benutzer als zusätzlicher Arbeitsbereich dienen. Von dieser Möglichkeit sollte man jedoch mit Vorsicht Gebrauch machen, da es immer vorkommen kann, daß durch einen plötzlichen Zusammenbruch der Verbindung zwischen Computer und Terminal der aktive Arbeitsbereich nach CONTINUE gerettet und damit alle bisher in CONTINUE gespeicherte Information überschrieben wird. Eine Terminal-Sitzung kann vom Benutzer auf folgende Weise beendet werden (1) (2) (3) (4) (5)
durch durch durch durch durch
das Systemkommando )OFF das Systemkommando )OFF HOLD das Systemkommando )CONTINUE das Systemkommando )CONTINUE HOLD unbeabsichtigte Unterbrechung der Verbindung zum Computer.
Im Falle von )OFF wird die Sitzung beendet, und der Inhalt des aktiven Arbeitsbereichs geht verloren. Im Falle von )CONTINUE wird die Sitzung ebenfalls beendet; der Inhalt des aktiven Arbeitsbereichs wird vorher aber nach CONTINUE gerettet. Der Zusatz HOLD bewirkt, daß die Sitzung zwar beendet, die Verbindung zum Computer aber aufrechterhalten wird. Wird eine neue Sitzung begonnen und war bei Beendigung der vorhergehenden Sitzung entweder durch das System (beim Ausfall der Verbindung) oder durch den Benutzer (durch )CONTINUE) der aktive Arbeitsbereich nach CONTINUE gerettet worden, so wird CONTINUE zu Anfang automatisch vom System in den aktiven Arbeitsbereich geladen, wenn dieser nicht durch ein Kennwort geschützt ist. Ist der aktive Arbeitsbereich geschützt, so wird CONTINUE stattdessen mit dem selben Kennwort in die Bibliothek des Benutzers kopiert. Es bleibt dann dem Benutzer überlassen zu entscheiden, was weiterhin damit geschehen soll. Der Benutzer kann in Verbindung mit dem Kommando, das die Beendigung einer Sitzung am Terminal anzeigt, ein Kennwort angeben. Dabei muß das Kommando und das Kennwort durch einen Doppelpunkt getrennt werden.
180
8. Die Verwaltung des Arbeitsbereiches, Systemkommandos und Systemfunktionen
Beispiel:
)OFF : KINKERLITZCHEN
Dies hat zur Folge, daß er zur nächsten Sitzung nur dann zugelassen wird, wenn er außer seiner Benutzer-Nummer auch dieses Kennwort angibt (s. nächster Abschnitt). Eine solche Maßnahme soll den Benutzer davor schützen, daß ein Unbefugter sich Zugang zu den Programmen und Daten, die er in seiner Bibliothek aufbewahrt, verschaffen kann. Das Kennwort kann am Ende einer jeden Sitzung geändert werden. Soll ein bestehendes Kennwort aufgehoben werden, so wird nach dem Beendigungs-Kommando nur ein Doppelpunkt geschrieben.
8.3 Bibliotheken und das Kopieren von Objekten Zu Beginn einer Sitzung muß sich der Benutzer eines Terminals, nachdem er die Verbindung mit dem Computer hergestellt hat, beim System anmelden (sign-on) und sich legitimieren. Dies geschieht in APL\360 zum Beispiel durch das Kommando ) < Benutzer-Nummer) Die Benutzer-Nummer ist eine mindestens vierstellige ganze Zahl, die dem Benutzer vom Rechenzentrum zugeteilt wurde. Hat der Benutzer am Ende der letzten Sitzung ein Kennwort angegeben (s. oben), so muß in diesem Falle das Kommando lauten ) • UNTIL B zu geben. B ist dabei ein Ausdruck, dessen Wert eine boolesche Größe ist. Die Iteration soll abgebrochen werden, wenn B wahr geworden ist. 4. Wie kann man mit Hilfe der Systemfunktion []IO sicherstellen, daß innerhalb einer definierten Funktion (unabhängig vom äußeren environment) der 1-Ursprung gilt?
9. Dateien
9.1 Felder als Datenstruktur Die Brauchbarkeit einer „Allzweck"-Programmiersprache wird wesentlich dadurch beeinflußt, welche Möglichkeiten sie zum Aufbau von Datenstrukturen bietet. Bei der Entwicklung der frühen algorithmischen Sprachen, zu denen wir FORTRAN, ALGOL 60 und APL zu zählen haben, ist dieser Aspekt nicht hinreichend gewürdigt worden, und erst später entwickelte Sprachen bieten hier bessere Möglichkeiten. Ebenso wie FORTRAN und ALGOL 60 besitzt auch APL zunächst nur einen Typ von strukturierten Variablen: das homogene, rechteckige Feld. Allerdings bietet APL gegenüber den beiden genannten anderen Sprachen den Vorteil, daß solche Felder auch vom Datentyp CHARACTER sein können und daß es eine Fülle von Operatoren zur Verarbeitung dieses Datentyps gibt. Trotzdem bleiben auch im Falle von APLN360 oder ähnlichen Implementierungen noch viele Wünsche unerfüllt. Wir betrachten hierzu ein Beispiel [10]. Es soll eine Patientendatei aufgebaut werden für maximal 5000 Patienten. Die Datei soll für jeden Patienten enthalten: Patientennummer, Name, Vorname, Alter, Geschlecht, Familienstand, Adresse, Codenummer der Krankenversicherung, Versicherungsnummer des Patienten, sowie die Befunde von maximal 50 klinischen Untersuchungen. Jede Eintragung für eine Untersuchung soll aus 23 mehrstelligen Zahlen bestehen, durch die die Ergebnisse von klinischen Tests und anderen Untersuchungen dargestellt werden, sowie eine stichwortartige Klartext-Diagnose von maximal 100 Zeichen. Die Daten sind also teils numerischer Art (auch die Angaben zur Person bestehen außer aus den Namen aus Codenummern), teils bestehen sie aus Text. Da APL nur Felder als strukturierte Variablen besitzt, könnte man zunächst auf die Idee kommen, alle numerischen Daten in einem einzigen Feld vom Rang 3 zusammenzufassen, dessen 3 Koordinaten die Patienten, die Untersuchungen und die Untersuchungsergebnisse sind. Dies hätte den Vorteil, daß man mit einem einzigen Namen für diese Struktur auskäme, wobei der Zugriff für jedes einzelne Datenelement durch entsprechende Indizierung erfolgen würde. Die Frage, wie die Text-Befunde und die persönlichen Daten der Patienten damit gekoppelt werden könnten, soll für den Augenblick außer Betracht bleiben. Man sieht sofort, daß eine solche Lösung nicht praktizierbar ist. Selbst wenn wir annehmen, daß jeder numerische Wert als ganze Zahl dargestellt wird, für die nur ein vier Byte langes Normalwort aufzuwenden ist, würde das gesamte Feld
196
9. Dateien
5000 • 50 • 23 • 4 = 23 • 10 6 Bytes, das heißt hunderte von Arbeitsbereiche groß sein. Dies ist erstens nicht möglich, und zweitens wäre auch im Falle der Realisierbarkeit die Verschwendung von Speicherplatz nicht zu verantworten. Nur wenige Patienten werden es auf die Maximalzahl von 50 Eintragungen bringen. Für manche wird es nur eine einzige Eintragung geben (der Patient wird beim erstmaligen Besuch der Klinik in die Datei aufgenommen), und die mittlere Zahl von Untersuchungen pro Patient sei mit etwa 10 angenommen. Der einzig vernünftige Ausweg ist daher, für jeden Patienten individuell eine Matrix der numerischen Befunde zu definieren. Die Spaltenzahl beträgt bei allen diesen Matrizen einheitlich 23, die Zeilenzahl ist jeweils gleich der Anzahl der Untersuchungen und damit variabel. Speicherplatz wird nicht verschenkt. Aber auch damit ist das Problem noch nicht gelöst, wie die restlichen, alphanumerischen Daten mit den numerischen Datenfeldern der Patienten verbunden werden können. APL erlaubt nur homogene Felder und schließt damit die Vereinigung von Zahlen und Zeichen in ein und demselben Feld aus. Im Falle der Klartext-Diagnose könnte man eine zweite Matrix für jeden Patienten definieren, deren Spaltenzahl gleich 100 ist (für die maximal 100 Zeichen einer Eintragung) und deren Zeilenzahl wieder gleich der Anzahl der Untersuchungen ist. Die numerischen Befunde und der Diagnosetext einer jeden Untersuchung wird damit automatisch durch den (gleichen) Zeilenindex korreliert. Der Speicherbedarf für die Textmatrix ist nicht zu vernachlässigen. Mit der mittleren Zahl von 10 Untersuchungen pro Patient beträgt er 5 Millionen Bytes. Unter der Annahme, daß der Mittelwert über alle Texteintragungen 50 Zeichen ist, besteht der Preis für die (bequeme) Verwendung der Feldstruktur darin, daß ein Speicherplatz von 2500000 Bytes verschenkt wird. Ähnliche Überlegungen gelten, wenn die persönlichen Daten des Patienten in einer weiteren Matrix zusammengestellt werden sollen, da auch dort die Länge der Eintragungen (Namen, Vornamen, Stadtnamen und Straßennamen der Adresse) recht variabel sein kann. Außerdem stoßen wir wieder auf das Problem, daß ein Teil der Eintragungen Zeichenketten und der andere Teil Zahlen sind. Man könnte nun die Zahlenangaben auch als Zeichenketten definieren, um alle persönlichen Angaben in einer homogenen Matrix unterzubringen, bei der eine jede Zeile nun genau eine der Angaben repräsentiert. Dies hat den Nachteil, daß mit solchen Zahlenangaben nicht mehr ohne weiteres gerechnet werden kann. Genau dies wird aber bei Such- oder Sortiervorgängen erforderlich. So kann zum Beispiel später die Aufgabe gestellt werden, die Liste nach Altersgruppen zu durchsuchen oder zu sortieren. Dazu ist die Ausfuhrung von Relations-Operatoren nötig, die mit Ausnahme der Gleichheits-Relation nur auf Zahlen anwendbar sind. Deshalb mag es zweckmäßig sein, die vier Namen (Vor- und Zunamen, Stadt- und Straßennamen) in einer Matrix mit 4 Zeilen und 20 Spalten zusammenzufassen und die Zahlenwerte in einem Vektor. Dazu werden zusammen
9.1 Felder als Datenstruktur
197
100 Bytes benötigt. Für die numerische Matrix eines Patienten kommen im Mittel 920 Bytes hinzu und für die Textmatrix 1000 Bytes. Damit werden im Mittel pro Patient ca. 2 K-Bytes (K = 1024) benötigt, d. h. in einem 32 K-Byte Arbeitsbereich können im Mittel die Angaben für 16 Patienten untergebracht werden. Die gesamte Datei für 5000 Patienten nimmt eine Bibliothek von etwas über 300 Arbeitsbereichen ein. Pro Patienten-„File" benötigen wir bei dieser Organisation 4 Variablennamen für die 4 Felder. Diese Variablen können zu einer Gruppe mit dem Patienten-identifier als Namen zusammengefaßt werden. Da ein Name global für den gesamten Arbeitsbereich gilt, benötigt man im Mittel 64 unterscheidbare Namen, an sich keine übermäßig große Zahl. Trotzdem ist es ein Schönheitsfehler, der überdies das Programmieren von Datei-Verwaltungsroutinen erschwert, daß gleichartige Strukturen (die einzelnen Matrizen) in den einzelnen Gruppen verschieden benannt werden müssen. Es wäre wesentlich einfacher und eleganter, wenn gleichartige Strukturen in den einzelnen files gleich benannt werden könnten und ihre Unterscheidung nur durch den Gruppennamen erfolgen würde, wie es dann der Fall ist, wenn die gesamte Information in einem Feld zusammengefaßt werden kann. Die Gruppenbildung im APL-Arbeitsbereich beeinflußt aber nicht den Gültigkeitsbereich der Variablennamen, sondern erlaubt lediglich, eine durch die Gruppenbildung definierte Menge von Objekten (dies müssen nicht nur Variable sein) als ganzes zu löschen oder in den aktiven Arbeitsbereich zu kopieren. Weiterhin wird eine Namensliste benötigt, um gezielt auf denjenigen Bibliotheks-Arbeitsbereich, in dem sich eine gesuchte Eintragung befindet, zugreifen zu können. Während numerische Daten meistens sehr gut zu Feldern zusammengefaßt werden können, hat man es bei alphanumerischen Daten in der Regel mit Zeichenketten variabler Länge zu tun. Die Zusammenfassung solcher Zeichenketten zu (rechteckigen) Feldern führt nicht nur zu einer unwirtschaftlichen Nutzung des Speichers, sondern bringt für den Benutzer die große Unbequemlichkeit mit sich, daß die einzugebenden Zeichenketten durch Anfügen von Leerzeichen alle auf das einheitliche, maximale Format gebracht werden müssen. Diese Nachteile können vermieden werden, wenn man sich eine Listenstruktur aufbaut, im einfachsten Falle als lineare Liste (sequential file) 1 . Eine solche Liste besteht in einer Menge von Datenelementen, auf der eine lineare Ordnung besteht, d. h. fiir jedes Element der Liste — außer dem ersten und dem letzten — gibt es genau einen Vorgänger und einen Nachfolger. Die Datenelemente selbst können Skalare, Zeichenketten oder Felder sein. Wenn alle Listenelemente gleiches Format haben, dann kann eine solche Liste natürlich als Feld definiert 1
In der USAI Standard COBOL - Definition wird der Begriff Liste für Dateien reserviert, die als sogenannte Zeiger-Listen organisiert sind.
9. Dateien
198
werden. Der allgemeinere und wichtigere Fall ist aber der, bei dem das Format der Listenelemente variabel ist. Eine solche Liste kann man sich als ein System •von zwei Feldern aufbauen. Das eine der beiden Felder ist ein Vektor, der Datenvektor genannt sei. In ihm werden die Komponenten der Listenelemente in ihrer gegebenen Ordnung in linearer Folge abgespeichert. Sind die Listenelemente Felder höheren Rangs, so werden sie durch die AufreihungsOperation (ravel) in einen Vektor verwandelt (dies isz ohnehin die Form, in dem sie intern gespeichert sind, so daß die Aufreihungs-Operation keine zusätzliche Ausführungszeit kostet). Das zweite Feld ermöglicht den Zugriff zu den einzelnen, im Datenvektor gespeicherten Listenelementen. Wir wollen dieses Feld das Adreßbuch nennen. Im einfachsten Falle kann der Aufbau einer solchen Liste die in Abb. 9-1 angedeutete Form haben. Hier besteht das Adreßbuch aus einer Matrix, die zwei Spalten und soviele Zeilen hat, wie es Listenelemente gibt. Die erste Eintragung in jeder Zeile des Adreßbuchs gibt den Index der Stelle im Datenvektor an, an der die erste Komponente des betreffenden Listenelements gespeichert ist. Man nennt diese Indexzahl auch einen Zeiger. Die zweite Zahl gibt die Länge des Datenvektors eines jeden Listenelements an. Adreflbuch
Datenvektor
Abb. 9-1. Speicherung von Datenelementen variabler Länge in einer linearen Liste. Der Zugriff erfolgt über ein Adreßbuch.
An sich könnte man einwenden, daß die Angabe der Länge eines Listenelements im Adreßbuch nicht notwendig ist, da sich diese jeweils aus der Differenz des Zeigers zum Nachfolger und des eigenen Zeigers ergibt. Normalerweise soll eine solche Listenorganisation jedoch ermöglichen, die Elemente einer Liste umzusor-
9.1 Felder als Datenstruktur
199
tieren oder einzelne Elemente einzufügen oder zu löschen. Wird nun ein Listenelement durch seinen Anfangszeiger und seine Länge gekennzeichnet, so muß die Reihenfolge, in der die Listenelemente im Datenvektor gespeichert sind, nicht mit der Ordnung dieser Elemente in der Liste übereinstimmen, sondern diese Ordnung wird allein durch die Folge der Eintragungen im Adreßbuch bestimmt. Dies vereinfacht die Verwaltung solcher Listen erheblich. Soll zum Beispiel in der Liste Abb. 9-1 das erste mit dem dritten Element vertauscht werden, so genügt es, die Eintragungen in der ersten und der dritten Zeile des Adreßbuchs miteinander zu vertauschen. Oft besteht ein Teil eines Listenelements aus einer Komponenten, die den Schlüssel für einen Such- oder Sortiervorgang darstellt. Diese Schlüssel- oder Leitinformation kann man z. B. jeweils zur ersten Komponenten jedes Listenelements machen (in Abb. 9-1 sind dies die schraffierten Komponenten). Es ist meist aber wesentlich zweckmäßiger, den Schlüssel als zusätzliche Eintragung in die entsprechende Zeile des Adreßbuchs zu schreiben, da dies Suchvorgänge vereinfacht. Läßt man ferner als Listenelemente auch Felder vom Rang > 1 zu oft die angemessenste Form der Strukturierung —, so muß an geeigneter Stelle die Dimensionsangabe eines Feldes gespeichert werden. Auch hierfür ist das Adreßbuch der geeignete Ort. Eine Adreßbuchzeile wird dadurch aus mehr als zwei Eintragungen bestehen (in Abb. 9 - 1 gestrichelt angedeutet). Abb. 9 - 2 zeigt als Beispiel das Format einer Adreßbuchzeile für den Fall, daß die Listenelemente maximal den Rang 2 haben können. Am einfachsten ist es in diesem Falle, alle Elemente grundsätzlich als Matrizen zu definieren. Elemente wie Zeichenketten, die von Hause aus Vektoren sind, werden dann als Matrix mit nur einer Zeile deklariert. Schlüssel
Spaltendimension
Zeilendimension
Anfangszeiger
Endzeiger
Abb. 9 - 2 . Adreßbuchzeile fur Datenelemente vom Rang < 2
Selbstverständlich besteht weiterhin das Problem, daß man auch in dem Datenvektor nur Komponenten des gleichen Datentyps speichern kann. Soll eine solche Liste also alphanumerische Elemente aufnehmen können, so müssen grundsätzlich alle Daten - auch Zahlenwerte - als Daten vom Typ CHARACTER deklariert werden. Immer dann, wenn mit diesen Zahlenwerten gerechnet werden soll, müssen diese erst von einer Zeichenkette in eine Zahl konvertiert werden. Hierzu ist die Elementarfunktion BERECHNEN (execute) äußerst nützlich (s. Abschnitt 5.5.3). Arbeitet man mit einer APL-Implementierung, die die Funktion BERECHNEN und FORMAT nicht hat, so kann man sich entsprechende Funktionen definieren (s. Aufgaben zu Kapitel 7). In Abschnitt 9.3 werden wir Funktionen zum Aufbau und zur Verwaltung von Listen angeben.
200
9. Dateien
Hat eine Programmiersprache den Variablentyp LISTE und ist es möglich, daß die Elemente einer Liste wieder Listen sein können, so steht dem Benutzer bereits eine recht flexible Datenstruktur zur Verfügung, die für viele Anwendungsfälle ausreicht. Andere Strukturen — wie zum Beispiel Zeigerlisten (eine sehr häufig benutzte Technik zum Aufbau von Baumstrukturen) — kann man sich natürlich dann immer noch gesondert programmieren. Neuere APL-Implementierungen wie APL*PLUS und APLSV stellen dem Benutzer Systemfunktionen zum Aufbau von Listen zur Verfügung. Das APLSVSystem ist flexibler und leistungsfähiger als das APL*PLUS-System, da es dem Benutzer erlaubt, Listenelemente an jeder beliebigen Stelle einzufügen und zu löschen. In APL*PLUS kann ein neues Element immer nur an das Ende der bisherigen Liste gesetzt werden, und nur das letzte Element kann jeweils gelöscht werden. Dies entspricht nicht der üblichen Auffassung von einer Listenstruktur. Außerdem werden in APLSV mnemonische Kommandos zum Aufbau und zur Verwaltung einer Liste verwendet, während diese in APL*PLUS die syntaktische Form der Systemfunktionen haben. Wir werden im folgenden Abschnitt daher als die beispielhaftere Lösung das APLSV-System besprechen.
9.2 Das Listenverwaltungs-System von APLSV APLSV {imAPL Shared Fariable System) stellt eine Reihe von SchlüsselwortAnweisungen zum Aufbau und zur Verwaltung von linearen Listen zur Verfügung. Diese Anweisungen sind in Tab. 9-1 zusammengestellt und sollen im folgenden in ihrer Wirkung kurz besprochen werden. Tab. 9-1. APLSV-Anweisungen zum Aufbau und zur Verwaltung von Listen
Syntaktische Form
Funktion
(Dimensionsangabe) CREATE '(Listenname)'
Erzeugung einer Liste
USE '(Listenname)'
Aktivierung einer Liste
'(Listenname)' AT (Listenelement-Index)
Element-Designator
(Element-Designator) SET '(Listenelement)'
Einspeichern eines Listenelements
R -e GET (Element-Designator)
Auslesen eines Listenelements
EXIST (Element-Designatoren)
Anfrage nach der Existenz von Listenelementen
ERASE (Element-Designator)
Löschen eines Listenelements
RELEASE '(Listenname)'
Deaktivierung einer Liste
DELETE '(Listenname)'
Löschen einer Liste
201
9.2 Das Listenverwaltungs-System von A P L S V
Die Dimensionsangabe in der dyadischen Funktion C R E A T E besteht aus einem Vektor mit drei Elementen. Die erste Zahl gibt die maximale Anzahl der Elemente an, die die Liste aufnehmen können soll. Die zweite Zahl gibt die Größe der Speichereinheiten (chunks) an, und die dritte Zahl gibt die Anzahl der Speichereinheiten an, die insgesamt für die Liste reserviert werden sollen. Ein Listenelement kann mehrere Speichereinheiten einnehmen, eine Speichereinheit kann aber nicht mehr als ein Listenelement aufnehmen (selbst dann, wenn mehrere Listenelemente in einer Speichereinheit Platz fänden). Gibt der Benutzer den leeren Vektor ( i 0 ) als linksstelliges Argument von C R E A T E an, so reserviert das System automatisch 110 Speichereinheiten mit je einer Kapazität von 550 Bytes. Wir nennen den zur Aufnahme eines Listenelements reservierten Speicherplatz eine Zelle. Eine Zelle kann damit leer sein, oder sie kann ein Listenelement enthalten. Die Funktion USE gibt dem Benutzer Zugriff zu einer existierenden Liste. Es ist möglich, auf die Listen anderer Benutzer zuzugreifen. In diesem Falle muß vor dem Listennamen die Benutzernummer des „Eigentümers" der Liste stehen. Ein Listenelement wird bezeichnet durch Aufruf der Funktion A T mit dem Listennamen (als Zeichenkette) als linksstelligem und dem Zellen-Index als rechtsstelligem Argument. Der Zellen-Index gibt an, an welcher Stelle in der linearen Liste das betreffende Element steht. Das rechtsstellige Argument von A T kann auch ein Vektor von Indexzahlen sein, wenn mehrere Zellen gleichzeitig bezeichnet werden sollen. Dies kann beim Aufruf der Funktion E X I S T der Fall sein. SET setzt ein bezeichnetes Listenelement in die Liste ein. Das Listenelement selbst (die einzutragende Information) wird dabei als Zeichenkette angegeben (als rechtsstelliges Argument von SET). Der Aufruf der monadischen Funktion GET führt dazu, daß das bezeichnete Listenelement der Funktion als Wert zugewiesen wird. Ein Element kann aus der Liste entfernt (gelöscht) werden, indem die monadische Funktion E R A S E mit dem Element-Designator als Argument aufgerufen wird. Will man wissen, welche Listenelemente tatsächlich existieren, so ruft man die monadische Funktion EXIST auf. Das System weist dieser Funktion als Wert dann einen Vektor mit ebensovielen Elementen zu, wie im Designator Indexzahlen angegeben wurden. Diese Elemente haben die Werte: 0 für leere Zellen, 1 für beschriebene Zellen,
für nicht existierende Zellen
(wird durch die CREATE-AnWeisung eine Liste mit k Zellen erzeugt, dann bezeichnet jeder Zellenindex > k eine nicht existierende Zelle). Durch die RELEASE-Anweisung wird eine Liste deaktiviert (aber nicht zerstört), während durch die DELETE-Anweisung die bezeichnete Liste zerstört (gelöscht) wird.
202
9. Dateien
Beispiel: 100 500 20 CREATE 'ADRESSLISTE' USE 'ADRESSLISTE' ('ADRESSLISTE' AT 5) SET 'SPARRHAERMLINGWEG 16' EXIST 'ADRESSLISTE' AT 1 2 3 4 5 10 100 200 0 0 0 0 1 0 0 "1 GET 'ADRESSLISTE' AT 5 SPARRHAERMLINGWEG 16 ERASE 'ADRESSLISTE' AT 5 EXIST 'ADRESSLISTE' AT 5 0 Das hier dargestellte Listenverwaltungs-System von APLSV erklärt noch nicht das Attribut shared variable (shared = von mehreren benutzt), durch das APLSV seinen Namen erhalten hat. Shared variable bedeutet, daß verschiedene Benutzer Zugriff zu bestimmten Variablen oder Listen haben können. Dies geschieht in der Regel dadurch, daß ein Benutzer einem anderen Benutzer anbietet, gewisse Variable seines aktiven Arbeitsbereichs (die global oder lokal sein können) mit ihm zu teilen. Ein solches Abkommen wird dadurch vollzogen, daß der Adressat eines solchen Angebots nun seinerseits das gleiche Angebot dem Anbieter macht. In diesem Falle können beide Parteien den genannten Variablen Werte zuweisen. Die Einzelheiten des „Abkommens", die Regeln, in welcher Reihenfolge beide Parteien die Werte gemeinsam benutzter Variablen verwenden oder verändern können, werden durch ein Zugriffsprotokoll (accessing protocol) geregelt, das zunächst der Anbieter aufstellt. Der Adressat des Angebots kann aber auch seinerseits die gemeinsame Verwendung einer Variablen durch ein Protokoll einschränken, so daß im Endeffekt nur das möglich ist, was keine der beiden Seiten ausschließt. Ohne solche Regelungen wäre das System sehr leicht Konflikten ausgesetzt, die bei gleichläufigen Prozessen (concurrent processes) entstehen können, und zwar immer dann, wenn beide Seiten gleichzeitig zu einer gemeinsam benutzten Variablen zugreifen. Die gemeinsame Benutzung von Variablen zwischen mehr als zwei Benutzern kann dadurch organisiert werden, daß ein Benutzer als „Kommunikationszentrum" eingesetzt wird und nun bilaterale Abkommen mit allen anderen Benutzern trifft. Benutzer können nicht nur die Teilnehmer in einem APL-TimesharingSystem sein, sondern auch andere Operatoren des Betriebssystems wie Compiler, Ein/Ausgabe-Operatoren, usw. Dadurch wird auch das in älteren APL-Implementierungen bestehende Problem gelöst, Daten auf anderen Wegen als über die Terminal-Schreibmaschine (oder den Terminal-Fernschreiber) ein- oder ausgeben zu können. Bei einem System, das als Basis für echte Anwendungssysteme dienen soll, muß es zumindest möglich sein, Daten auch von Lochkarten, Magnetbändern oder anderen Datenträgern einlesen oder auf solche Träger ausgeben zu können.
9.3 Die Programmierung eines Systems zur Listenverwaltung
203
Es muß aber noch einmal betont werden, daß in solchen Fällen alle Protokollfragen wohlüberlegt und eindeutig geregelt werden müssen, um unvorhergesehene Sackgassen-Situationen oder andere Konflikte zu vermeiden. Es würde aber bei weitem den für dieses Buch gesetzten Rahmen sprengen, auf solche Fragen hier näher einzugehen.
9.3 Die Programmierung eines Systems zur Listenverwaltung Im folgenden sollen einige Funktionen angegeben werden, die es dem Benutzer auch in Systemen wie zum Beispiel APL\360 oder APL*CYBER erlauben, Dateien in der Form von linearen Listen aufzubauen und zu verwalten. Dadurch kann einem wesentlichen Mangel älterer APL-Implementierungen abgeholfen werden. Die Funktionen, die wir benutzen wollen sind:
'(Eintragung)'
CREATE '(Listennamen)'
: erzeugt eine (leere) Liste des bezeichneten Namens
PUT Element-Designator
: trägt das bezeichnete Element ein
GET Element-Designator
: liest das bezeichnete Element aus
DROP Element-Designator
: löscht das bezeichnete Element
EXISTS ''
: liefert den Indexzahlen-Vektor aller Elemente (records)
Wir erkennen die starke Ähnlichkeit unseres Systems mit dem Listenverwaltungssystem von APLSV. Allerdings müssen wir die erzeugten Listen als globale Variablen des aktiven Arbeitsbereichs definieren. Es gibt damit keine besonderen Aktivierungs- oder Deaktivierungs-Anweisungen; es gibt aber auch keinen besonderen Schutz für unsere Dateien. Eine Liste wird zunächst als leerer Vektor kreiert, der dann mit jeder Eintragung wächst bzw. mit jeder Löschung wieder schrumpft. Wir nutzen damit die völlige Dynamisierung der Felddimensionen in APL aus, und eine Deklaration der maximalen Listengröße wie in APLSV ist nicht notwendig (unsere Listen sind in ihrer Größe naturgemäß durch die Größe eines Arbeitsbereichs beschränkt). Für den Element-Designator wählen wir die syntaktische Form Element-Designator ::= '(Listennamen) [(Element-Index)]'. Um eine möglichst große Flexibilität mit einer möglichst unkomplizierten Listenverwaltung zu verbinden, lassen wir als Listenelemente nur Zeichenketten, d. h.
204
9. Dateien
Vektoren vom Datentyp CHARACTER zu. Es ist jedoch eine Kleinigkeit, die nachstehend angegebenen Funktionen so zu modifizieren, daß Matrizen als Listenelemente auftreten dürfen. Allerdings müssen dann Vektoren als Matrizen mit nur einer Zeile deklariert werden, was zu einer gewissen Umständlichkeit führt. Es sei daher dem Leser überlassen, falls er es wünscht, eine solche Modifikation des Systems vorzunehmen. Intern werden die Komponenten der Listenelemente zu einem einzigen Datenvektor aufgereiht, unabhängig davon, welche Struktur die Listenelemente bei der Ein- und Ausgabe haben. Bei der Eingabe wird für jedes Listenelement ein Deskriptor angelegt, in dem die folgenden Informationen enthalten sind: ELEMENTINDEX, ZEIGER AUF ELEMENTANFANG IM DATENVEKTOR, DIMENSION. Diese Deskriptoren bilden die Zeilen einer Matrix, die wir die Deskriptoren-Tafel (oder kurz ,Tafel') nennen wollen. Die Reihenfolge der Eintragungen ist beliebig, da wir in den Funktionen GET und DROP nach der im Element-Designator angegebenen Indexzahl suchen. Bei der Ausfuhrung der Funktion DROP werden die Daten des Listenelements aus dem Datenvektor und der zugehörige Deskriptor aus der Tafel entfernt, und beide Listen werden entsprechend komprimiert. Ein ,garbage collection'-Problem gibt es daher nicht. Der hauptsächliche Aufwand liegt bei unseren Funktionen darin, daß wir dem Benutzer gestatten, beliebige Listennamen nach eigener Wahl einzuführen und die Listenelemente dann durch Indizierung des Listennamens zu bezeichnen. Die Deskriptorentafel wird intern dann durch den Namen XTX (Listennamen) bezeichnet (wir nehmen an, daß die Verbindung des Listennamens mit dem Präfix XTX ungebräuchlich genug ist, um vom Benutzer nicht zufällig anderweitig als Namen verwandt zu werden). Datenvektor und Deskriptorentafel sind selbstverständlich globale Variablen. V CREATE X [1] JLX,' OpiO' n ERZEUGT LEEREN DATENVEKTOR [2] i'XTX',X,'0 3 p i 0 ' n ERZEUGT TAFEL ¥ 9 A PUT [1] [2] [3] [4] [5]
X;N;P;S;ID S X i ' [ ' A AUFFINDEN DES TRENNZEICHENS [ ' , S 4 - " H X r AUSMASKIEREN VON N ID -e (S-l)-t-X n AUSMASKIEREN DES LISTENNAMENS i ' -* ENDxi N e X T X T , I D , ' [ ; l ] ' p AUSSPRUNG WENN ZELLE SCHON BELEGT i ' P ^ l + p ' . I D fl P=ANFANGSZEIGER AUF DATENLISTE
9.3 Die Programmierung eines Systems zur Listenverwaltung
[6] [7] [8] [9] [10]
205
itlD,' ',ID,',A' q EINTRAG IN DATENVEKTOR i'XTX',ID,' XTX',ID,', [l]N,P,p A' q UPDATEN DER TAFEL • -e- N;' ENTERED' q VOLLZUGSMELDUNG ->0 END: 'RECORD ALREADY ENTERED'
9 R + GET X;N;S;ID;RN [1] S ^ X i ' [ ' q AUFFINDEN DES TRENNZEICHENS [ [2] *'N+- ' . s r i + X q AUSMASKIEREN VON N [3] ID -«- (S-l)+X p AUSMASKIEREN DES LISTENNAMENS [4] j>'RN •«- X T X ' , I D , ' [ ; l ] i N ' q SUCHE NACH N [5] i'-*-ENDxiRN>l+(pXTX',ID,') [ 1 ] ' q AUSSPRUNG WENN N NICHT GEFUNDEN [6] i ' R ^ XTX',ID,'[RN;3]t(XTX',ID,'[RN;2]-l)+',ID q GE SUCHTE EINTRAGUNG [7] [8] END: 'RECORD NOT FOUND'
V DROP [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14]
X;D;N;P;S;DD;ID;RN S X i ' [ ' q q AUFFINDEN DES TRENNZEICHENS [ ' . s r i + X q AUSMASKIEREN VON N ID ( S - l ) t X q AUSMASKIEREN DES LISTENNAMENS i'RN XTX',ID,'[;1] i N ' q SUCHE NACH N ->ENDxiRN>( p XTX',ID,'[;l] q AUSSPRUNG WENN N NICHT GEFUNDEN 4L'P+- XTX',ID,'[RN;2]-1' q P ZEIGT AUF ENDE DES VORHERGEHENDEN EINTRAGS i'D XTX',ID,' [RN;3] ' q D=LAENGE DES ZU LOESCHENDEN EINTRAGS iID,' (Pt',ID,'),(XTX',ID,'[RN;3]+P)+',ID q LOESCHT EINTRAG IN LISTE i ' D D -e ( P XTX',ID,')[1]-RN' q RESTLAENGE DER TAFEL i'XTX'.ID,'[RN+iDD;2] XTX',ID,'[RN+iDD;2]-D' q UP DATEN DER ZEIGER i'XTX'.ID,' - 8x~C 4NAME, ~1+',NAME i ' L A B E L 8x~C FORSTACK 3 + FORSTACK V
Manche Autoren sind der Meinung, daß man auf die FOR-Konstruktion ganz verzichten kann, da man ja immer explizit eine Laufvariable einfuhren und in einer REPEAT- oder WHILE-Schleife abprüfen kann. Obwohl dies im Prinzip natürlich richtig ist, sind wir nicht der Meinung, daß man auf die FOR-Konstruktion verzichten sollte. Erstens bedeutet die Notwendigkeit, Laufvariable explizit einführen und inkrementieren oder dekrementieren zu müssen, einen gewissen Abstieg von der Höhe einer Programmiersprache, und zweitens werden dabei erfahrungsgemäß bei stark verschachtelten Schleifen leicht Fehler gemacht, indem eine Initialisierung oder Inkrementierung an der falschen Stelle vorgenommen wird. 10.3.3 Die DAHL-Konstruktionen Während wir für die REPEAT-UNTIL- und für die FOR-Konstruktion sehr elegante Lösungen finden konnten, geraten wir in ernsthafte Schwierigkeiten bei dem Versuch, die gleiche Methode zur Realisierung der WHILE-Konstruktion anzuwenden. Der Grund liegt darin, daß bei der WHILE-Konstruktion die Weiche bereits am Anfang eines Programmblocks gestellt wird und nicht erst,
222
10. Strukturiertes Programmieren in APL
wie bei REPEAT-UNTIL, am Ende. Ein Interpretierer hat am Beginn eines Blockes jedoch keine Information darüber, wo der Block endet, es sei denn, der Block wurde bereits mindestens einmal durchlaufen. Nun sind in der Praxis die Fälle, wo ein Block gar nicht durchlaufen werden mag, lange nicht so häufig, wie man aus der Beliebtheit der WHILE-Konstruktion schließen könnte. Jedoch muß man diese Möglichkeit vorsehen (andernfalls könnte man auf die WHILE-Konstruktion auch ganz verzichten). Es wurde daher auch mehrfach vorgeschlagen, einen ,look-ahead'-Mechanismus in den APL-Interpretierer einzubauen [15; 19], um ihn auf diese Weise mit einer Fähigkeit zu versehen, über die sonst nur ein Compiler verfugt. Wir wollen jedoch APL nicht erweitern, sondern das gegebene Potential in der bestmöglichen Weise nutzen. Daher besteht für uns die einzige Möglichkeit darin, in diesem Falle zu einer Kombination von speziellen Funktionen und Marken Zuflucht zu nehmen. Um den Anreiz für die Benutzung dieser etwas umständlicheren Konstruktion zu vergrössern (die Mehrzahl der Fälle kann ohnehin mit dem REPEAT-UNTIL abgedeckt werden), bauen wir die WHILE-Konstruktion zu einer leistungsfähigeren, von DAHL vorgeschlagenen und von KNUTH sehr empfohlenen Konstruktion aus [5], die wir die DAHL-Konstruktion nennen wollen. DAHL schlägt die folgende Konstruktion vor LOOP: (erster Block);WHILE C: (zweiterBLOCK);REPEAT , mit der Bedeutung, daß für ~C der zweite Block einschließlich der REPEATAnweisung übersprungen wird. Diese Konstruktion hat zwei entscheidende Vorteile [20]: (1) Sie stellt eine sehr elegante Lösung des .^Vz-Problems" dar, bei dem eine Iteration nach ihrer Initialisierung aus zwei Teilen besteht, dem eigentlichen Iterationsschritt und einem folgenden Schritt, in dem die Parameter für den nächsten Iterationslauf berechnet werden. (2) Sie schließt sowohl die WHILE-Konstruktion ein (wenn der erste Block leer ist) als auch die REPEAT-Konstruktion (wenn der zweite Block leer ist). Um im Einklang mit der APL-Syntax zu bleiben, müssen wir die DAHL-Konstruktion umformulieren. Wir schlagen dazu die folgende Form vor LOOP
+ WHILE C DOTO END
END:
POOL
10.3 Eine moderne Kontrollstruktur für A P L
223
Bei der Wahl der Schlüsselwörter kann der Benutzer natürlich seinem eigenen Geschmack folgen. Wer den Begrenzer POOL (Umkehrung von L O O P ) nicht mag, mag ein anderes Wort (wie z. B. I T E R A T E ) wählen. Das DAHLsche REPEAT können wir jedoch nicht wählen, da wir die REPEAT-UNTIL-Konstruktion in jedem Falle beibehalten wollen. END ist ein Markennamen (auch hier ist der Benutzer frei in seiner Wahl). Werden mehrere DAHL-Konstruktionen verschachtelt, so müssen die einzelnen Marken unterschiedlich benannt werden; z. B. mit END1, END2 enthalten
Die WHILE-Konstruktion ist in der DAHL-Konstruktion in der Form LOOP WHILE C DOTO END
END: + POOL Wir geben nun die Funktionsdefinitionen für die DAHL-Konstruktion an.
[1]
V L x- LOOP LOOPSTACK ^ ( l + ( i 27)f l + i 11), L O O P S T A C K , L + V
[1]
V CLABEL C DOTO LABEL CLABEL-«- C,LABEL V
iQ
V DEST WHILE CLABEL [1] CLABEL [ i 1] /0,DEST ( ~ C L A B E L [ i 1])/CLABEL [ l + i 1] + 1 [ 2 ] LOOPSTACK 1| LOOPSTACK V [1]
V BACK BACK
POOL H LOOPSTACK
V
10.3.4 IF-ELSE und CASE Der Vollständigkeit halber wollen wir noch die Möglichkeiten aufzeigen, die sich für eine Realisierung der IF-ELSE-Konstruktion und der CASE-Konstruktion bieten. In beiden Fällen stehen wir demselben Problem gegenüber wie bei der WHILE-Konstruktion: Eine Verzweigung muß an einer Stelle ausgeführt werden, an der der Interpretierer noch keine Kenntnis davon haben kann, wo eine Funktion, durch die das Sprungziel ermittelt werden könnte, steht. Damit
224
10. Strukturiertes Programmieren in APL
müssen wir auch hier mit einer Kombination von Funktionen und Marken arbeiten. Für die IF-ELSE-Konstruktion schlagen wir deshalb vor -*-IF
C
DOTO
LABEL1 | Block 1
LABEL 1: -* ELSE DOTO LABEL2 } Block 2 LABEL2:
FI
Block 2 mag leer sein. FI ist eine „dummy"-Variable, die aber aufgerufen werden muß, da die mit LABEL gekennzeichnete Zeile weder eine auszuführende Programmzeile enthalten darf (sie wird nämlich u. U. übersprungen), noch leer sein darf (da sonst die Marke einfach gelöscht wird). Die Konstruktion kann durch die folgenden Funktionen und Variablen verwirklicht werden (DOTO ist die gleiche Funktion wie die auf S. 223 angegebene). V LABEL [ 1 ] LABEL
IF CLABEL ( ~ CLABEL [i 1 ] )/1+CLABEL [ 1 +i 1 ]
V
ELSE 0 FI -«- 10 Für die CASE-Konstruktion wählen wir folgende Form -> CASE C + ON DOTO
LABEL 1
LABEL 1: -»- ON DOTO LABEL2
LABEL2:
ESAC
ON DOTO LABELk
10.3 Eine moderne Kontrollstruktur für APL
225
Zur Realisierung schlagen wir die folgenden Funktionen vor.
[1]
V L -«- CASE C CASESTACK - START
[2]
REPEAT
n SUCHT NACH DEM ERSTEN ELEMENT > V
[3] [4] [5]
-> IF (ID [ I I >V) DOTO END ID[J] I D [ I ] n SPEICHERT DIESES ELEMENT IN ID[J] AB RESUME 'MOVEJ' a GIBT KONTROLLE AN MOVEJ AB
[6] [7] [8] [9]
END: ->- FI I^I+l UNTIL I^J -* DETACH
SICHERHEITSHALBER EINGESETZT
V
[1] [2] [3] [4] [5]
V MOVEJ -* START REPEAT q SUCHT NACH DEM ERSTEN ELEMENT < V -»- IF ( V > I D [ J ] ) DOTO END ID [ I ] ID [J] q SPEICHERT DIESES ELEMENT IN ID [ I ] AB RESUME 'MOVEI' n GIBT KONTROLLE AN MOVEI AB
[6] [7] [8] [9]
END: + 0 J J-1 ^ UNTIL I>J DETACH SICHERHEITSHALBER EINGESETZT V
10. Strukturiertes Programmieren in A P L
232
Es ist nun nichts naheliegender, als die rekursive Anwendung der Partitionierung auf die Liste in einem rekursiven Programm auszufuhren [24], Dazu schreiben wir die rekursive Funktion SORT. Schließlich stellen wir uns noch ein kleines „Hauptprogramm" QSORT her, das es erlaubt, die zu sortierende Liste per mme zu referieren, so daß das Programm auf jede beliebige Liste mit beliebigem Namen angewandt werden kann und man am Ende die sortierte Liste unter dem gleichen Namen erhält.
[1] [2]
V M SORT N;S -* (M>N)/0 a AUSSPRUNG WENN LISTENLAENGE —> n o abhängt: 1
A
iÖo»nM-2.-. no)=
• n
2
A(nM_i,..., no) Wn"m 1M-1 = °
2M_1
.
Die Summation über n M _ 2 führt zu einem Ausdruck l A
2Öo.ji.nM-3
n
o)=
s
A
"M-2-0
1 > undsofort.
Die allgemeine Formel ist A
=
L(]O> —. j L - 2 > j L - L > N M - L - L » —. n o )
1 2
a
=
l - i Ö o ' —Jl-2» nM.L, nM.L.1(..., n0)X
"M-L=0 x w(iL-l2
L
"1+ "+jo) nM-l2M"L
Der schließlich nach M Schritten erhaltene Wert A M ist das gewünschte Resultat X(j). Wir können nun praktisch so vorgehen, daß wir das Argument ( j 0 , . . . , n 0 ) als die Dualzahlendarstellung der Speicheradresse wählen, unter der der entsprechende Wert A L zu finden ist. Wegen der Bijektivität der Funktion, die einen Wert A L . J in einen Wert A L abbildet, kann die Zelle, die A l . J enthalten hat, zum Speichern des Zwischenergebnisses A L benutzt werden („in place computation"). Wir ersehen auch aus dem obigem Schema, daß die Werte am Ende nicht mehr in ihrer natürlichen Ordnung gespeichert sind, wenn sie am Anfang in einer konsekutiven Adressfolge gespeichert waren (oder umgekehrt). Genauer gesagt haben wir am Ende die Zuordnung X(JM-I ,..., jo) ~
A
M Ö O ' •••> J M - I ) ' -
Aufgaben zu Kapitel 10
237
Dies bedeutet, daß am Ende die richtige Ordnung durch einfache Bitreversion erhalten werden kann. Bitreversion bedeutet dabei, daß das erste Bit mit dem letzten, das zweite mit dem zweitletzten, usw., vertauscht wird. Wir sehen ferner, daß auch der Faktor ( j l - i > •••> jo) i m Exponent von W durch Bitreversion aus dem Teilfaktor ( j 0 , . . . , J ' l - i ) im Argument von A L erhalten werden kann. Die Berechnung eines Werts A L kann weiter vereinfacht werden. Sei J die Dualzahl, die aus den Bits in den L - 1 höchsten Stellen des Arguments von A L besteht, und sei K die Dualzahl, die aus den M - L niedrigsten Stellen des gleichen Arguments besteht, d. h. sei J = j 0 , . . . , j L _ 2 und K = n M _ L _ ! , . . . , n 0 . Dann können wir auch A L ( J , j L . i , K) schreiben und erhalten ferner für den Exponenten im Ausdruck für A L -L-l
.M-L
»M-l
Damit läßt sich die Prozedur zur Berechnung eines Vektors von Werten X(j), j = 0, 1,..., N - 1, aus einem Vektor von Werten A(n), n = 0 , 1 , . . . , N - 1, wie folgt in der Form eines Pseudoprogramms angeben DO L FROM 1 TO M DO J FROM 0 TO ( 2 * L - 1 ) - 1 DO K FROM 0 TO (2*M-L) - 1 AL+1(J,0,K) = AL(J,0,K) + AL(J, l,K)xW*(J x2*M-L)
A l + 1 ( J , 1,K) = A l ( J , 0 , K ) - A l (J,1,K)xW*(TX2*M-L) END END END Dabei bedeutet J das „bitrevertierte" J. Insgesamt sind N • log 2 N komplexe Additionen und ^ N(log 2 N -
komplexe Multiplikationen auszufuhren.
Sei p = J , 0 , K und q = J, 1,K. Wenn wir im Augenblick der Einfachheit halber den Exponenten von W fallenlassen, dann haben wir das (vereinfachte) Gleichungspaar für einen „FFT butterfly" A L + i ( p ) = AL(p) + A L ( q ) . W AL+i(q) = AL(p)-AL(q).W . In dem Programm sind diese komplexen Gleichungen durch die entsprechenden Gleichungen für Realteil und Imaginärteil zu ersetzen.
238
10. Strukturiertes Programmieren in APL
Der folgende Graph veranschaulicht die Prozedur für N = 8. Die Kanten symbolisieren dabei eine Multiplikation (entweder mit 1 oder mit W), wobei der Faktor 1 nicht angegeben wird. Die Knoten symbolisieren Additionen bzw. Subtraktionen.
Das Programm sei so zu schreiben, daß mit ihm auch die umgekehrte Transformation A(n)=I VxO)W-Nnj j=o
(DFT)
durchgeführt werden kann. Der Aufruf soll FFT S lauten. S ist eine Steuergröße, für die gilt S= 1 S=~l
für DFT für D F T ' 1 .
Das Datenfeld soll A heißen.
Anhang
A.1 Der APL-Zeichensatz und seine mnemonische Ersetzung
Symbol Mnemo- Bedeutung nische ErSetzung
Symbol Mnemo- Bedeutung nische Ersetzung
?
A bis
Ü) e P
+ i o
e
$QU $OM $EP SRO $TL STA SDR $10 SCI $RT SRU
is?
$TP
*
-
®
SLG SGO SIS
->-
t A A
SAL SMX SMN SUL SDL SLD SDG SDT SDU
k
SUG
a
r L V ¥
QUery OMega EPsilon RhO TiLde TAke DRop 10 ta Circle RoTate (reverse indexed rotate) TransPose asterisk LoG GOto IS ALpha MaX MiN UnderLine DeL Locked Del DownGrade DelTa Delta Underscored (lower case delta) UpGrade
(upper case alphabetics)
Z A bis Z
$AA bis
(lower case alphabetics)
0
_
(numerics)
-
(space)
szz
bis
9
SGE
>
SGT $NE SOR SNR $AN SND
! !
T )