skrip tum 05

64
 uages comp lang uter Skriptum zur Vorlesung TYPSYSTEME Wintersemester 2005/2006 Franz Puntigam Technische Universit¨ at Wien Institut f ¨ ur Computersprachen Programmiersprachen und  ¨ Ubersetzer http://ww w.complang. tuwien.ac.at/fran z/typsysteme.h tml

Upload: mortair-smiley

Post on 18-Jul-2015

131 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 1/64

uages

comp

lang

uter

Skriptum zur Vorlesung

TYPSYSTEME

Wintersemester 2005/2006

Franz Puntigam

Technische Universitat Wien

Institut f ur Computersprachen

Programmiersprachen und Ubersetzerhttp://www.complang.tuwien.ac.at/franz/typsysteme.html

Page 2: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 2/64

Page 3: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 3/64

Inhaltsverzeichnis

1 Begriffsbestimmungen und Uberblick 5

1.1 Was sind Typen? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2 Kategorisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.2.1 Paradigmen und Sprachklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.2.2 Einteilung von Typsystemen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.2.3 Polymorphe Typsysteme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.3 Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2 Einfache theoretische Modelle 13

2.1 Der Lambda-Kalkul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.1.1 Der untypisierte Lambda-Kalkul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.1.2 Der typisierte Lambda-Kalkul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152.1.3 Strukturierte Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.2 Logik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.2.1 Die Pradikatenlogik erster Stufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.2.2 Definite Horn-Klausel-Logik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.2.3 Typisierte logische Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.3 Algebren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2.3.1 Abstrakte Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.3.2 Prozessalgebra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2.4 Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3 Typen in imperativen Sprachen 23

3.1 Datentypen in Ada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233.1.1 Skalare Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233.1.2 Zusammengesetzte Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253.1.3 Unterbereichstypen, abgeleitete Typen und Zeiger . . . . . . . . . . . . . . . . . . . . . . 26

3.2 Prozeduren und Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.2.1 Funktionen und Prozeduren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273.2.2 Inkarnationen und Prozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283.3 Generische Pakete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293.4 Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

4 Modelle polymorpher Typsysteme 31

4.1 Order-sorted Algebras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.1.1 Verbande uber Sortenmengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.1.2 Polymorphe Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4.2 Typinferenz und das Typsystem von ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334.2.1 Typinferenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334.2.2 Typen in ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344.2.3 Ein Typinferenz-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.2.4 Transparenz und Entscheidbarkeit von Typinferenz . . . . . . . . . . . . . . . . . . . . . . 36

4.3 Funktionale Ansatze f ur Subtyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

3

Page 4: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 4/64

4 INHALTSVERZEICHNIS

4.3.1 Einfache Untertypbeziehungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364.3.2 Rekursive Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

4.4 Das PER-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

4.5 Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

5 Typen in objektorientierten Sprachen 41

5.1 Vererbung in Beispielen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415.1.1 Ada 95 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415.1.2 Eiffel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

5.2 Allgemeine Konzepte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.2.1 Varianz von Parametertypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.2.2 Untertypen und Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475.2.3 Subtyping und Verhalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495.2.4 Untertypen und Generizitat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

5.3 Logik und Subtyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525.4 Typen f ur aktive Ob jekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

5.4.1 Prozesstypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535.4.2 Vererbungsanomalie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.5 Eigenschaften ausgewahlter objektorientierter Sprachen . . . . . . . . . . . . . . . . . . . . . . . 555.5.1 Smalltalk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.5.2 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565.5.3 Java und C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565.5.4 Ada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575.5.5 Eiffel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

5.6 Literaturhinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

Literaturverzeichnis 59

Glossar 61

Page 5: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 5/64

Kapitel 1

Begriffsbestimmungen und Uberblick

Typsysteme sind Bestandteile von Programmier-sprachen und konnen als solche nur im Zusammenhang

mit Programmiersprachen sinnvoll betrachtet werden.Dennoch gibt es theoretische Grundlagen von Typsy-stemen, die einheitlich auf viele Sprachen und Sprach-klassen anwendbar sind. Typsysteme entwickelten sichin den letzten Jahren stetig weiter und gewannen zu-nehmend an Bedeutung—ein Prozess, dessen Endenoch nicht absehbar ist. Typen konnen als ein Aspektgesehen werden, der gemeinsame und unterschiedlicheEigenschaften von Sprachen in allen Sprachklassen her-ausstreicht. Das Verstehen von Typsystemen tragt da-her wesentlich zum Verstehen von Programmierspra-chen und der Softwareentwicklung und -wartung imAllgemeinen bei.

1.1 Was sind Typen?

Jeder, der schon einmal eine typisierte Programmier-sprache verwendet hat, weiß ungef ahr, was ein Typist. Allerdings gibt es zahlreiche Definitionen des Be-griffs

”Typ“, die auf den ersten Blick keinerlei Gemein-

samkeiten aufweisen. Fur einen Anwendungsprogram-mierer bedeuten Typen sicher etwas anderes als f ureinen Compilerentwickler oder Programmiersprachen-designer. Folgende Antworten auf die Frage

Was sind

Typen?“ spiegeln verschiedene Interessen und Sichtwei-sen wider:

1. Typen sind Werkzeuge zur Klassifikation von Wer-ten aufgrund ihrer Eigenschaften, ihres Verhaltensund ihrer Verwendungsm oglichkeiten.

Die Zuordnung von Werten (z. B. 1, 2, true undder Implementierung von print) zu Typen (z.B.Integer, Boolean und Procedure) hilft bei derStrukturierung von Programmen und vereinfachtdamit die Programmerstellung. Der Typ einer Va-riablen beschreibt nicht nur welche Werte sie ent-halten kann, sondern auch welche Operationendarauf sinnvoll anwendbar sind.

2. Typen sind Schutzschilder gegen unbeabsichtigtebzw. falsche Interpretation roher Daten.

Bitmuster in Speicherzellen konnen auf verschie-dene Arten (z. B. als Adressen, ganze Zahlen oderGleitkommazahlen) interpretiert werden. Typenschranken die erlaubten Interpretationsmoglich-keiten ein und verhindern dadurch eine Klasse vonProgrammierfehlern.

3. Typen sind Werkzeuge des Softwareentwicklers zur Unterst utzung der Programmerstellung, Weiter-entwicklung und Wartung.

Diese Antwort erweitert die vorhergehenden Ant-worten aus der Sicht eines Softwareentwicklers, der

neben der Erstellung vor allem auch die Weiter-entwicklung und Wartung von Software im Augehat. Wichtige Konzepte in objektorientierten Pro-grammiersprachen (z. B. Klassen und Vererbung)stehen zum Teil in enger Beziehung mit Typsyste-men.

4. Typen schr anken Ausdr ucke syntaktisch ein, sodass Kompatibilit at zwischen Operatoren und Ope-randen zugesichert wird.

In vielen Programmiersprachen braucht nicht je-der Operator mit allen Operanden kompatibelsein. Typuberprufungen stellen sicher, dass Ope-

ratoren und Operanden vertraglich sind und dieOperationen daher tatsachlich ausgef uhrt werdenkonnen. Dadurch wird eine mogliche Fehlerquel-le ausgeschaltet. Falls die Typuberprufung vomCompiler vorgenommen wird, kann das Programmmoglicherweise effizienter ausgef uhrt werden.

5. Typen bestimmen Speicherauslegungen f ur Werte.

Aus der Sicht eines Compilerentwicklers ist es not-wendig zu wissen, welchen Speicherplatz ein Wertbelegt und wie dieser strukturiert ist. Typen sindeine gute Quelle f ur diese Information.

6. Typen legen Verhaltens-Invarianten fest, die alleInstanzen der Typen erf ullen m ussen.

Page 6: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 6/64

6 KAPITEL 1. BEGRIFFSBESTIMMUNGEN UND UBERBLICK

Typen dienen auch der Verifikation. Dadurch, dassalle Instanzen (= Konstanten, Objekte, Werte) ei-nes Typs dasselbe invariante (= unveranderliche)

Verhalten zeigen, wird die Verifikation vereinfacht.Invarianten sind eine Grundlage f ur das Ersetzbar-keitsprinzip in der objektorientierten Programmie-rung.

7. Ein Typsystem ist eine Menge von Regeln, die jedem Ausdruck einen eindeutigen allgemeinsten Typ zuordnet. Dieser Typ beschreibt die Menge al-ler Umgebungen, in denen der Ausdruck vorkom-men kann und eine Bedeutung hat.

Vor allem in funktionalen Programmiersprachenwerden die Typen von Ausdrucken oft nicht ex-plizit angegeben. Trotzdem haben einige dieser

Sprachen ein ausgeklugeltes Typsystem, das es er-laubt, jedem Ausdruck einen eindeutig berechen-baren Typ zuzuordnen. Der vom Compiler berech-nete Typ eines Ausdrucks (z. B. einer Funktion)erlaubt dem Programmierer, Fehler zu erkennenund ermoglicht Optimierungen.

Die ersten drei Antworten stammen am ehesten vonBenutzern einer Sprache bzw. eines Compilers, die zweinachsten von Compilerentwicklern und die beiden letz-ten von Leuten, die sich mit der Theorie der Program-mierung und Programmiersprachen beschaftigen. Ei-ne gute Programmiersprache kann nur das Produkt ei-

ner Zusammenarbeit dieser drei Disziplinen sein. EineSprache, die aus der Sicht des Programmierers großeVorteile bietet, aber nur sehr ineffizient implementiertwerden kann oder keine wohldefinierte Semantik hat,wird langerfristig genausowenig Erfolg haben wie eine,die effiziente Implementierungen eines schonen theore-tischen Modells erlaubt, aber praktisch kaum verwend-bar ist. Die Frage, welche der oben genannten Defi-nitionen die richtige oder die wichtigere sei, ist daheruberflussig: Ein Typkonzept soll den Definitionen ausallen Sichtweisen entsprechen. Falls eine davon nichtvollstandig erf ullt werden kann, muss es zumindest ei-

ne uberzeugende Erklarung daf ur geben. Tatsachlichkonnen sich einige der genannten Definitionen teilwei-se widersprechen, so dass jedes Typsystem einen Kom-promiss darstellt.

Als generelles Ziel f ur den Einsatz von Typsystemenin Programmiersprachen wird haufig die verbesserteLesbarkeit, Sicherheit und Effizienz von Programmengenannt. Die obigen Antworten Nr. 1, 3 und 6 zielenauf verbesserte Lesbarkeit ab, die Antworten Nr. 2, 4, 6und 7 auf erhohte Sicherheit und die Antworten Nr. 4,5 und 7 auf verbesserte Effizienz von Programmen. Da-neben hat Antwort Nr. 3 zusammen mit 7 auch effek-tivere Softwareentwicklungsprozesse zum Ziel.

Im folgenden Text wird hauptsachlich diese Definiti-on des Typbegriffs implizit verwendet werden:

Ein Typ beschreibt eine Menge von Instanzen.

Diese Definition ist so allgemein gehalten, dass sie jederder weiter oben angef uhrten Definitionen entspricht.Sie gibt nicht an, auf welche Art die Menge beschriebenist, welche Elemente sie enthalt, in welcher Beziehungdiese Elemente zueinander stehen und welche gemein-same Eigenschaften sie haben. Genausowenig legt siedie Relationen zwischen den Typen eines Typsystemsfest. Ein konkretes Typsystem wird durch die Festle-gung dieser offenen Punkte definiert.

Wenn man vom Typ eines Wertes oder Objektsspricht, meint man die Menge der Instanzen zu denender Wert bzw. das Objekt zahlt. Die vielf altige Ver-wendung des Typ-Begriffs hat jedoch zu einer etwas

”schlampigeren“ Terminologie gef uhrt. So spricht man

haufig vom ”Typ einer Variablen“ und meint damitEinschrankungen der Werte oder Objekte, die durchdiese Variable bezeichnet werden konnen. Wenn man esgenau nimmt ist der

”Typ einer Variablen“ eine Menge

von Variablennamen; das was salopp”

Typ einer Varia-blen“ genannt wird ist genaugenommen eine

”Typein-

schrankung auf einer Variablen“. In diesem Skriptumwird oft diese saloppe Terminologie verwendet, da sienaturlich klingt und in der Regel nicht zu Mehrdeutig-keiten f uhrt.

1.2 Kategorisierung

Die folgenden Einteilungen von Programmiersprachenaufgrund ihrer Paradigmen und Typsysteme solleneinen systematischen Uberblick uber die zahlreichenMoglichkeiten f ur den Entwurf von Programmierspra-chen geben, wobei Typen im Mittelpunkt stehen.

1.2.1 Paradigmen und Sprachklassen

Weltweit gibt es zwei- bis dreitausend Programmier-sprachen, und standig kommen neue dazu. Die mei-sten davon, insbesondere alle haufig verwendeten, las-

sen sich anhand der von ihnen am besten unterstutztenProgrammierparadigmen in einige wenige Sprachklas-sen einteilen:

Sprache

imperativ

prozedural

objektorientiert

deklarativ

funktional

logisch

algebraisch

Imperative Sprachen werden dadurch charakterisiert,dass Programme aus Anweisungen (= Befehlen) aufge-baut sind. Diese werden in einer festgelegten Reihen-

Page 7: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 7/64

1.2. KATEGORISIERUNG 7

folge ausgef uhrt, in parallelen imperativen Sprachenteilweise auch gleichzeitig bzw. in beliebiger Reihenfol-ge. Grundlegende Sprachelemente sind Variablen, Kon-

stanten, Routinen (= Funktionen bzw. Prozeduren)und—in fast allen imperativen Sprachen—Typen. Derwichtigste Befehl in diesen Sprachen ist die destruk-tive Zuweisung : Eine Variable bekommt einen neuenWert, unabhangig vom Wert den sie vorher hatte. DieMenge der Werte in allen Variablen im Programm so-wie ein (oder mehrere) Zeiger auf den (die) nachstenauszuf uhrenden Befehl(e) beschreiben den Programm-zustand, der sich mit der Ausf uhrung jeder Anweisungandert.

Der wichtigste Abstraktionsmechanismus in proze-duralen Sprachen (z. B. Algol, Fortran, Cobol, C, Pas-cal, Modula-2, Ada, u.s.w.) ist die Routine (bzw.Prozedur). Programme werden aufgrund funktiona-ler Abhangigkeiten zwischen Daten in sich gegensei-tig aufrufende und den globalen Programmzustandverandernde Routinen zerlegt.

”Saubere“ prozedurale

Programme werden mittels strukturierter Programmie-rung geschrieben.

Die objektorientierte Programmierung ist eine Wei-terentwicklung der strukturierten prozeduralen Pro-grammierung, die den abstrakten Begriff des Objekts inden Mittelpunkt stellt. Ein Objekt hat eine Identit at,einen Zustand und ein festgelegtes Verhalten . Objektekommunizieren miteinander durch den Austausch von

Nachrichten. Man unterscheidet zwischen aktiven undpassiven Objekten. Bei passiven Objekten, die in al-len sequentiellen objektorientierten Sprachen verwen-det werden, entspricht das Senden einer Nachricht inetwa einem Prozeduraufruf. Das Verhalten eines passi-ven Objekts wird durch eine Menge von Operationen,die den Objektzustand andern konnen, beschrieben.Aktive Objekte sind Prozesse, die sich auch uber an-dere Kommunikationsmechanismen wie z. B. synchro-nes oder asynchrones

”message passing“ verstandigen.

Das Verhalten eines aktiven Objekts wird meist durchein Programmstuck, das in einer Endlosschleife beliebig

oft ausgef uhrt werden kann, festgelegt. Wie bei pro-zeduralen Sprachen erfolgen Zustandsanderungen so-wohl von passiven als auch aktiven Objekten durchdestruktive Zuweisungen. Der wesentliche Unterschiedzur prozeduralen Programmierung ist der, dass zusam-mengehorende Operationen, Prozesse und Daten zuObjekten zusammengefasst werden. Dadurch ist es invielen Fallen moglich, die Programmausf uhrung an-hand der Zustandsanderungen in den einzelnen Ob- jekten zu beschreiben, ohne globale Anderungen derProgrammzustande betrachten zu mussen. Als wei-tere Abstraktionsmechanismen bieten objektorientier-te Sprachen (z. B. C++, Eiffel, Smalltalk, Modula-3,Java, C#, Ada 95, u.s.w.) Klassen und Vererbung oder vergleichbare Sprachkonstrukte. Klassen dienen

der Klassifikation und Beschreibung von Objekten undstehen oft in enger Beziehung zu Typen. Vererbungist ein haufig verwendeter—obwohl nicht der einzig

mogliche—Mechanismus zur Erstellung neuer Klassendurch Verwendung bereits existierender Klassen.

Deklarative Programme beschreiben ein statischesModell durch die Beziehungen zwischen Ausdruckendieses Modells. Im Prinzip gibt es in deklarativen Spra-chen keine zustandsandernden Anweisungen. Deklara-tive Sprachen entstanden aus mathematischen Spra-chen zur Beschreibung von Modellen und stehen daherin der Regel auf einem hoheren Abstraktionsniveau alsimperative Sprachen, die sich aus Maschinensprachenund hardwarenahen Berechnungsmodellen entwickel-ten. Grundlegende Sprachelemente sind Symbole, diesich manchmal in mehrere Klassen (z. B. Variablen-symbole, Funktionssymbole, Pradikate, u.s.w.) eintei-len lassen.

Eines der f ur die Informatik bedeutendsten theore-tischen Modelle ist der Lambda-Kalkul, der den ma-thematischen Begriff

”Funktion“ formal definiert. Pro-

grammiersprachen, die auf diesem Kalkul beruhen,heissen funktionale Sprachen. Beispiele sind Lisp, ML,Miranda und Haskell. Alle Ausdrucke in diesen Spra-chen werden als Funktionen aufgefasst, und der wesent-liche Berechnungsschritt besteht in der Anwendung ei-ner Funktion auf einen Ausdruck. Der Lambda-Kalkulhatte einen großen Einfluss auf die historische Ent-

wicklung der imperativen Sprachen. Manchmal werdenfunktionale Sprachen als

”saubere“ Varianten proze-

duraler Sprachen angesehen, die ohne”

unsaubere“ de-struktive Zuweisung auskommen.

Logische Sprachen beruhen auf der Klausel-Logik,einer Teilmenge der Pradikatenlogik. Die Menge allerwahren Aussagen in einem Modell wird mittels Faktenund Regeln beschrieben. Um einen Berechnungsvor-gang zu starten, wird eine Anfrage gestellt. Das Ergeb-nis der Berechnung besagt, ob und unter welchen Be-dingungen die in der Anfrage enthaltene Aussage wahrist. Der wichtigste Vertreter dieser Sprachen, Prolog,

hat eine prozedurale Interpretation; das heisst, Faktenund Regeln konnen als Prozeduren aufgefasst und wiein prozeduralen Sprachen ausgef uhrt werden.

Algebraische Sprachen , die auf freien Algebren beru-hen, wurden bisher fast ausschließlich als Spezifikati-onssprachen verwendet. Sie sind hier dennoch als eige-ne Sprachklasse angef uhrt, da ihre Konzepte in vielenSprachen in Form von Modulen und in objektorientier-ten Sprachen als Klassen vorkommen.

Die Zuordnung von Sprachen zu Sprachklassen istnicht immer so eindeutig wie man vielleicht glaubenmochte. Zum Beispiel gibt es in Lisp, einer im We-sentlichen funktionalen Sprache, auch eine destrukti-ve Zuweisung, und CLOS (=

”Common Lisp Object

System“) ist eine bekannte objektorientierte Erweite-

Page 8: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 8/64

8 KAPITEL 1. BEGRIFFSBESTIMMUNGEN UND UBERBLICK

rung von Lisp. Naturlich kann man auch in C++ oderJava funktionale oder prozedurale Programme schrei-ben. Und auch in C oder Modula-2 kann man, aller-

dings meist nur mit hohem Aufwand, objektorientierteProgramme schreiben. Die Art der Verwendung einerProgrammiersprache lasst sich oft leichter einem Para-digma zuordnen als die Sprache selbst. Da Program-miersprachen im Hinblick auf bestimmte Paradigmenentwickelt werden, ist die Zuordnung von Sprachen zuParadigmen aufgrund ihrer vorherrschenden Verwen-dungen jedoch meist problemlos moglich.

1.2.2 Einteilung von Typsystemen

Typsysteme konnen nach einer Reihe von Kriterien ein-geteilt werden:

Kriterium AuspragungenDefinierbarkeit vordefiniert, definierbarArt der Definition extensional, intensionalTypfestlegung statisch, stark, schwach, . . .Machtigkeit flexibel, sicher, einfach, . . .Typzwang konservativ, optimistischDurchlassigkeit dicht, lochrigTypaquivalenz struktur-, namensgleichAbstraktionsgrad abstrakt, konkretAusdrucklichkeit implizit, explizitUberschneidungen monomorph, polymorph

Prinzipiell konnen Typen extensional oder intensio-nal spezifiziert sein. Eine extensionale Spezifikationzahlt die Instanzen eines Typs auf. Durch die Anga-be seiner Eigenschaften relativ zu anderen Typen wirdein Typ intensional spezifiziert. Eine Analogie gibt esin der Mathematik bei der Spezifikation von Mengen:2, 3, 5, 7, 11, 13 ist eine extensional spezifizierte Men-ge. i ∈ 1, . . . , 16 | p(i) beschreibt dieselbe Mengerelativ zur Menge 1, . . . , 16 der ganzen Zahlen im Be-reich von 1 bis 16, wobei das Pradikat p(i) genau dannwahr ist wenn i eine Primzahl ist. Bei extensional spe-

zifizierten Mengen (bzw. Typen) ist die Zugehorigkeiteines Elements (einer Instanz) stets einfach feststell-bar. Dagegen ist bei intensional spezifizierten Mengen(Typen) nicht immer entscheidbar, ob ein bestimmtesElement in der Menge enthalten ist (zum Typ gehort)oder nicht. Intensionale Spezifikationen sind wesentlichmachtiger als extensionale. Zum Beispiel beschreibti ∈ N | p(i), wobei N die Menge der naturlichenZahlen bezeichnet, die unendliche Menge aller Prim-zahlen, obwohl diese nicht auf einfache Weise rekursivaufzahlbar ist.

Eine Programmiersprache, in der es Typen gibt,heisst typisiert. Eine Programmiersprache, in der dereindeutige Typ jedes Ausdrucks durch statische Pro-grammanalyse ermittelt werden kann, heisst statisch

typisiert. Statische Typisiertheit ist eine brauchbareEigenschaft, da Typen die moglichen Verwendungenvon Ausdrucken angeben und dadurch zur Lesbarkeit

von Programmen beitragen. Aber die Forderung, dass jedem Ausdruck zur Compilezeit ein eindeutiger Typzugeordnet sein muss, ist in einigen Fallen zu restrik-tiv. Eine schwachere Forderung, vollst andige Typisiert-heit , ist, dass alle Ausdrucke garantiert typkonsistent sind, obwohl nicht alle Typen dem Compiler bekanntsein mussen. Typkonsistenz muss spatestens wahrendder Programmausf uhrung uberpruft werden konnen.Sprachen, in denen die Typkonsistenz aller Ausdruckedurch einen Compiler garantiert werden kann, heissenstark typisiert. In stark typisierten Sprachen konnenwahrend der Programmausf uhrung niemals Typfehlerauftreten. Jede statisch typisierte Programmierspra-che ist auch stark typisiert, da dem Compiler die zurTypuberprufung notwendige Information vorliegt. Und jede stark typisierte Sprache ist vollstandig typisiert.Die Umkehrungen gelten nicht: Nicht jede vollstandigtypisierte Sprache ist stark typisiert, und nicht jedestark typisierte Sprache ist statisch typisiert. Diese Be-ziehungen konnen grafisch veranschaulicht werden—Rechtecke stellen Mengen dar:

Sprachen

typisierte Sprachen

vollstandig typisierte Sprachen

stark typisierte Sprachen

statisch typisierte Sprachen

Typisierte Sprachen, die nicht statisch typisiert sind,werden dynamisch typisiert genannt. Solche, die nicht

stark typisiert aber typisiert sind, heissen schwach ty-pisiert.

Die im vorigen Absatz eingef uhrten Begriffe und Be-ziehungen sind etwas verwirrend und konnen leicht zuVerwechslungen f uhren. Daher werden sie hier nochein-mal gegenubergestellt:

Statisch bzw. dynamisch typisiert: Diese Eigen-schaften beziehen sich darauf, ob jedem Ausdruckeines Programms statisch (d. h. zur Compilezeit)ein eindeutiger Typ zugeordnet werden kann odernicht. Sie bezieht sich nicht darauf, ob die Typ-konsistenz von einem Compiler uberpruft werdenkann, obwohl in der Praxis jedes statisch typisierteProgramm auch stark typisiert ist.

Page 9: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 9/64

1.2. KATEGORISIERUNG 9

Von einer Sprache, in der mehr Ausdrucken sta-tisch ein eindeutiger Typ zugeordnet werden kannals in einer zweiten Sprache, sagt man manchmal,

sie sei ”statischer“ typisiert als diese zweite Spra-che. Man sagt von Sprachen auch, sie seien bis auf gewisse Sprachkonstrukte oder Aspekte statischtypisiert, wenn die entsprechenden Sprachen oh-ne diese Konstrukte bzw. unter Vernachlassigungdieser Aspekte statisch typisiert sind.

Stark bzw. schwach typisiert: Diese Eigenschaf-ten beschreiben, ob ein Compiler Typkonsistenz garantieren kann oder nicht. Man beachte, dassdie Typuberprufung zur Compilezeit auch stati-sche Typ¨ uberpr ufung und jene zur Laufzeit dyna-mische Typ¨ uberpr ufung genannt wird. Eine Spra-

che, die eine statische Typuberprufung ermoglicht,heisst stark typisiert , aber sie ist nicht notwendi-gerweise statisch typisiert. Eine Sprache, die einestatische Typuberprufung nicht ermoglicht, heisstschwach typisiert.1

Von einer Sprache, in der mehr Ausdrucke statischauf Typkonsistenz gepruft werden konnen als in ei-ner zweiten Sprache, sagt man oft, sie sei starkertypisiert als diese zweite Sprache. Eine stark ty-pisierte Sprache ist starker typisiert als jede nichtstark typisierte Sprache. Man sagt von Sprachenauch, sie seien bis auf gewisse Konstrukte oder

Aspekte stark typisiert, wenn diese Sprachen oh-ne diese Konstrukte bzw. unter Vernachlassigungdieser Aspekte stark typisiert sind.

Vollstandigkeit: Eine Sprache ist vollstandig ty-pisiert wenn eine Typuberprufung (statisch oderdynamisch) moglich ist. Von einer Sprache, inder Programme nur zum Teil auf Typkonsistenzuberpruft werden konnen, sagt man, sie habe eindurchl assiges oder l ochriges Typsystem, bzw. siesei bis auf diese oder jene Konstrukte oder Aspek-te vollstandig typisiert. Nicht vollstandig typisier-te Sprachen haben mehr oder weniger L

¨ocher, bzw.

sind mehr oder weniger typisiert.

Eine Sprache ist typisiert, wenn es in der Spracheein Typsystem gibt. Eine Sprache, f ur die uber-haupt kein Typsystem definiert ist, heisst untypi-siert.

In (bis auf einige Konstrukte) statisch typisier-ten Sprachen sind verschiedene Aspekte eines Pro-gramms statisch festgelegt. Diese Aspekte konnen f urProgrammanalysen und Optimierungen herangezogenwerden und haben haufig auch den Charakter einer

1

Diese verwirrende Terminologie hat historische Grunde. Eswar nicht von Anfang an klar, dass es stark typisierte Sprachengibt, die nicht auch statisch typisiert sind.

zuverlassigen Programmdokumentation. DynamischeAspekte sind statischen Analysen—aber auch der sta-tischen Betrachtung durch den Programmierer—nur in

beschranktem Umfang zuganglich. Statisch festgelegteTypen sind praktisch immer durch die Sprache syntak-tisch festgelegt. Programme werden also ausschließlichauf syntaktische Konsistenz hin uberpruft; die Bedeu-tung (= Semantik ) eines Programms bzw. Typs bleibtdem Compiler unbekannt. In allen in der Praxis ein-gesetzten (bis auf einige Konstrukte) statisch typisier-ten Sprachen ist es moglich, Programme zu schreiben,deren Typen syntaktisch konsistent sind, obwohl dieintuitiven Bedeutungen der Typen inkonsistent seinkonnen. Statische Typuberprufung ist also kein All-heilmittel gegen Laufzeitfehler. Der Begriff Typfehler bezieht sich nur auf syntaktische Einschrankungen, dievon Sprache zu Sprache stark divergieren.

Eines der Ziele jedes Typsystems ist die Ausschal-tung einiger Fehlerarten in Programmen. Allerdingsschranken Typsysteme, die eine große Anzahl vonFehlerarten abfangen, die Freiheit der Programmiererstark ein. Daher hat nicht jede Programmiersprache einumfangreiches und sicheres Typsystem. Zum Beispielgibt es in Forth im Wesentlichen nur den Typ cell,der je nach Operation als ganze Zahl, Fließkommazahloder Adresse aufgefasst wird. Dieses sehr einfache Typ-konzept erlaubt große Flexibilitat und erlaubt einfache,effiziente Implementierungen von Interpretern auf Ko-

sten der Typsicherheit. Das Ziel der meisten neuerenProgrammiersprachen ist jedoch, sowohl Typsicherheitals auch Flexibilitat zu bieten. Man versucht, die mogli-chen Fehlerquellen immer genauer einzugrenzen, sodass die Anzahl der vom Typsystem akzeptierten Pro-gramme ebenso wie die der abgefangenen Fehlerartenvergroßert wird. Die Kombination von Typsicherheitund Flexibilitat ist in weitem Umfang moglich, jedochhaufig nur auf Kosten der Einfachheit und Verstand-lichkeit. In einigen Sprachen mit einem sehr machti-gen Typkonzept konnen sogar erfahrene Programmie-rer kaum begrunden, warum ein bestimmtes Programm

vom Compiler akzeptiert wird oder nicht. Viele Spra-chen verzichten deswegen zugunsten eines uberschau-baren, einigermaßen sicheren Typsystems zum Teil auf Flexibilitat.

Im vorigen Absatz wurde implizit davon ausgegan-gen, dass der Compiler nur solche Programme uber-setzen kann, die typkonsistent sind. Bei Typfehlernmuss der Programmierer das Programm so umschrei-ben, dass es nachweislich keine Typfehler enthalt. DieseVorgangsweise nennt man konservativ. Da Typsystemenur einen Teil der Ausdruckskraft einer Sprache um-fassen konnen, schließen konservative Typuberprufun-gen immer eine großere Anzahl von Programmen vonder Ausf uhrung aus als tatsachlich Typfehler enthal-ten. Im Gegensatz dazu gehen optimistische Typuber-

Page 10: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 10/64

10 KAPITEL 1. BEGRIFFSBESTIMMUNGEN UND UBERBLICK

prufungen davon aus, dass Programme keine Typfehlerenthalten. Sie warnen hochstens vor moglichen Typ-fehlern, weisen aber nur solche Programme zuruck, die

nachweislich f ur alle moglichen Ausf uhrungen fehler-haft sind. Optimistische Typuberprufungen findet manzum Beispiel in einigen Lisp-Dialekten.

In jeder vollstandig typisierten Sprache werden, imPrinzip, alle Typfehler spatestens bei der Programm-ausf uhrung entdeckt. In der Praxis wird aus Effizienz-grunden jedoch haufig auf Typuberprufungen wahrendder Ausf uhrung verzichtet. Ein klassisches Beispiel istdie Uberprufung, ob ein Index innerhalb der Feldgren-zen liegt. Solche Locher in der Typuberprufung konnenleicht Fehler unerkannt bleiben lassen und zu falschenResultaten f uhren. Dies ist besonders dann unange-nehm wenn der Programmierer sich darauf verlasst,dass die Typen auf Konsistenz uberpruft werden. Es istdaher wichtig, alle solchen Locher, also alle Konstruk-tionen, die aufgrund der Sprachdefinition zwar verbo-ten sind aber durch die Typuberprufung nicht als ver-boten erkannt werden, zu kennen.

Oft besteht ein Typsystem nur aus einigen vordefi-nierten einfachen Typen (z. B. ganze Zahlen, Fließkom-mazahlen) und wenigen Typkonstruktoren (z. B. Fel-der, Verbunde, Paare). Man kann Typen als aquivalentansehen, wenn sie gleich strukturiert sind (z. B. in Fort-ran, Lisp und zu einem großen Teil in C), oder wenn ex-plizit definierte Typen den gleichen Namen haben (z. B.

teilweise in Pascal und praktisch allen objektorientier-ten Sprachen). Typ¨ aquivalenz aufgrund von Struktur-gleichheit hat den Vorteil der einfacheren Handhabung.Es braucht nicht darauf geachtet werden, wie der Typheisst. Andererseits kann Typaquivalenz aufgrund vonNamensgleichheit als zusatzlicher Abstraktionsmecha-nismus zur Programmstrukturierung eingesetzt wer-den. Zum Beispiel ist es nicht moglich, die Instanzender Typen Apfel und Birne in ungewunschter Weise zuvermischen, auch wenn beide Typen ganze Zahlen sind.Man sagt, Apfel und Birne sind zwei von Integer

abgeleitete Typen. Im Zusammenhang mit Verbunden

stellt dieser Abstraktionsmechanismus eine Grundla-ge f ur Vererbung in vielen objektorientierten Sprachendar.

Typaquivalenz aufgrund von Namensgleichheit er-laubt die Einf uhrung von abstrakten Datentypen , kurzADT. Ein ADT

”versteckt“ die interne Darstellung sei-

ner Instanzen. Nach außen hin wird eine Instanz als ab-straktes Objekt ohne innere Struktur, z. B. als Adres-se, reprasentiert. Auf diesen Objekten sind nur die vomADT exportierten Operationen anwendbar. Manchmalwird ein ADT als Modul implementiert, das Instanzenals

”opaque“ Zeiger auf Instanzen eines nicht expor-

tierten Typs darstellt. Diese Zeiger und einige darauf anwendbare Routinen werden exportiert. In vielen ob- jektorientierten Sprachen ist ein ADT ein Verbund, der

neben Daten auch Routinen (Methoden) enthalt. Ei-nige Komponenten werden durch das Typsystem vorZugriffen von außen geschutzt.

Von abstrakten Datentypen zu unterscheiden sindabstrakte Typen. Abstrakte Typen stellen unvollstandigspezifizierte Typen dar, aus denen konkrete Typen er-zeugt werden konnen. Abstrakte Typen werden alsStrukturierungsmittel zur Erzeugung vieler gleich oderahnlich strukturierter konkreter Typen und als Schnitt-stellenbeschreibungen in objektorientierten Sprachenverwendet.

Nicht alle Ausdrucke in einer typisierten Sprachemussen einen Typ haben. So sind zum Beispiel Varia-blen in Lisp oder Smalltalk nicht typisiert; d. h. es gibtkeine Typeinschrankungen auf Variablen. Dagegen sinddie Werte bzw. Objekte in diesen Sprachen vollstandigtypisiert; die Typkompatibilitat von Werten bzw. Ob- jekten wird zur Laufzeit luckenlos uberpruft. In statischtypisierten Sprachen mussen jedoch auch Variablen ty-pisiert sein.

Typen von Variablen werden auf verschiedene Ar-ten deklariert. In einigen alteren imperativen Sprachenist die Typdeklaration implizit. Zum Beispiel enthal-ten in Fortran Variablen, die mit den Buchstaben i bisn oder I bis N beginnen, falls kein anderer Typ dekla-riert ist, eine ganze Zahl, solche, die mit einem anderenZeichen beginnen, eine Fließkommazahl. In praktischallen neueren imperativen Sprachen muss der Typ der

meisten typisierten Sprachelemente explizit deklariertwerden. Ausgenommen sind in der Regel nur einfacheKonstanten. In sehr vielen deklarativen Sprachen istder Typ von Ausdrucken nach wie vor implizit defi-niert. Zum Beispiel ist in Lisp 1 eine ganze Zahl, a

ein Symbol und (a 1) eine Liste. Letzterem Ausdruckkann man aber auch den etwas spezielleren Typ

”zwei-

elementige Liste von einem Symbol und einer ganzenZahl“ zuordnen. Typen sind also nicht eindeutig, son-dern konnen in einem weiten Bereich zwischen sehr all-gemein und ganz speziell liegen.

Funktionen in funktionalen Sprachen konnen be-

liebige Ausdrucke als Argumente ubergeben werden.Mittels Typinferenz lasst sich in neueren funktionalenSprachen f ur jede vom Compiler akzeptierte Funktionein allgemeinster, f ur jedes Argument gultiger Ergeb-nistyp berechnen.

1.2.3 Polymorphe Typsysteme

Konventionelle typisierte Sprachen wie z.B. Pascal be-ruhen darauf, dass jede Variable oder Routine genaueinen eindeutigen Typ hat. Solche Sprachen heissenmonomorph . Im Gegensatz dazu konnen Variablen undRoutinen in polymorphen Sprachen mehrere Typen ha-ben. Jeder formale Parameter einer polymorphen Rou-tine kann an Argumente von mehr als nur einem Typ

Page 11: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 11/64

1.2. KATEGORISIERUNG 11

gebunden werden. Polymorphe Typen sind solche, de-ren Operationen auf Operanden mehrerer Typen an-wendbar sind.

In einer Sprache mit polymorphem Typsystem hateine Variable oder ein Parameter meist gleichzeitig fol-gende Typen: Der deklarierte Typ ist jener, mit demdie Variable deklariert wurde. Dieser existiert naturlichnur in Sprachen, in denen Typen von Variablen de-klariert werden. Der statische Typ ist jener, der vomCompiler (z. B. durch Typinferenz) ermittelt wird. Die-ser Typ kann spezieller sein als der deklarierte Typ.In vielen Fallen ordnet der Compiler ein und dersel-ben Variablen an verschiedenen Stellen in einem Pro-gramm verschiedene statische Typen zu. Statische Ty-pen werden f ur die statische Typuberprufung verwen-det. Der dynamische Typ ist der speziellste Typ, dender in der Variable gerade gespeicherte Wert hat. Dy-namische Typen werden unter anderem f ur die dyna-mische Typuberprufung und f ur dynamisches Bindenin objektorientierten Sprachen verwendet.

Man kann verschiedene Arten von polymorphenTypsystemen unterscheiden:

polymorphesTypsystem

universellquantifiziert

parametrisch

Subtyping

ad hoc

polymorph

uberladen

umwandelnd

Parametrische Typsysteme—auch generische Typ-systeme genannt—heissen so, weil die Gleichf ormig-keit der Typen durch Typparameter erreicht wird. Dasheisst, Typausdrucke konnen Typparameter enthalten,f ur die Typausdrucke eingesetzt werden. Zum Beispielkann im Ausdruck List[a] → a, dem Typ aller Funktio-nen von List[a] in a, f ur den Typparameter a der Typ-ausdruck Integer eingesetzt werden. Das Ergebnis,List[Integer] → Integer, ist der (generierte) Typ ei-ner Funktion, die Listen von ganzen Zahlen in ganze

Zahlen abbildet. Ein Typausdruck mit freien Typpara-metern bezeichnet die Menge aller Typausdrucke, diedurch Einsetzen von Typausdrucken generiert werdenkonnen. Anders gesagt, Typparameter werden impli-zit als universell uber die Menge aller Typausdruckequantifizierte Variablen betrachtet. Daher werden pa-rametrische Typsysteme den universell quantifizierten Typsystemen zugerechnet.

Vererbung in objektorientierten Programmierspra-chen ist mit Subtyping verwandt. (Wir verwendenhier den englischen Begriff, da es keinen adaquatendeutschen Begriff gibt.) Zum Beispiel hat der TypPerson, ein ADT, die beiden Untertypen Student undAngestellter. An eine Routine mit einem formalenParameter vom Typ Person kann ein Argument vom

Typ Student oder Angestellter ubergeben werden,da jeder Student und jeder Angestellte auch eine Per-son ist. Die Menge der Instanzen von Person bein-

haltet alle Instanzen von Student und Angestellter.Die Routine akzeptiert alle Argumente vom Typ t, wo-bei t eine universell uber Person und dessen Unterty-pen quantifizierte Variable ist. Daher zahlen auch Typ-systeme mit Subtyping zu den universell quantifiziertenTypsystemen.

Wenn ein ADT eine Routine (Methode) exportiert,dann exportiert auch jeder seiner Untertypen eineRoutine mit einer dazu passenden Schnittstelle. Nichtdie Routinen selbst, sondern nur deren Schnittstel-len mussen ubereinstimmen. Der Compiler sieht nurden statischen Typ einer Variablen (oder eines Para-meters). Der dynamische Typ wird erst wahrend derAusf uhrung festgelegt. Daher kann der Compiler auchnicht immer feststellen, welche konkrete Routine desin der Variable enthaltenen Wertes gegebenenfalls aus-gef uhrt werden muss, da ja nur die Schnittstelle be-kannt ist. Die Routine kann manchmal erst wahrendder Programmausf uhrung festgelegt werden. Dies istals dynamisches Binden (engl.

”dynamic binding“) be-

kannt. Statisches Binden (engl.”

static binding“) be-deuted, dass bereits der Compiler die auszuf uhrendeRoutine festlegt.

Im Gegensatz zu universell quantifizierten Typsyste-men haben ad hoc polymorphe Systeme nicht notwen-

digerweise eine gleichf ormige Struktur. Eine Routineist ad hoc polymorph wenn sie Argumente mehrererverschiedener Typen, die in keiner Relation zueinan-der stehen mussen, akzeptiert und sich f ur jeden dieserTypen anders verhalten kann. ¨ Uberladene Typsyste-me erlauben, dass ein und derselbe Name verschiedeneRoutinen bezeichnet, die sich durch die statischen Ty-pen ihrer formalen Parameter unterscheiden. Die sta-tischen Typen der ubergebenen Argumente bzw. Va-riablen entscheiden, welche Funktion ausgef uhrt wird.Das Uberladen dient haufig nur der syntaktischen Ver-einfachung, da f ur Operationen mit ahnlicher Funktio-

nalitat nur ein gemeinsamer Name vorgesehen werdenbraucht. Zum Beispiel bezeichnet”

/“ in vielen Pro-grammiersprachen sowohl die ganzzahlige Division alsauch die Division von Fließkommazahlen, obwohl die-se Funktionen sich im Detail sehr stark voneinanderunterscheiden.

Typumwandlung ist im Gegensatz zum Uberladen ei-ne semantische Operation. Sie dient hauptsachlich zurUmwandlung eines Wertes in ein Argument eines dy-namischen Typs, der von einer Funktion erwartet wird.Zum Beispiel wird in C jede ganze Zahl des Typs char,short oder long bei der Argumentubergabe implizitin eine Zahl vom Typ int umgewandelt wenn der for-male Parameter vom Typ int ist. Typumwandlungenkonnen auch explizit erfolgen. Einige Programmier-

Page 12: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 12/64

12 KAPITEL 1. BEGRIFFSBESTIMMUNGEN UND UBERBLICK

sprachen (z. B. C++) definieren durch oft recht diffi-zile Regeln, wie Typen von Argumenten umgewandeltwerden, wenn zwischen mehreren uberladenen Routi-

nen gewahlt werden kann.

1.3 Literaturhinweise

Die Definitionen des Begriffs Typ aus verschiedenenSichtweisen wurden aus [29] ubernommen. Dieser Ar-tikel enthalt eine thematisch umfangreiche Einf uhrungin objektorientierte Programmiersprachen, in der auchTypen nicht zu kurz kommen. Es gibt zahlreiche allge-meine Einf uhrungen in Programmiersprachen und de-ren Klassifikation. Als Beispiel sei [11] angef uhrt. Ei-nige Klassifikationskriterien, insbesondere die Eintei-

lung polymorpher Typsysteme, wurden aus [6] uber-nommen. Eine gute, verstandliche Einf uhrung in poly-morphe Typsysteme wird auch in [8] gegeben.

Leider ist die Terminologie hinsichtlich der Klassi-fikation von Typsystemen nicht ganz einheitlich. Dieswird vor allem beim Lesen von News-Gruppen, die sich(auch) mit Programmiersprachen befassen, immer wie-der deutlich. Vor allem die Begriffe

”statisches Typ-

system“ und”

starkes Typsystem“ werden oft in zahl-reichen unterschiedlichen Bedeutungen verwendet. Diein diesem Skriptum verwendete Terminologie ist aberinsofern etabliert, als die meisten klassischen Bucherund Artikel auf diesem Gebiet (wie die oben zitierteLiteratur) diese Terminologie verwenden.

Page 13: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 13/64

Kapitel 2

Einfache theoretische Modelle

Wer Typsysteme genauer kennen und verstehen will,braucht grundlegendes Wissen uber die Programmier-sprachen, in die Typsysteme eingebettet sind, eben-

so wie Wissen uber die formalen Modelle, auf denendiese Typsysteme beruhen. Dieses Kapitel gibt einenkurzen Uberblick sowohl uber die wichtigsten Berech-nungsmodelle als auch uber einfache Typmodelle, auf denen komplexere Typsysteme aufbauen.

2.1 Der Lambda-Kalkul

Der Lambda-Kalkul ist wahrscheinlich das einfluss-reichste theoretische Modell der Informatik. Entspre-chend umfangreiche Literatur uber verschiedene Vari-anten des Lambda-Kalkuls und seine Typsysteme gibtes. In diesem Abschnitt werden eine haufig verwende-te untypisierte Variante und eine darauf aufbauendemonomorph typisierte Variante beschrieben. Die typi-sierte Variante wird dann um einige strukturierte Ty-pen erweitert, die in spateren Kapiteln verwendet wer-den. Ansatze f ur polymorphe Typsysteme im Lambda-Kalkul werden in Kapitel 4 behandelt.

2.1.1 Der untypisierte Lambda-Kalkul

Syntaktisch sind alle Elemente des Lambda-KalkulsLambda-Ausdr ucke. Wir definieren die Menge aller

Lambda-Ausdrucke Exp folgendermaßen:1. x ∈ Exp wenn x ∈ Id ; Id ist eine vorgegebene

Bezeichner-Menge;

2. (e1 e2) ∈ Exp wenn e1 ∈ Exp und e2 ∈ Exp;

3. (λx.e) ∈ Exp wenn x ∈ Id und e ∈ Exp.

Runde Klammern veranschaulichen die Struktur vonLambda-Ausdrucken und konnen weggelassen werdenwenn die Struktur eindeutig ist. Die Elemente der Men-ge Id sind einfach nur Namen, denen keine weitere Be-deutung beigemessen wird. Ausdrucke der Form (e1 e2)werden Anwendungen genannt, da e1, als Funktion in-terpretiert, auf das Argument e2 angewandt wird. Aus-drucke der Form λx.e heissen Abstraktionen ; sie stellen

einstellige Funktionen mit dem formalen Parameter xund dem Funktionsrumpf e dar.

Die Semantik des Lambda-Kalkuls wird mittels Re-

geln bestimmt, die wiederholt auf Lambda-Ausdruckeangewandt werden um diese zu verandern. Veranderun-gen sind hauptsachlich Ersetzungen aller Vorkommeneines Namens x in einem Lambda-Ausdruck e2 durcheinen Lambda-Ausdruck e1; das Ergebnis ist wieder einLambda-Ausdruck, der mit [e1/x]e2 (gelesen als

”e1 er-

setzt x in e2“) bezeichnet wird. Obwohl diese Definitionvon Ersetzung sehr einleuchtend erscheint, muss manaus mathematischer Sicht einen großeren Aufwand be-treiben, um mogliche Namenskonflikte zu vermeiden.Zu diesem Zweck wird die Menge der freien Variablen free(e) eines Lambda-Ausdrucks e definiert:

free(x) = x f ur alle x ∈ Id

free(e1 e2) = free(e1) ∪ free(e2)

free(λx.e) = free(e) \ x

Damit kann die Ersetzung [e1/x]e2 induktiv definiertwerden:

[e/x1]x2 =

e f ur x1 = x2

x2 f ur x1 = x2 und x2 ∈ Id

[e1/x](e2 e3) = ([e1/x]e2) ([e1/x]e3)

[e1/x1](λx2.e2) =

λx2.e2 f ur x1 = x2

λx2.[e1/x1]e2 f ur x1 = x2 und x2 /∈ free(e1)

λx3.[e1/x1]([x3/x2]e2) sonst, wobeix1 = x3 = x2 und x3 /∈ free(e1) ∪ free(e2)

In der dritten Regel werden Namenskonflikte durch Na-mensanderungen beseitigt. Das folgende Beispiel zeigtdie Anwendung aller drei Reglen:

[y/x](((λy.x) (λx.x)) x) ≡ ((λz.y) (λx.x)) y

Drei weitere Regeln genugen, um die Semantik desLambda-Kalkuls vollstandig zu beschreiben:

1. α-Konversion (Umbenennung):

λx1.e ⇐⇒ λx2.[x2/x1]ewobei x2 ∈ Id und x2 /∈ free(e)

Page 14: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 14/64

14 KAPITEL 2. EINFACHE THEORETISCHE MODELLE

Diese Regel erlaubt, Namen gebundener Varia-blen (= formale Parameter) beliebig gegen andere,noch nicht verwendete Namen auszutauschen. Na-

men formaler Parameter haben also keine globaleBedeutung.

2. β -Konversion (Anwendung):

(λx.e1) e2 ⇐⇒ [e2/x]e1

Das ist die wichtigste Regel, die die Funktions-weise des Lambda-Kalkuls zum Großteil erklart.Das Argument e2 einer Anwendung (eines Funkti-onsaufrufs) ersetzt jedes Vorkommen des formalenParameters x im Rumpf e1 der Funktion.

3. η-Konversion:

λx.(e x) ⇐⇒ e f ur x ∈ Id und x /∈ free(e)

Diese Regel wurde erst spater zum Lambda-Kalkuldazugef ugt. Sie beseitigt einige subtile Unvoll-standigkeiten im ursprunglichen Kalkul, auf diehier nicht weiter eingegangen wird. Wenn hin-ter λx.(e x) ein weiterer Ausdruck folgt, ist stattη-Konversion stets und gleichbedeutend auch β -Konversion verwendbar.

Die gerichteten Varianten der beiden letztgenanntenKonversionen, sie werden Reduktionen genannt, sind

von großerer praktischer Bedeutung:

1. β -Reduktion:

(λx.e1) e2 =⇒ [e2/x]e1

2. η-Reduktion:

λx.(e x) =⇒ e f ur x ∈ Id und x /∈ free(e)

Ein Lambda-Ausdruck ist in Normalform wenn er we-der mit β -Reduktion noch mit η-Reduktion weiter re-duzierbar ist. Nicht f ur jeden Lambda-Ausdruck gibt es

einen entsprechenden Ausdruck in Normalform. ZumBeispiel ist im Ausdruck

(λx.(x x)) (λx.(x x))

nur eine β -Reduktion anwendbar, die zum selben Aus-druck f uhrt. Wenn es f ur einen Ausdruck aber eine Nor-malform gibt, dann gilt folgender Satz:

Kein Lambda-Ausdruck kann in zwei ver-schiedene Normalformen konvertiert werden,wenn man Namensunterschiede aufgrund vonα-Konversionen unberucksichtigt lasst.

Ein weiterer Satz, das Fixpunkt-Theorem , erlaubt es,im Lambda-Kalkul Rekursion auszudrucken:

Jeder Lambda-Ausdruck e hat einen Fix-punkt e, so dass (e e) zu e konvertiert wer-den kann.

Fur dieses Theorem gibt es einen uberraschend einfa-chen Beweis, der hier als Beispiel f ur Konversionen imLambda-Kalkul angef uhrt wird:

Fur e nehmen wir (Y e), wobei

Y ≡ λf.((λx.(f (x x)))(λx.(f (x x))))

der sogenannte Y -Kombinator ist. Dann gilt

(Y e) = (λx.(e (x x)))(λx.(e (x x)))

= e ((λx.(e (x x)))(λx.(e (x x))))

= e (Y e)

In der Ableitung wurde zweimal β -Reduktion auf dem jeweiligen außeren Ausdruck angewandt.

Jede rekursive Funktion kann im Lambda-Kalkulleicht durch eine rekursionsfreie (und nichtiterative)Funktion ersetzt werden. Nehmen wir an, f sei einerekursive Funktion, die folgendermaßen definiert sei:

f ≡ · · · f · · ·

Dieser Ausdruck kann leicht umgeschrieben werden in

f ≡ (λf. · · · f · · ·) f

so dass das innere Vorkommen von f nun gebundenist. Diese Gleichung besagt, dass f ein Fixpunkt desLambda-Ausdrucks (λf. · · · f · · ·) ist. Das ist genaudas, was Y berechnen kann. Wir erhalten also folgenderekursionsfreie Definition von f :

f ≡ Y (λf. · · · f · · ·).

Die Machtigkeit des Lambda-Kalkuls wird durchChurchs These angedeutet:

Genau jene Funktionen von den naturlichenZahlen in die naturlichen Zahlen sind effek-tiv berechenbar, die im Lambda-Kalkul aus-

gedruckt werden konnen.

Die Bedeutung dieser These wird sofort ersichtlich,wenn man weiss, dass auf den Begriff der

”effektiven

Berechenbarkeit“ der Satz uber die Turing-Vollstandig-keit anwendbar ist. Das heisst, alles was berechenbarist, ist auch im Lambda-Kalkul berechenbar.

Eine abschließende Bemerkung zur Syntax: Funk-tionen im Lambda-Kalkul sind auf ein Argument be-schrankt. Mehrstellige Funktionen konnen leicht durch

”geschachtelte“ Ausdrucke der Form λx.e dargestellt

werden. So schreibt man im Lambda-Kalkul—stattder in der Mathematik ublichen Schreibweise f (a, b)—f ur eine zweistellige Funktion (f a b) oder geklammert((f a) b) oder etwas ausf uhrlicher (((λx.(λy.e)) a) b).

Page 15: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 15/64

2.1. DER LAMBDA-KALK UL 15

2.1.2 Der typisierte Lambda-Kalkul

Wir wollen den untypisierten Lambda-Kalkul um Ty-pen erweitern. Dazu f uhren wir die Mengen BasTypvon Basistypen (z. B. Boolean, Integer und String)und Typ von strukturierten Typen ein. (BasTyp ist eineTeilmenge von Typ.) In diesem Abschnitt wird implizitein monomorphes Typsystem verwendet. Daher konnenwir der Einfachheit halber annehmen, dass jeder Typeine Menge von Werten beschreibt und je zwei verschie-dene Typen keine gemeinsamen Werte beschreiben.1

Jedem Lambda-Ausdruck wird ein Typ, das ist das Ele-ment von Typ, in dem der durch den Lambda-Ausdruckfestgelegte Wert enthalten ist, zugeordnet. Wir schrei-ben e:t wenn der Lambda-Ausdruck e den Typ t ∈ Typhat. Der strukturierte Typ t1→t2 bezeichnet den Typ

aller Funktionen von Werten des Typs t1 in Werte desTyps t2.

Beim Ubergang vom untypisierten in den typisiertenLambda-Kalkul sind Modifikationen der Syntax undSemantik notwendig. Wir definieren daher einige Be-griffe um. In den folgenden Abschnitten werden wir,wenn nichts anderes gesagt wird, die Definitionen destypisierten Lambda-Kalkuls verwenden.

Die Menge BasTyp ist als gegeben angenommen. DieMenge Typ ist induktiv definiert:

1. b ∈ Typ wenn b ∈ BasTyp;

2. t1→t2 ∈ Typ wenn t1 ∈ Typ und t2 ∈ Typ.

Die Menge Id von typisierten Bezeichnern der Form x:tmit t ∈ Typ ist als gegeben angenommen. Die MengeExp aller typisierten Lambda-Ausdrucke ist induktivdefiniert:

1. x:t ∈ Exp wenn x:t ∈ Id ;

2. (e1:t2→t1 e2:t2):t1 ∈ Exp wenn e1:t2→t1 ∈ Expund e2:t2 ∈ Exp;

3. (λx:t2.e:t1):t2→t1 ∈ Exp wenn x:t2 ∈ Id und

e:t1 ∈ Exp.

Die Konversions-Regeln und entsprechenden Reduk-tions-Regeln sind analog zu denen im untypisiertenLambda-Kalkul definiert:

1. α-Konversion:

(λx1:t2.e:t1):t2→t1 ⇐⇒(λx2:t2.[x2/x1]e:t1):t2→t1

wobei x2:t2 ∈ Id und x2 /∈ free(e)

1Tatsachlich mussen als Instanzen der Typen etwas kompli-ziertere Strukturen als nur Mengen von Werten verwendet wer-

den, um grundlegende Eigenschaften des Lambda-Kalkuls bei-behalten zu konnen. Eine Diskussion dieses Themas wurde denRahmen des Skriptums aber bei weitem sprengen.

2. β -Konversion:

((λx:t2.e1:t1):t2→t1 e2:t2):t1 ⇐⇒ [e2/x]e1:t1

3. η-Konversion:

(λx:t2.(e:t2→t1 x:t2):t1):t2→t1 ⇐⇒ e:t2→t1

wenn x:t2 ∈ Id und x /∈ free(e)

Die Einf uhrung von Typen in den Lambda-Kalkulhat schwerwiegende Auswirkungen auf seine Machtig-keit. Fur jeden Ausdruck im typisierten Lambda-Kalkul gibt es genau eine Normalform, die effektiv be-rechenbar ist. Folglich ist es aber nicht moglich, alleeffektiv berechenbaren Funktionen darzustellen. Ins-besondere kann im typisierten Lambda-Kalkul kein

Fixpunktoperator definiert werden, so dass rekursiveFunktionen nicht berechenbar sind. Der Grund daf urist, dass im Lambda-Ausdruck (e e) der Teilausdruck esowohl einen Typ t2→t1 als auch t2 haben muss, wasin den oben definierten Strukturen ausgeschlossen ist.

Glucklicherweise gibt es einen Ausweg aus diesem Di-lemma. Anstatt sich auf die Selbstanwendung zu ver-lassen, kann man den Lambda-Kalkul um eine vierteRegel erweitern, die typisierte Versionen des Y -Kombi-nators implementiert. Fur jeden Typ t ∈ Typ defi-nieren wir einen typisierten Fixpunktoperator Y t vomTyp (t→t)→t, den wir als Konstante zum typisier-

ten Lambda-Kalkul hinzuf ugen. Die Semantik der Fix-punktoperatoren wird durch eine δ-Konversionsregelfestgelegt:

(Y t:(t→t)→t e:t→t):t ⇐⇒(e:t→t (Y t:(t→t)→t e:t→t):t):t

Wenn man die Typinformation ignoriert kann manleicht feststellen, dass diese δ-Regel der Konversion(Y f ) ⇐⇒ (f (Y f )) im untypisierten Kalkul ent-spricht. Zur Implementierung der Rekursion kann der-selbe Trick wie in Abschnitt 2.1.1 verwendet werden.

2.1.3 Strukturierte TypenIn Abschnitt 2.1.2 wurden strukturierte Typen nur f urFunktionen verwendet. Mit Hilfe der Technik, die zurEinf uhrung der typisierten Fixpunktoperatoren ver-wendet wurde, namlich dem Dazuf ugen von δ-Regeln2

zum Lambda-Kalkul, kann eine Reihe weiterer struktu-rierter Typen definiert werden. Genaugenommen wer-den nicht die Typen selbst, sondern Zugriffsfunktionenauf und Erzeugungsfunktionen von Instanzen struktu-rierter Typen uber gerichtete δ-Regeln definiert. ImPrinzip kann man mit δ-Regeln den Lambda-Kalkulbeliebig verandern. Allerdings ist dabei großte Vorsicht

2Der Begriff δ-Regel steht einfach f ur alle weiteren Regelnaußer den α-, β - und η-Regeln.

Page 16: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 16/64

16 KAPITEL 2. EINFACHE THEORETISCHE MODELLE

geboten, wenn man grundlegende Eigenschaften desLambda-Kalkuls beibehalten will. Die hier beschrie-benen Erweiterungen haben sich als einigermaßen gut

vertraglich mit dem ursprunglichen Lambda-Kalkul er-wiesen und werden in vielen imperativen und funktio-nalen Sprachen verwendet.

Zu den einfachsten strukturierten Typen gehoren jene f ur kartesische Produkte (Kreuzprodukte): Fur je zwei Typen t1 und t2 in Typ enthalt Typ auchden Typ t1×t2. Beispiele sind Integer×Boolean und(Integer→Integer)×(Integer×Boolean). Instanzendieses Typs heissen Paare und werden in der Form(e1, e2) dargestellt. Auf ihnen sind die Zugriffsfunk-tionen first und rest sowie die Erzeugungsfunktioncons definiert:

(first:(t1×t2)→t1 (e1, e2):t1×t2):t1=⇒ e1:t1

(rest:(t1×t2)→t2 (e1, e2):t1×t2):t2

=⇒ e2:t2

((cons:t1→(t2→(t1×t2)) e1:t1):t2→(t1×t2) e2:t2):t1×t2

=⇒ (e1, e2):t1×t2

Beachten Sie in der δ-Regel f ur cons, dass ein Typ derForm t1→(t2→t3) nicht nur f ur eine Funktion steht,die ein Argument vom Typ t1 nimmt und als Ergebniseine Funktion von t2 in t3 liefert, sondern auch f ur ei-ne Funktion, die zwei Argumente der Typen t1 und t2

nimmt und als Ergebnis eine Instanz des Typs t3 liefert.Eigentlich handelt es sich bei first, rest und cons umdrei Familien von Funktionen, da die Funktionsnamen,wie bei den typisierten Fixpunktoperatoren, mit den jeweiligen Typen als Indizes versehen werden mussten.Wir verzichten zugunsten einer besseren Lesbarkeit auf das Anschreiben dieser Indizes.

Eine weitere strukturierte Datenstruktur ist der Ver-bund (engl.

”record“ ). Das ist eine ungeordnete Menge

indizierter Werte. Als Indexmenge kann jede beliebigeMenge genommen werden. Jedes Element dieser Men-ge wird als Label bezeichnet. Der Typ eines Wertes im

Verbund wird durch den Typ bestimmt, der dem Labeldes Wertes zugeordnet ist. Ein Verbundtyp wird in ge-schwungenen Klammern als Folge von durch Kommagetrennten, typisierten Labels angeschrieben. Ein Bei-spiel ist a:Integer, b:Boolean, c:String. Einen Ver-bund dieses Typs erzeugt man, indem man jedem Labeleinen Wert zuordnet, wie dieses Beispiel zeigt:

a=3, b=true, c="abcd":a:Integer, b:Boolean, c:String

Die einzige Operation auf Verbunden ist die Aus-wahl eines Feldes, dargestellt durch die ubliche Punkt-

Notation (i ∈ 1, . . . , n):

l1=e1, . . . , ln=en:l1:t1, . . . , ln:tn.li =⇒ ei:ti

Tupel kann man als Verallgemeinerung von Paa-ren und Spezialisierung von Verbunden sehen: ZumBeispiel ist ein Tupel (3, true, "abcd") vom Typ

Integer × Boolean × String aquivalent zu einem Ver-bund 1=3, 2=true, 3="abcd" vom entsprechendenTyp 1:Integer, 2:Boolean, 3:String.

Als letzter strukturierter Typ wird in diesem Ab-schnitt der Variantentyp eingef uhrt. Syntaktisch ist einVariantentyp genauso wie ein Verbundtyp definiert, au-ßer dass (statt der geschwungenen) eckige Klammernverwendet werden. Ein Beispiel f ur einen Variantentypist [a:Integer, b:Boolean, c:String]. Jede Instanz die-ses Typs ordnet genau einem der Label einen Wert zu.Drei mogliche Instanzen des Beispieltyps sind also

[a=3]:[a:Integer, b:Boolean, c:String]

[b=true]:[a:Integer, b:Boolean, c:String][c="abcd"]:[a:Integer, b:Boolean, c:String]

Die einzige Operation auf Varianten ist die Mehrfach-verzweigung (engl.

”case statement“ ). Seine Syntax

und Semantik sind am besten aus der entsprechendenδ-Regel ersichtlich (i ∈ 1, . . . , n):

(case [li=ei]:[l1:t1, . . . , ln:tn] of

l1⇒f 1:t1→t , . . . , ln⇒f n:tn→t):t=⇒ (f i:ti→t ei:ti):t

Eine Mehrfachverzweigung gibt f ur jedes Label eines

Variantentyps eine entsprechend typisierte Funktionan. Je nach dem Label der in der Mehrfachverzwei-gung angegebenen Instanz dieses Typs wird eine Funk-tion ausgewahlt. An diese Funktion wird der Wert derInstanz ubergeben.

2.2 Logik

Logiken, insbesondere verschiedene Formen der Klau-sel-Logik, f ur die automatische Beweissysteme existie-ren, spielen eine zweifache Rolle im Zusammenhangmit Typen. Einerseits gibt es logische Programmier-

sprachen, in die Typen eingebaut wurden, um verschie-dene Arten von Programmierfehlern leichter finden zukonnen. Andererseits kann man Logiken zur Spezifika-tion von Typen verwenden.

2.2.1 Die Pradikatenlogik erster Stufe

Die Pradikatenlogik erster Stufe ist eine Grundlage f urdie meisten Logiken. Durch die Beschrankung auf dieerste Stufe konnen in der Logik selbst keine Aussagenuber die Variablen der Logik getroffen werden; es gibtkeine Metavariablen. Wohlgeformten Formeln (= Aus-drucke) der Pradikatenlogik erster Stufe sind folgen-dermaßen aufgebaut. [Ublichen Schreibweisen sind ineckigen Klammern angef uhrt.]

Page 17: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 17/64

2.2. LOGIK 17

1. Vorgegebene disjunkte Mengen:

(a) eine Menge von Variablen V [x,y,z];

(b) eine Menge von Funktionssymbolen F [a(n), b(n), . . .; 0 ≤ n gibt die Stelligkeit an];0-stellige Funktionssymbole werden Konstan-ten genannt;

(c) eine Menge von Pr adikaten P [ p(n), q(n), . . .;0 ≤ n gibt wieder die Stelligkeit an].

2. Die Menge aller Terme [u,v,w] wird induktiv kon-struiert: x ∈ V ist ein Term; und a(u1, . . . , un) istein Term wenn a(n) ∈ F und u1, . . . , un Termesind. [Bei n = 0 werden die Klammern weggelas-

sen.] Ein Term, der keine Variablen enthalt, heisstGrundterm.

3. p(u1, . . . , un) ist eine Atomformel [A , B , . . .] wenn p(n) ∈ P ein Pradikat ist und u1, . . . , un Termesind. [Bei n = 0 werden die Klammern weggelas-sen.] Wenn u1, . . . , un keine Variablen enthalten,heisst p(u1, . . . , un) Grundatomformel.

4. Die Menge aller wohlgeformten Formeln der Pradi-katenlogik [U , V , W , . . .] ist induktiv definiert:

(a) Eine Atomformel ist ein wohlgeformte For-mel.

(b) Wenn U und V wohlgeformte Formeln sind,dann sind auch ¬U , U ∧ V , U ∨ V , U → V ,U ← V und U ↔ V wohlgeformte Formeln.Die logischen Operatoren stehen (in der Rei-henfolge ihres Auftretens) f ur Negation, Kon- junktion (Und), Disjunktion (Oder), Implika-tion nach rechts bzw. links und Aquivalenz.

(c) Wenn U eine wohlgeformte Formel und x eineVariable ist, dann sind auch ∀x.U und ∃x.U

wohlgeformte Formeln. ∀ ist der universelleund ∃ der existenzielle Quantor. Eine wohl-geformte Formel heisst geschlossen, wenn al-le darin vorkommenden Variablen im Bereicheines Quantors liegen.

Ein pr adikatenlogisches Modell ist eine Abbildungaller wohlgeformten Formeln in die Menge der Wahr-heitswerte true, false. Das heisst, jede wohlgeform-te Formel entspricht im Modell einer wahren oderfalschen Aussage. Modelle mussen den vordefiniertenBedeutungen der logischen Operatoren und Quanto-ren, die als allgemein bekannt vorausgesetzt werden,entsprechen. Hier sind zur Erinnerung einige Eigen-schaften angef uhrt:

¬¬U ⇔ U U ∧ (¬U ) ⇔ false

¬(U ∧ V ) ⇔ (¬U ) ∨ (¬V )

¬(∀x.U ) ⇔ ∃x.(¬U )U ∧ (V ∨ W ) ⇔ (U ∧ V ) ∨ (U ∧ W )U → V ⇔ (¬U ) ∨ V U ↔ V ⇔ (U → V ) ∧ (U ← V )

Logische Programmiersprachen wie z. B. Prolog ver-wenden als Modell das Herbrand-Modell , in dem je-der Grundterm und jede Grundatomformel als dieserGrundterm bzw. diese Grundatomformel selbst inter-pretiert werden. Daher bestimmen ausschließlich die lo-gischen Operatoren und Quantoren, ob eine Aussageaus einer Menge als wahr angenommener wohlgeform-ter Formeln ableitbar ist. In neueren logischen Spra-

chen und ”constraint“-Sprachen werden teilweise auchandere Modelle verwendet.

2.2.2 Definite Horn-Klausel-Logik

Definite Horn-Klauseln , hier kurz Klauseln genannt,sind eine relativ machtige Teilmenge der Pradikaten-logik erster Stufe. Eine solche Klausel ist eine geschlos-sene wohlgeformte Formel der Form

∀x1. · · · ∀xm.(A ∨ ¬B1 ∨ · · · ∨ ¬Bn)

wobei A, B1, . . . , Bn Atomformeln und x1, . . . , xm alle

darin vorkommenden Variablen sind (m, n ≥ 0). DerEinfachheit halber werden Klauseln meist in der Form

A ← B1, . . . , Bn

angeschrieben; das heisst, Quantoren werden wegge-lassen, die Aquivalenz zwischen A ∨ ¬B1 ∨ · · · ∨ ¬Bn

und A ← (B1 ∧ · · · ∧ Bn) wird ausgenutzt, und Kon- junktion wird mittels Beistrich ausgedruckt. Die Aus-sage einer Klausel kann in Deutsch etwa so umschrie-ben werden:

”Wenn es Belegungen f ur alle Variablen

in A, B1, . . . , Bn gibt, so dass B1 bis Bn alle zugleichwahr sind, dann ist auch A mit diesen Variablenbe-

legungen wahr.“ A wird als Kopf und B1, . . . , Bn alsRumpf der Klausel bezeichnet. Eine Klausel mit lee-rem Rumpf (n = 0) heisst Faktum , eine mit nichtlee-rem Rumpf heisst Regel. Ein definites Programm isteine Menge definiter Horn-Klauseln, die als wahr ange-nommen werden.

Eine logische Programmiersprache kann Ziele (dassind Atomformeln) aus einem logischen Programm (dasist eine Menge als wahr angenommener Klauseln) ab-leiten. Welche Ziele sind nun ableitbar? Wir versuchen,diese Frage vorerst nur f ur Grundatomformeln zu be-antworten. Eine Klausel, die Variablen enthalt, kannman als eine Menge von Klauseln ohne Variablen auf-fassen; in jedem Element dieser Menge sind alle Va-riablen mit Grundtermen belegt. Diese Menge ist fast

Page 18: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 18/64

18 KAPITEL 2. EINFACHE THEORETISCHE MODELLE

immer unendlich groß, da es unendliche viele mogli-che Variablenbelegungen mit Grundtermen gibt. Wirnennen das Programm, das dadurch entsteht, dass alle

Klauseln mit Variablen durch alle ihnen entsprechen-den Klauseln ohne Variablen ersetzt werden, GP. Esist leicht einsichtig, dass der Kopf jedes Faktums inGP eine auch im ursprunglichen Programm ableitbareGrundatomformel ist. Wir bezeichnen die Menge die-ser aus Fakten ableitbaren Grundformeln mit R0. Aus jedem Ri mit 0 ≤ i kann leicht die Menge von ableit-baren Grundformeln Ri+1 errechnet werden:

Ri+1 = Ri ∪ A | (A ← B1, . . . , Bn) ∈ GP

∧ B1 ∈ Ri ∧ · · · ∧ Bn ∈ Ri

Die Menge aller aus dem ursprunglichen Programm ab-

leitbaren Grundatomformeln ist der kleinste Fixpunktdieser Iteration, das ist jenes Rm (mit kleinstmogli-chem m) f ur welches Rm = Rm+1 gilt.

Im vorigen Absatz wurde eine einfache”

bottom-up“-Methode zur Ableitung von Zielen beschrieben. Vielelogische Programmiersprachen, wie z. B. Prolog, ver-wenden jedoch eine

”top-down“-Methode. Eine wich-

tige Teilaufgabe ist dabei die Unifikation. Zwei Ter-me sind unifizierbar, wenn die Variablen in den bei-den Termen einheitlich so mit Termen belegt wer-den konnen, dass die beiden Terme gleich werden. Ei-ne Menge von Variablenbindungen, die dies zustan-

de bringt, heisst Unifikator. Ein Unifikator, der nurdie f ur die Unifikation unbedingt notwendigen Varia-blenbindungen enthalt, heisst allgemeinster Unifika-tor. Ebenso konnen zwei Atomformeln unifiziert wer-den wenn sie das gleiche Pradikat haben und ein all-gemeinster Unifikator existiert, der die Argumentter-me der beiden Atomformeln gleich macht, so dass auchdie Atomformeln selbst gleich werden. Wenn θ ein all-gemeinster Unifikator von zwei Atomformeln A undB ist, dann schreiben wir formal θA bzw. θB f urdie Atomformeln die entstehen, wenn alle Variablen-bindungen von θ auf A bzw. B angewandt werden.Naturlich gilt θA = θB. Die Antwort auf eine An-

frage, nennen wir das Ziel G, wird so berechnet: Eswerden Mengen S i noch ungeloster Atomformeln be-rechnet, wobei der Index i uber die einzelnen Schritteder Berechnung lauft. Die Anfangsmenge S 0 = Genthalt nur das Ziel. Die Menge S i (0 < i) wird ausS i−1 = G1, . . . , Gj , . . . , Gm folgendermaßen erzeugt:Es werden eine Atomformel Gj (1 ≤ j ≤ m) aus S iund eine Klausel A ← B1, . . . , Bn aus dem Programmgewahlt, so dass Gj mit A mittels eines allgemeinstenUnifikators θi unifizierbar ist; S i ist dann die Menge

θiG1, . . . , θiGj−1, θiB1, . . . , θiBn, θiGj+1, . . . , θiGn.

In anderen Worten, in jedem Schritt wird eine nochunbewiesene Atomformel aus der Menge gewahlt und

durch eine Bedingung—ausgedruckt durch eine (mogli-cherweise leere) Menge unbewiesener Atomformeln—,unter der die zu beweisende Atomformel wahr ist, er-

setzt. Wenn es einen Index k gibt, so dass S k die leereMenge ist, dann ist θk · · · θ1G aus dem Programm ab-leitbar. Die Antwort enthalt also nicht nur die prinzi-pielle Aussage, dass es Variablenbelegungen gibt, unterdenen G ableitbar ist, sondern liefert solche Variablen-bindungen gleich mit. Wenn S k nicht leer ist, aber imProgramm keine Klausel vorkommt deren Kopf mit ei-ner Atomformel in S k unifizierbar ist, dann ist der Be-weisversuch gescheitert. G konnte aber trotzdem be-weisbar sein und der Beweis ware gefunden wordenwenn in einem fruheren Schritt eine andere Klauselgewahlt worden ware. In Prolog werden in diesem Fallsystematisch die letzten Berechnungsschritte zuruckge-nommen und mit einer anderen Klausel wiederholt, bisein Beweis gefunden ist oder keine alternative Klau-sel mehr existiert. Diesen Vorgang nennt man

”back-

tracking“.Die Ableitung von Zielen aus logischen Programmen

ist”

semi-decidable“. Es gibt Ziele, die weder ableit-bar noch widerlegbar sind. In unentscheidbaren Fallenkommt die Programmausf uhrung in eine Endlosschlei-fe. Es ist jedoch im Allgemeinen nicht entscheidbar,ob sich eine Ausf uhrung in einer Endlosschleife befin-det oder nur mehr Ressourcen braucht, um zu einemErgebnis zu kommen.

2.2.3 Typisierte logische Programme

Typen konnen in logischen Sprachen im Prinzip auf die gleiche Weise eingef uhrt werden wie im Lambda-Kalkul. Wir konnen annehmen, dass ein Typ eine Men-ge von Termen beschreibt.

Der auf den ersten Blick einfachste Ansatz ist, je-der Variable in einem logischen Programm einen Typzuzuordnen. Zum Beispiel schreiben wir x:t f ur eine Va-riable x vom Typ t. Typkompatibilitat bedeutet danneinfach, dass jedes Vorkommen einer Variable densel-ben Typ hat und als Variablenbelegungen nur Termeaus den Typen der Variablen in Frage kommen. DieVariablen sind also nicht uber alle Terme, sondern nuruber die Menge T i der zum jeweiligen Typ ti gehoren-den Terme quantifiziert (1 ≤ i ≤ m):

∀x1 ∈ T 1. · · · ∀xm ∈ T m.(A ← (B1 ∧ · · · ∧ Bn))

Wahrend jeder Unifikation muss uberpruft werden, obdie an Variablen gegundenen Terme mit dem Typ derVariablen kompatibel sind. Falls diese Uberprufungfehlschlagt, wird mittels

”backtracking“ ein anderer

Losungsweg gesucht.Daraus kann man einen formal anderen, aber inhalt-

lich identischen Ansatz ableiten: Jeder Typ wird uberein einstelliges Pradikat definiert, das f ur genau jene

Page 19: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 19/64

2.3. ALGEBREN 19

Terme wahr ist, die zum Typ gehoren. Damit kann obi-ge Klausel aquivalent in folgender Form ausgedruckt

werden, wobei t(1)i das Pradikat ist, das den Typ ti be-

schreibt (1 ≤ i ≤ m):

∀x1. · · · ∀xm.(A ← (t1(x1)∧ · · · ∧tm(xm)∧B1∧ · · · ∧Bn))

oder in abgekurzter Schreibweise

A ← t1(x1), . . . , tm(xm), B1, . . . , Bn.

Daraus ergibt sich eine Reihe weiterer Moglichkei-ten. Es muss zum Beispiel nicht jede Variable einen be-stimmten Typ haben, und beliebig komplexe Typen—auch solche, die von mehreren Argumenten abhangen—konnen definiert werden. Andererseits stellt sich da-

bei die Frage, wodurch sich Typen von gewohnlichenPradikaten unterscheiden. Eine mogliche Antwort dar-auf ware, dass es tatsachlich keinen Unterschied gibt.Ein logisches Programm kann als eine Menge von Typ-spezifikationen—also als Typsystem—gesehen werden,und das Ergebnis einer Anfrage besagt, ob und un-ter welchen Bedingungen ein Ziel den Typspezifikatio-nen entspricht. Ein Prolog-Interpreter ist demgemaßein einziges großes Typuberprufungssystem und Pradi-katenlogik eine machtige Typbeschreibungssprache.

Nicht jeder gibt sich mit dieser simplifizierendenAntwort zufrieden. Es ist ja technisch kein Problem,

Typspezifikationen und sonstige Atomformeln in einerKlausel syntaktisch zu trennen. Genauso einfach kannman sicherstellen, dass f ur jede Variable eine Atom-formel zur Typuberprufung eingef uhrt wird und jedesArgument aller Vorkommen eines Pradikats im Pro-gramm denselben Typ hat. Eine Motivation daf ur ist,dass ein Typfehler von Natur aus etwas anderes als eineEinschrankung der Losungsmenge ist. Einer aufgetre-tenen Typinkompatibilitat sollte also nicht stillschwei-gend durch

”backtracking“ begegnet werden, sondern

eine unubersehbare Fehlermeldung sollte auf den Pro-grammfehler aufmerksam machen. Die verschiedenenAnsatze in diese Richtung unterscheiden sich in der

Syntax, der Flexibilitat und im Zeitpunkt der Typuber-prufung. Praktisch alle Ansatze erlauben polymorpheTypen, die wir in Kapitel 4 behandeln werden.

2.3 Algebren

Algebra ist ein Begriff aus der Mathematik. Eine uni-verselle Algebra ist ein Paar A, Ω, wobei die Tr ager-menge A eine beliebige nichtleere Menge und Ω einSystem von Operationen auf A ist. Unter dem Typ ei-ner Algebra versteht man in der Mathematik die Stel-ligkeiten der Operationen in Ω. Diese Typen sind nurentfernt mit Typen in Programmiersprachen verwandt.Zum Beispiel ist der Ring uber den ganzen Zahlen

Z, +, ·, −, 0 eine Algebra des Typs 2, 2, 1, 0, da +und · binare Operationen, die Negation (−) eine unareOperation und 0 eine nullstellige Operation bzw. ei-

ne ausgewahlte Konstante ist. In dieser Algebra geltenneben anderen auch folgende Gesetze:

(a + b) + c = a + (b + c) a + b = b + aa + 0 = a a + (−a) = 0

(a · b) · c = a · (b · c)

Alle in Ringen geltenden Gesetze lassen sich aus sol-chen Gesetzen herleiten. Diese Gesetze bestimmen dasVerhalten der Operationen. Man konnte statt den gan-zen Zahlen genausogut die Menge R der reellen Zahleneinsetzen, ohne dass sich die Gesetze dadurch andern

wurden. Fur die Beschreibung des Verhaltens der Ope-rationen ist es also gar nicht notig, die Tragermenge zukennen. In der Mathematik bezeichnet der Begriff Va-riet at , angeschrieben als V (Ω, ∆), eine Familie univer-seller Algebren, die durch eine Menge von OperationenΩ und eine Menge dazugehoriger Gesetze ∆ bestimmtist. Algebren in V (Ω, ∆) konnen sich durch ihre Trager-mengen unterscheiden, und in einigen Algebren konnennoch weitere, nicht aus ∆ ableitbare Gesetze gelten.

2.3.1 Abstrakte Datentypen

Aufgrund ihrer Eigenschaften werden universelle Al-gebren und deren Varietaten manchmal als theoreti-sche Grundlage f ur abstrakte Datentypen (ADT) ver-wendet. Die Menge der Operationen Ω und deren Ty-pen (= Stelligkeiten) werden zusammen als Signa-tur bezeichnet, die im Wesentlichen der Schnittstellen-beschreibung eines ADT entspricht. Die Menge der Ge-setze ∆ spezifiziert das Verhalten des ADT nur unvoll-standig, da in einigen Algebren der Varietat V (Ω, ∆)neben ∆ weitere Gesetze gelten konnen. Von besonde-rer praktischer Bedeutung sind daher die sogenannten freien Algebren in V (Ω, ∆), in denen außer den Geset-zen in ∆ und den daraus ableitbaren Gesetzen keine

weiteren Gesetze gelten. Im nachsten Beispiel wird derADT WW (Wahrheitswert) spezifiziert:

Operationen:Ω = wahr, falsch, nicht, und, oder

Typ der Algebra: 0, 0, 1, 2, 2

Gleichungen: nicht(wahr) = falsch

nicht(falsch) = wahr

und(w, wahr) = wund(w, falsch) = falsch

und(v, w) = und(w, v)

oder(w, wahr) = wahr

oder(w, falsch) = woder(v, w) = oder(w, v)

Page 20: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 20/64

20 KAPITEL 2. EINFACHE THEORETISCHE MODELLE

Fur viele praktische Anwendungen (vor allem in Pro-grammiersprachen) ist es sinnvoll, mehrere Tragermen-gen haben zu konnen. Eine heterogene Algebra (engl.

”many-sorted algebra“ ) ist ein Tupel A1, . . . , An, Ω,wobei A1, . . . , An Tragermengen sind und Ω ein Sy-stem von Operationen ist. Jede Tragermenge hat einenNamen, der ublicherweise Sorte (engl.

”sort“ ) genannt

wird. Benennen wir also Ai mit si (f ur i ∈ 1, . . . , n).Offensichtlich sind heterogene Algebren Erweiterungender universellen Algebren (engl.

”single-sorted alge-

bra“ ). Wir mussen die Definition des oben eingef uhrtenBegriffs Signatur entsprechend erweitern: Die Signa-tur einer heterogenen Algebra beschreibt neben demTyp der Algebra auch die Menge der Sorten S =s1, . . . , sn und den Index jeder Operation, die da-bei als Funktion aufgefasst wird. Das Beispiel der alge-braischen Spezifikation einer Liste von Wahrheitswer-ten veranschaulicht diese Definition:

Sorten: WListe, WW

Operationen:leerw : WListe

cons : WW × WListe → WListe

first : WListe → WW

rest : WListe → WListe

wahr : WW

falsch : WW

nicht : WW → WW

und : WW × WW → WWoder : WW × WW → WW

Gleichungen:first(cons(w, l)) = w

rest(cons(w, l)) = lrest(leerw) = leerw

nicht(wahr) = falsch

nicht(falsch) = wahr

und(w, wahr) = wund(w, falsch) = falsch

und(v, w) = und(w, v)

oder(w, wahr) = wahr

oder(w, falsch) = woder(v, w) = oder(w, v)

Wie man an diesem Beispiel sieht, entspricht eineSorte einem Typ in einer Programmiersprache. Aller-dings kann man einen solchen Typ nur beschranktals ADT bezeichnen, da mehrere Typen in einer ein-zigen Datenkapsel spezifiziert sind. Andererseits wirdder Typ WW f ur die Spezifikation des Typs WListe ge-braucht. Man kann WListe gar nicht unabhangig vonWW spezifizieren. Wenn man diese Uberlegungen wei-ter treibt erkennt man, dass im Endeffekt jedes aus-reichend komplexe Programm in einer einzigen Daten-kapsel dargestellt werden muss. Dadurch geht naturlich

viel von der Attraktivitat von Algebren zur Spezifika-tion von abstrakten Datentypen verloren.

Es gibt aber eine einfache Losung f ur dieses Problem:

Wie man am Beispiel sieht, hangt WListe von WW ab,aber WW hangt nicht von WListe ab. Daher kann mandie Sorte WW als einen Parameter einer generischen Sor-te Liste auffassen:

Sorten: Liste

Parameter-Sorten: Element

Operationen:leer : Liste

cons : Element × Liste → Liste

first : Liste → Element

rest : Liste → Liste

Gleichungen:first(cons(e, l)) = e

rest(cons(e, l)) = lrest(leer) = leer

Der ursprungliche Typ WListe entspricht dem TypListe[WW], wobei WW f ur den Parameter Element ein-gesetzt ist. Mit parametrischen Typen sind heterogeneAlgebren tatsachlich machtige Werkzeuge zur Spezifi-kation von Programmen im Allgemeinen und von ab-strakten Datentypen im Besonderen.

2.3.2 Prozessalgebra

Vor allem f ur die Spezifikation nebenlaufiger Prozessewurden in den letzten Jahren sogenannte Prozessalge-bren entwickelt. Eine einfache Prozessalgebra mit denOperationen ∗, , +, ε vom Typ 2, 2, 2, 0 ist zumBeispiel uber folgende Gesetze definiert, wobei ∗ diehochste und + die niedrigste Prioritat hat:

x + y = y + x

(x + y) + z = x + (y + z)

x + x = x

(x + y) ∗ z = x ∗ z + y ∗ z(x ∗ y) ∗ z = x ∗ (y ∗ z)

x ∗ ε = x

ε ∗ x = x

x y = y x

(x y) z = x (y z)

x ε = x

Neben ε gibt es noch eine Reihe weiterer Konstantenin der Algebra, die nicht ausdrucklich genannt wer-den. Jede dieser Konstanten entspricht einer atoma-ren Aktion (= einem Befehl) einer virtuellen Maschi-ne. Prozesse entstehen durch wiederholte Anwendun-gen der Operatoren ∗, und + auf atomare Aktionen.

Page 21: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 21/64

2.4. LITERATURHINWEISE 21

Ein Ausdruck x ∗ y bedeutet, dass zuerst die atomarenAktionen von x und danach die atomaren Aktionenvon y ausgef uhrt werden. Im Ausdruck x y konnen

sich die Ausf uhrungen der atomaren Aktionen von xund y beliebig uberlappen, d. h. x und y werden par-allel (bzw. quasi-parallel oder nebenlaufig) ausgef uhrt.Ein Ausdruck x + y bedeutet, dass entweder x odery, aber nicht beide, ausgef uhrt werden. ε ist eine leereAktion, die nichts macht. Mit dieser Interpretation las-sen sich obige Gesetze einfach intuitiv erklaren. Beach-ten Sie, dass diese Gesetze aus Grunden der Einfach-heit gewahlt wurden. Es wurden keine Gesetze beruck-sichtigt, die den Zusammenhang zwischen sequentiel-ler und paralleler Ausf uhrung herstellen. Zum Beispielgilt f ur alle atomaren Aktionen a und b offensichtlicha ∗ b + b ∗ a = a b, obwohl dies nicht aus den Gesetzenableitbar ist. Vollstandige Prozessalgebren sind in derLiteratur beschrieben.

2.4 Literaturhinweise

Die Beschreibung des Lambda-Kalkuls wurde zum Teilaus [14] ubernommen. Dieser Artikel gibt einen um-fangreichen und verstandlichen Uberblick uber moder-ne funktionale Programmiersprachen. Kurze Beschrei-bungen strukturierter Typen und der Semantik vonTypen im Lambda-Kalkul finden sich in [6]. Generellgibt es umfangreiche Literatur zum Thema Lambda-Kalkul. Da typisierte Versionen des Lambda-Kalkulsnach wie vor Gegenstand wissenschaftlicher Abhand-lungen sind, enthalten viele aktuelle Ausgaben vonZeitschriften uber (funktionale) Programmiersprachenauch Artikel zu diesem Thema. Eine umfangreichetheoretische Einfhrung mit zahlreichen Literaturanga-ben vor allem uber Typen in funktionalen Sprachengibt Mitchell in [23].

Eine kompakte, formale und inhaltlich umfangrei-che, allerdings f ur mathematisch weniger Interessierteschwer verstandliche Einf uhrung in die logische Pro-

grammierung gibt Lloyd in [17]. Darin werden auchTypen und die Eigenschaften typisierter logischer Spra-chen beschrieben. Praktische Einf uhrungen in die lo-gische Programmierung, meist jedoch ohne auf Typeneinzugehen, bieten zahlreiche Bucher uber Prolog. Hierseien die

”Klassiker“ [27] und [7] erwahnt. Tieferge-

hende, aber nicht leicht verstandliche Beschreibungenverschiedener Ansatze zur Einf uhrung von Typen inlogische Sprachen werden in [24] gegeben.

Viele Einf uhrungen in die Algebra beinhalten auchBeschreibungen universeller und heterogener Algebren.Es gibt auch zahlreiche Abhandlungen uber algebrai-sche Spezifikation, in denen Zusammenhange zwischenAlgebren und abstrakten Datentypen erlautert werden.Als Beispiel sei [9] erwahnt. Vergleiche zwischen al-gebraisch spezifizierten und uber erweiterte Lambda-Kalkule definierte Typsysteme werden in [8] angestellt.

Gute Einf uhrungen in Prozessalgebren sind z. B. [5]und [20]. Beide Bucher sind jedoch ohne mathemati-sche Grundkenntnisse nur schwer verstandlich. Der mitAbstand wichtigste Vertreter der Prozessalgebren istheute der π-Kalkul [21, 22].

Page 22: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 22/64

22 KAPITEL 2. EINFACHE THEORETISCHE MODELLE

Page 23: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 23/64

Kapitel 3

Typen in imperativen Sprachen

In diesem Kapitel werden einige praktische Aspek-te von Typen in herkommlichen imperativen Spra-

chen untersucht. Als Grundlage wurde die Program-miersprache Ada gewahlt, da Ada ein—im Vergleichzu anderen herkommlichen imperativen Sprachen wieFortran, Cobol, C, Pascal und Modula-2—relativ um-fangreiches Typsystem hat, das zu einem guten Teilauf der im vorigen Kapitel eingef uhrten Theorie be-ruht und bereits einige f ur die objektorientierte Pro-grammierung wichtige Eigenschaften vorwegnimmt. Indiesem Kapitel werden wir uns im Wesentlichen auf Ada 83, die 1983 standardisierte Ada-Version, be-schranken. In Kapitel 5 werden wir einige neue Eigen-schaften von Ada 95, den Anfang 1995 verabschiedetenAda-Standard, behandeln.

Ein weiterer Grund f ur die Wahl von Ada ist, dassdessen Typsystem, mehr als Typsysteme in vielen an-deren imperativen Sprachen, umfangreiche dynami-sche Komponenten enthalt, die Typuberprufungen zurLaufzeit notwendig machen. Damit soll gezeigt werden,dass statische Uberprufbarkeit nicht in allen praktischverwendeten imperativen Sprachen gegeben sein muss.Dennoch ist Ada, wie alle imperativen Sprachen, eherkonservativ; das heisst, alle Programme, die moglicher-weise Typfehler im statisch uberpruften Teil des Typsy-stems enthalten, lassen sich nicht ubersetzen. Man teiltdas Typsystem also in zwei Teile. Typkompatibilitat im

einen Teil wird vom Compiler uberpruft, die im ande-ren Teil wahrend der Ausf uhrung. Die Zuordnung vonUberprufungen zu diesen beiden Teilen erfolgt aus reinpragmatischen Gesichtspunkten.

3.1 Datentypen in Ada

Das Typsystem von Ada wurde stark von Algebren be-einflusst, so dass abstrakte Datentypen zu den wich-tigsten Strukturierungsmitteln in der Sprache zahlen.Sogar die einfachen vordefinierten Typen werden durcheine implizit vorgegebene Algebra spezifiziert. Die zu-sammengesetzten Datentypen entsprechen im Großenund Ganzen den im Abschnitt uber den Lambda-

Kalkul beschriebenen, obwohl Ada die funktionale Pro-grammierung nur in einer eingeschrankten Form un-

terstutzt.Das folgende Schema gibt einen Uberblick uber diein diesem Abschnitt angesprochenen Datentypen. Da-neben werden noch Unterbereichstypen und abgeleiteteTypen jeder Typkategorie in diesem Schema behandelt.(Typen von

”tasks“ zahlen in Ada zwar auch zu den

Typen, werden aber erst in Abschnitt 3.2 erlautert.)

Datentypenelementar

skalardiskret

Aufzahlungen

ZeichenWahrheitswerteandere

ganze Zahlenvorzeichenbehaftetmodular

reelle ZahlenGleitkommazahlenFestkommazahlen

gewohnlichdezimal

Zeiger

zusammengesetztVerbundeeinfache Verbundediskriminante Verbunde

mit Variantenohne Varianten

Feldermit spezifizierten Grenzenohne spezifizierte Grenzen

3.1.1 Skalare Typen

Zu den skalaren Typen zahlen Aufzahlungstypen undZahlen. Jeder dieser Typen beschreibt eine Menge von

Page 24: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 24/64

24 KAPITEL 3. TYPEN IN IMPERATIVEN SPRACHEN

Werten ohne sichtbare innere Struktur und die auf die-sen Werten ausf uhrbaren Operationen.

Aufzahlungstypen. Der Wertebereich eines Auf-zahlungstyps wird, wie der Name schon sagt, exten-sional durch Aufzahlung der Werte festgelegt. DieWerte eines Typs sind vollstandig geordnet; je zweiWerte sind anhand ihrer relativen Position in derAufzahlung vergleichbar. Die Vergleichsoperatoren <,<=, =, >=, > und /=, alle vom Typ t × t → BOOLEAN,sind auf jedem Aufzahlungstyp t definiert. Weiterssind einige sogenannte Attribute von Aufzahlungsty-pen vordefiniert; das sind Familien von Funktionen,die einen Aufzahlungstyp als Index haben. Fur jedenAufzahlungstyp t sind diese null- oder einstelligen At-

tribute vorgegeben:

Funktion Typ Beschreibungt’FIRST t kleinster Wert in tt’LAST t großter Wert in tt’SUCC t → t nachfolgender Wertt’PRED t → t vorausgehender Wertt’POS t → CARDINAL Position des Wertest’VAL CARDINAL → t Wert an Position

Allgemeine Aufz ahlungstypen sind frei definierbareAufzahlungstypen, die keine weiteren Zugriffsfunktio-nen haben. Ein Beispiel zeigt, wie sie definiert werden:1

type TAG is (MO, DI, MI, DO, FR, SA, SO);

type ARBEITSTAG is (MO, DI, MI, DO, FR);

An diesem Beispiel ist zu beachten, dass TAG undARBEITSTAG zwei unabhangige Typen sind, die kei-ne gemeinsamen Werte enthalten. MO in TAG ist al-so ein anderer Wert als MO in ARBEITSTAG. Wenn ausdem Kontext, in dem MO verwendet wird, nicht klarhervorgeht, welches MO gemeint ist, dann muss dergewunschte Typ mittels Typqualifikation in der FormTAG’(MO) bzw. ARBEITSTAG’(MO) angegeben werden.

Von Aufzahlungstypen konnen Unterbereiche gebil-det werden. Zum Beispiel beschreibt der Typausdruck(TAG’(MO)..TAG’(FR)) einen Unterbereich von TAG.

Wahrheitswerte. Der Typ BOOLEAN ist ein vordefi-nierter Aufzahlungstyp, der nur die Werte TRUE undFALSE enthalt. Neben den f ur alle Aufzahlungstypendefinierten Funktionen gibt es noch die Operationennot, and, or und xor (

”exclusive or“) mit den ublichen

Definitionen. Die Kurzschlussoperatoren and then undor else werten, im Unterschied zu and und or, denrechten Operanden nur aus, wenn die Auswertung deslinken Operanden TRUE (f ur and then) bzw. FALSE (f ur

1

Groß- und Kleinschreibung wird in Ada nicht unterschieden.Wir folgen hier der Konvention, dass nur Schlusselworter miteinem Kleinbuchstaben beginnen.

or else) ergibt. An Hand der Wahrheitswerte wirdbesonders deutlich, dass in Ada, wie in den meistenimperativen Sprachen, das Typsystem untrennbar in

die Sprache integriert ist. Die Eigenschaften von Wahr-heitswerten werden z. B. in Kontrollstrukturen f ur Op-timierungen genutzt, so dass jede Anderung der Defi-nition von Wahrheitswerten zwangslaufig große Ande-rungen der Sprache nach sich ziehen wurde.

Zeichen. Auch der vordefinierte Datentyp CHARACTER

ist ein Aufzahlungstyp, der als Wertebereich die Zei-chen des ASCII-Codes hat. Syntaktisch werden Zei-chen in einfache Hochkomma eingeschlossen; beispiels-weise sind ’a’ und ’1’ Zeichen. Es lassen sich beliebi-ge Aufzahlungstypen definieren, deren Werte Zeichensind. Ein Beispiel ist

type GeradeZiff is (‘0’,’2’,’4’,’6’,’8’);

Zahlen. Ada unterstutzt drei Klassen von Zahlen:ganze Zahlen, Gleitkomma- und Festkommazahlen.

Ganze Zahlen. Ahnlich wie in anderen Sprachen sindauf ganzen Zahlen dieselben Vergleichsoperatoren wief ur Aufzahlungstypen, die unaren Operatoren + und -,die binaren Operatoren +, -, *, / (ganzzahlige Divi-sion), mod (Divisionsrest, Vorzeichen des rechten Ope-randen), rem (Divisionsrest, Vorzeichen des linken Ope-randen) und ** (Exponentiation) sowie die einstelli-ge Funktion ABS (Absolutwert) definiert. Operanden

und Ergebnisse (außer bei Vergleichsoperatoren) sind jeweils vom selben Typ; es finden keine impliziten Typ-umwandlungen statt. Dies ist besonders deshalb inter-essant, weil es mehrere ganzzahlige Datentypen gibt.Zu den vordefinierten ganzzahligen Datentypen zahlenSHORT INTEGER, INTEGER und LONG INTEGER. Mehrereganzzahlige Datentypen sind in imperativen Sprachenhaufig anzutreffen, und fast so haufig sind die abso-luten Wertebereiche, wie auch in Ada, implementie-rungsabhangig. Das kann bei Programmportierungenzu einer großen Hurde werden. Eine Teillosung bietetdie Moglichkeit in Ada, Bereiche f ur ganze Zahlen an-zugeben. Zum Beispiel wird durch

type SEITENZAHL is range 1..10000;

ein ganzzahliger Typ SEITENZAHL im Bereich von 1bis 10000 definiert. Bei dieser Form der Typdefinitiongibt es die Einschrankungen, dass die untere und obereGrenze statisch (durch den Compiler) berechenbar seinund der definierte Bereich in dem von LONG INTEGER

liegen muss. Auf jedem ganzzahligen Typ t sind dieFunktionen t’FIRST und t’LAST vom Typ t → t, diedie kleinste bzw. großte Zahl im Wertebereich von tzuruckliefern und vom Compiler ausgewertet werdenkonnen, definiert. Zum Beispiel kann der weiter oben

verwendete Typ CARDINAL definiert werden durch

type CARDINAL is range 0..INTEGER’LAST;

Page 25: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 25/64

3.1. DATENTYPEN IN ADA 25

Alle obigen Beispiele f ur ganzzahlige Typen fallen indie Klasse der vorzeichenbehafteten ganzzahligen Ty-pen. Wird der Wertebereich eines solchen Typs uber-

schritten, so wird in Ada eine Ausnahmebehandlung(”

exception“) ausgelost. Im Gegensatz dazu gibt es beimodularen ganzzahligen Typen keine Wertebereichs-uberschreitungen, sondern der Wert wird an den vorge-gebenen Wertebereich angepasst. Zum Beispiel umfasstder modulare Wertebereich des Typs ZWEI ZIFFERN de-finiert durch

type ZWEI ZIFFERN is mod 100;

die ganzen Zahlen von 0 bis 99 modulo 100. Die Zah-len -90, 10, 110, . . . werden als aquivalent angesehen. InSprachen wie C sind alle ganzzahligen Typen modular;

d.h. bei Uberlauf werden die Zahlen automatisch auf einen vorgegebenen modularen Wertebereich verkurzt.Anders als in Ada ist der Wertebereich stets eine Zwei-erpotenz und kann auch negative Zahlen umfassen.

Gleitkommazahlen sind auf ahnliche Weise definiertwie ganze Zahlen. Außer rem und mod sind alle Opera-tionen auf ganzen Zahlen auch auf Gleitkommazahlenanwendbar, wobei Argument- und Ergebnistypen wie-der ubereinstimmen mussen. Die vordefinierten Typensind SHORT FLOAT, FLOAT und LONG FLOAT. Es konneneigene Gleitkommatypen definiert werden, wobei ne-ben dem Wertebereich auch die Genauigkeit angegebenwerden kann:

type MY FLOAT is digits 8 range -1.0..1.0E30;

Wiederum sind sowohl der Wertebereich als auch dieGenauigkeit durch die Beschrankungen eines vordefi-nierten Typs LONG FLOAT begrenzt. Auf jeder Gleit-kommazahl t sind einige Attribute vordefiniert, die denTyp genauer spezifizieren:

Funktion Typ Beschreibungt’DIGITS CARDINAL berechenb. Dezimalstellent’MANTISSA CARDINAL Lange der Binar-Mantisset’EMAX CARDINAL maximaler Exponent

t’SMALL t kleinste positive Zahlt’LARGE t großte positive Zahlt’EPSILON t max. Fehler/Rechenschritt

Festkommazahlen. Fur Festkommazahlen gibt es kei-ne vordefinierte Typen. Alle solchen Typen mussen ex-plizit definiert werden. Die Definition eines gewohnli-chen Festkommatyps sieht folgendermaßen aus:

type SPANNUNG is delta 0.1 range 0.0..10.0;

Die delta-Klausel gibt die Mindestgenauigkeit an.Obwohl Festkommazahlen oft f ur dieselben Aufgabenwie Gleitkommazahlen herangezogen werden, sind ihreEigenschaften und Implementierungen doch—aus der

Sicht der Ada-Designer—ausreichend verschieden, umdarauf leicht voneinander abweichende Operationsmen-gen zu definieren. So kann bei Festkommazahlen einer

der Operanden der Multiplikation und der zweite Ope-rand der Division eine ganze Zahl sein, da es f ur dieseFalle einfache, effiziente Implementierungen gibt. DieseAttribute sind auf Festkommazahlen vordefiniert:

Funktion Typ Beschreibungt’DELTA CARDINAL Mindestgenauigkeitt’ACTUAL DELTA CARDINAL tatsachl. Genauigkeitt’BITS CARDINAL Speicherbedarf t’LARGE t großte Zahl

Fur Anwendungen im Finanzbereich benotigt manhaufig dezimale Festkommazahlen. Deren Typen wer-den folgendermaßen definiert:

type PREIS is delta 0.01 digits 5;

Der Wertebereich dieses Typs reicht von -999.99 bis999.99 (f unf Dezimalstellen). Da im Finanzbereich ge-nau gerechnet und gerundet werden muss, entsprichtdie Mindestgenauigkeit von dezimalen Festkommazah-len der tatsachlichen Genauigkeit.

3.1.2 Zusammengesetzte Typen

Zusammengesetzte (= strukturierte) Typen werden

aus skalaren und anderen zusammengesetzten Daten-typen (und Typen von

”tasks“, siehe Abschnitt 3.2.2)

aufgebaut. Die wichtigsten zusammengesetzten Typensind ein- und mehrdimensionale Felder mit spezifizier-ten oder unspezifizierten Grenzen und Verbunde mitund ohne Varianten.

Felder. Ein Feld wird, wie ublich, durch den Typ sei-ner Komponenten und den (oder die) Indexbereich(e)definiert. Als Indexbereich kann jeder diskrete Typ die-nen; das ist ein Aufzahlungstyp oder ein ganzzahligerTyp, oder ein Unterbereich davon. Hier sind einige Bei-

spiele:type VEKTOR is array (1..5) of INTEGER;

type MATRIX is array

(1..K*J,Func(N)..Func(N*M)) of FLOAT;

type STUNDENTAFEL is array

(TAG range MO..SA, 1..8) of TEXT;

Die generelle Form einer Felddefinition ist

type t is array (t1, . . . , tn) of t;

wobei t der definierte Typ des n-dimensionalen Feldesist, t1, . . . , tn die n Indexbereiche sind und t der Kom-ponentyp ist.

Eine Besonderheit von Ada ist, dass Feldgrenzennicht vom Compiler festgelegt werden brauchen. Daher

Page 26: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 26/64

26 KAPITEL 3. TYPEN IN IMPERATIVEN SPRACHEN

konnen auch Bereiche wie Func(N)..Func(N*M) ange-geben werden, deren Grenzen erst zur Laufzeit durchAnwendung der Funktion Func berechenbar sind. In

diesen Fallen sind naturlich auch die von den Gren-zen abhangigen Attribute erst wahrend der Ausf uhrungberechenbar. Diese Attribute sind (f ur i ∈ 1, . . . , n)vordefiniert:

Funktion Typ Beschreibungt’FIRST(i) ti untere Indexgrenzet’LAST(i) ti obere Indexgrenzet’LENGTH(i) CARDINAL Anzahl der Werte

Eine weitere Besonderheit sind Feldtypen mit unspe-zifizierten Grenzen. Beispielsweise ist diese Typdefini-tion erlaubt:

type MATR is array

(INTEGER range <>, INTEGER range <>)

of FLOAT;

Allerdings konnen von diesem abstrakten Typ direktkeine Instanzen erzeugt werden. Bei der Deklarationeiner Variable dieses Typs mussen die fehlenden Be-reichsangaben nachgetragen werden. Zum Beispiel istMY MATRIX eine Variable eines Typs mit (dynamisch)festgelegten Grenzen:

MY MATRIX: MATR(1..20,FUNC(N)..FUNC(N*M));

Der vordefinierte Typ STRING ist ein eindimensiona-

les Feld von Zeichen mit unspezifizierten Grenzen. DieLange eines Strings muss bei Variablendeklarationenangegeben werden. Die Langenangabe kann aber ent-fallen, wenn die Lange aus dem Kontext herleitbar ist.Auch hier spielen pragmatische Entscheidungen einegroßere Rolle als ein klares Konzept.

Verbundtypen. Diese werden ahnlich wie in vielenanderen Sprachen definiert:

type DATUM is

record

TAG: INTEGER range 1..31;

MONAT: MONATSNAME;JAHR: INTEGER range 1800..2200;

end record;

Verbundtypen konnen Diskriminanten enthalten; dassind spezielle Komponenten, die dazu beitragen, dieStruktur des Verbundtyps zu bestimmen. Im nachstenBeispiel werden sie dazu verwendet, die Bereichsgren-zen von Feldern des weiter oben definierten Typs MATR

zu spezifizieren.

type MATRIZEN (L: INTEGER; R: INTEGER) is

record

M1: MATR(1..2*L, 1..3*R);M2: MATR(1..3*R, 1..2*L);

end record;

Ein Spezialfall sind Verbunde mit Varianten. Varian-ten werden mittels case-Anweisung auf einer (odermehreren) Diskriminanten festgelegt. Der variante Ver-

bundtyp PERIPHERIE im folgenden Beispiel enthalt dieKomponente ZEILE genau dann, wenn die Diskrimi-nante SORTE den Wert DRUCKER enthalt. In den bei-den anderen Fallen sind statt dessen die KomponentenZYLINDER und SPUR vorhanden.

type GERAET is (DRUCKER,PLATTE,TROMMEL);

type PERIPHERIE (SORTE: GERAET) is

record

STATUS: ZUSTAND;

case SORTE is

when DRUCKER =>

ZEILE: INTEGER;

when others =>ZYLINDER: INTEGER;

SPUR: INTEGER;

end case;

end record;

Ada bietet zahlreiche Moglichkeiten, die Speicher-auslegung f ur Instanzen von Typen genau zu spezifi-zieren. Beispiele:

type Device Register is range 0..2**8-1;

D: Device Register;

for D’Size use 8;

for D’Address use To Address(16#FF00#);

type Colors is (Red,Green,Blue);

for Colors use (Red=>10,Green=>20,Blue=>30);

type Status Word is record

Mask : array (0..7) of Boolean;

Protection: Integer range 0..3;

Something : Address;

end record;

for Status Word use record

Mask : at 0*Word range 0..7;

Protection: at 0*Word range 10..11;

Something : at 1*Word range 8..31;end record;

for Status Word’Alignment use 8;

3.1.3 Unterbereichstypen,

abgeleitete Typen und Zeiger

Von jedem Typ (oder Unterbereichstyp) konnen ver-schiedene Unterbereichstypen erzeugt werden. Ein Un-terbereichstyp enthalt zusatzliche Einschrankungen ge-genuber dem Typ, dessen Unterbereichstyp er ist. Eskonnen Wertebereiche (range MO..FR, range 1..9,etc.) und Genauigkeiten (digits 6, delta 0.1, etc.)starker eingeschrankt bzw. bei Feldern Bereichsanga-ben und bei Verbunden Diskriminanten festgelegt wer-

Page 27: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 27/64

3.2. PROZEDUREN UND PROZESSE 27

den. Hier sind einige Beispiele f ur Typen, die Unterbe-reichstypen von weiter oben definierten Typen sind:

subtype WERKTAG is TAG’(MO)..TAG’(SA);(entspricht TAG range MO..SA)subtype SMALL is INTEGER range -100..100;

subtype BETRIEBS SPANNUNG is

SPANNUNG delta 0.5 range 0.0..5.0;

subtype PP is PREIS range 0.01..999.99;

subtype SMALL VEKTOR is VEKTOR(2..4);

subtype MATRIX1 is MATR(1..100,1..100);

subtype PER DR is PERIPHERIE(DRUCKER);

Unterbereichstypen stellen keine neuen Typen im ei-gentlichen Sinn dar. Sie legen nur Einschrankungenfest, die zur Laufzeit uberpruft werden. Falls eine

Einschrankung nicht erf ullt ist, wird eine Ausnahme-behandlung (engl.

”exception handling“ ) eingeleitet.

Instanzen verschiedener Unterbereichstypen eines ge-meinsamen Typs durfen in Ausdrucken gemischt vor-kommen. (Wie wir spater sehen werden, sind Unter-bereichstypen wie Untertypen verwendbar; jedoch er-folgt die Uberprufung der Einschrankungen in Adazur Laufzeit, wahrend bei Untertypen meisst statischeTypuberprufungen moglich sind.)

Alle Typen haben einen Namen. Das heisst, mit Aus-nahme von Feldern und Zeigern muss es f ur jeden ver-wendeten und nicht vordefinierten Typ eine explizite

Typdefinition geben. (Fur Feld- und Zeigertypen wirdimplizit ein nach außen nicht sichtbarer Name ange-nommen falls kein Name explizit definiert ist.) Unter-bereichstypen konnen dagegen direkt in Variablende-klarationen vorkommen. Siehe zum Beispiel obige De-klaration von MY MATRIX.

Neben den Unterbereichstypen gibt es die soge-nannten abgeleiteten Typen. Im Gegensatz zu Unter-bereichstypen sind abgeleitete Typen tatsachlich ei-genstandige Typen, die in Ausdrucken nicht gemischtvorkommen durfen. Der Compiler pruft auf Namens-gleichheit. Auf jedem abgeleiteten Typ sind dieselbenOperationen anwendbar, die auf dem Typ, aus dem erabgeleitet wurde, anwendbar sind (= Uberladen). Fol-gendes Beispiel zeigt die Syntax abgeleiteter Typdekla-rationen:

type APFEL is new INTEGER range 0..100;

type BIRNE is new INTEGER range 0..100;

Obwohl APFEL und BIRNE dieselbe Struktur haben undaus demselben Typ erzeugt wurden, stellen sie zweivollig verschiedene Typen dar. Instanzen dieser Typenwerden jedoch durch explizite Typumwandlung ver-gleichbar. Dies ist moglich, da die interne Reprasenta-tion eines abgeleiteten Typs im ubersetzten Programmder des Typs, von dem abgeleitet wurde, entspricht. Al-lerdings ist die Typumwandlung nur zwischen je zwei

Typen, die gemeinsam in einer Definition eines abgelei-teten Typs vorkommen, erlaubt. Daher kann aus demInhalt einer Variable A vom Typ Apfel nur durch die-

se zweifache Typumwandlung eine Instanz des TypsBIRNE erzeugt werden: BIRNE(INTEGER(A)).

Zum Abschluss seien Zeigertypen erwahnt. Auf jedenbeliebigen Datentyp kann auch ein Zeigertyp definiertwerden. Zum Beispiel ist APFEL P der Typ eines Zeigersauf eine Instanz des Typs Apfel:

type APFEL P is access APFEL;

MY APFEL: APFEL P := new APFEL;

Ein Zeiger dieses Typs auf eine neue Instanz von APFEL

im Heap wird mittels new APFEL erzeugt. Der Typ, dernach new steht, muss so genau spezifiziert sein, dass ei-

ne neue Instanz davon erzeugt werden kann; die Gren-zen von Arrays und Diskriminanten von Verbundenmussen festgelegt werden. Bei der Definition eines Zei-gertyps brauchen diese Einschrankungen nicht bekanntsein.

3.2 Prozeduren und Prozesse

Nicht nur Daten im eigentlichen Sinn haben einen Typ.Auch Funktionen, Prozeduren und Prozessen kann einTyp zugeordnet werden. In manchen Sprachen haben

auch Module einen Typ—z. B. in ML, in Ada aber nuransatzweise.

3.2.1 Funktionen und Prozeduren

Hier sind einige Beispiele f ur Funktions- und Prozedur-Deklarationen in Ada:

procedure PUSH (EL: in ELEMENT T;

ST: in out STACK T);

procedure POP (EL: out ELEMENT T;

ST: in out STACK T);

function MAX (X,Y: in FLOAT) return FLOAT;function "*" (X: in MATR(A,B);

Y: in MATR(B,C))

return MATR(A,C);

Die Worter in, out und in out legen den Modus(= Flussrichtung der Daten) der Parameter fest. InAda konnen vordefinierte Operatoren, wie z. B. *, uber-laden werden.

Der Kopf einer Routine kann als Typdefinition auf-gefasst werden, die den Namen der Routine, die An-zahl, Typen und Modi der Parameter und, bei Funk-tionen, den Ergebnistyp festlegt. In Ada 83 wird jedemFunktions- oder Prozedurnamen genau eine Implemen-tierung zugeordnet, die man als die einzige Instanz des

Page 28: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 28/64

28 KAPITEL 3. TYPEN IN IMPERATIVEN SPRACHEN

entsprechenden Typs der Routine ansehen kann. We-gen dieser strikten eins-zu-eins-Beziehung zwischen ei-ner Implementierung und dessen Typ reicht ein gemein-

samer Name f ur beide. Es ist wenig sinnvoll, Funktions-oder Prozedurtypen wie gewohnliche Datentypen zubehandeln, da die Angabe eines solchen Typs die da-zugehorige Implementierung eindeutig festlegt.

Im Gegensatz zu Ada 83 behandeln einige imperativeSprachen (z. B. C, Modula 2 und auch Ada 95) Typenvon Routinen wie (gewohnliche) Datentypen. Diese Ty-pen erlauben, Routinen in Feldern und Verbunden zuspeichern und als Argumente an Routinen zu uberge-ben. Dadurch wird die eins-zu-eins-Beziehung zwischenTypen und Implementierungen aufgehoben, so dass esf ur jeden Typ mehrere Implementierungen geben kann.Es ist notwendig, Implementierungen einen Namen zugeben, der sich vom Typnamen unterscheidet. In C undAda 95 wird dies durch Zeiger auf Implementierungenbewerkstelligt. Zum Beispiel:

type OP P is access function (X,Y: in FLOAT)

return FLOAT;

MY OPERATION: OP P := MAX’Access;

Welcher der beiden oben beschriebenen Ansatze istbesser? Diese Frage lasst sich nicht allgemein beant-worten. (In Sprachen wie C und Ada 95 werden daherbeide Ansatze verwendet—eine eins-zu-eins-Beziehungzwischen Typ und Implementierung f ur gewohnliche

Routinen und Trennung zwischen Typ und Implemen-tierung bei Verwendung von Zeigern auf Routinen.) Ei-nerseits bietet die Trennung von Typ und Implemen-tierung großere Flexibilitat, andererseits stellt sie aucheine zusatzliche Fehlerquelle dar. In fast allen Spra-chen, die mehrere Implementierungen eines Typs vonRoutinen erlauben, wird Typaquivalenz als Struktur-gleichheit aufgefasst. Daher haben alle Implementie-rungen mit gleich strukturierten Schnittstellen densel-ben Typ, auch wenn die Implementierungen ansonstennichts miteinander zu tun haben.

Eine eins-zu-eins-Beziehung zwischen Typ und Im-

plementierung bedeutet dagegen immer Typaquiva-lenz aufgrund von Namensgleichheit. Erst in letzterZeit wurden Sprachen entwickelt, die Aquivalenz vonFunktionstypen aufgrund von Namensgleichheit un-terstutzen und trotzdem mehrere Implementierungeneines Typs zulassen. Dabei mussen getrennte Namenf ur Routinentypen und Implementierungen definiertwerden, und jede Implementierung muss sich explizitauf einen Routinentyp beziehen, wodurch sich manch-mal der Schreibaufwand stark erhoht (siehe Kapitel 5).

Ein bedeutendes neues Typmodell f ur die objekt-orientierte Programmierung zeigt, dass Typaquivalenzaufgrund von Strukturgleichheit neben ubereinstim-menden Schnittstellen auch explizit spezifizierte uber-einstimmende semantische Eigenschaften einschließen

kann (siehe ebenfalls Kapitel 5). Dieses Typmodellist daher bezuglich der Aquivalenz von Prozedurtypennoch aussagekraftiger als ein auf Typaquivalenz auf-

grund von Namensgleichheit beruhendes System, daNamensgleichheit zwar das Zusammenfassen beliebigerProzeduren zu einem Typ erlaubt, aber diese Zuord-nung in keiner Weise begrundet.

3.2.2 Inkarnationen und Prozesse

Eine Besonderheit von Ada sind Prozesse (”

tasks“ inAda-Terminologie) f ur die nebenlaufige Programmie-rung (

”concurrency“ ). Jeder Prozess hat einen Typ,

der wie jeder Datentyp behandelt wird und von demmehrere Instanzen erzeugt werden konnen, wie folgen-des Beispiel zeigt:

task type DRUCKER TREIBER is

entry DRUCKE ZEILE(ZL: in STRING(80));

entry SEITENVORSCHUB;

entry DRUCKER STATUS(F: out STATUS);

end;

DRUCKER POOL: array(1..MAX DRUCKER)

of DRUCKER TREIBER;

DRUCKER POOL(1).DRUCKE ZEILE("Hallo!");

Die Deklaration eines solchen Typs spezifiziert Ein-

stiegspunkte (”entries“ ) in einen Prozess dieses Typs.Ein Prozess ist die Ausf uhrung eines Programmstucks,in der Regel in einer Schleife. Wenn ein

”accept state-

ment“ f ur einen Einstiegspunkt ausgef uhrt wird, wartetder Prozess so lange, bis ein anderer Prozess einen Auf-ruf dieses Einstiegspunktes macht und dadurch das

”ac-

cept statement“ und der Aufruf in Interaktion treten.Der aufrufende Prozess wartet so lange, bis das

”ac-

cept statement“ vollstandig abgearbeitet ist (”

Rendez-vous“ ). Wie bei Prozeduraufrufen konnen Argumentein beide Richtungen ubergeben werden. Fur jeden Pro-zess p und jeden seiner Einstiegspunkte e gibt es einige

Attribute:Funktion Typ Beschreibung p’TERMINATED BOOLEAN p beendet? p’PRIORITY CARDINAL Prioritat von p p’STORAGE SIZE CARDINAL Speicherbedarf f ur pe’COUNT CARDINAL Anzahl der Aufrufe

Wie bei Funktionen und Prozeduren besteht eineeins-zu-eins-Beziehung zwischen dem Typ eines

”task“

und dem von allen Prozessen dieses Typs ausgef uhr-ten Programmstuck, der Implementierung des Prozes-ses. Wie bei Funktionen und Prozeduren konnte manauch bei Prozessen diese fixe eins-zu-eins-Beziehungauflosen. Dadurch wurde eine dreistufige Hierarchie

Page 29: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 29/64

3.3. GENERISCHE PAKETE 29

entstehen: Ein Prozesstyp hat mehrere Implementie-rungen, und jede Implementierung kann mehrere Pro-zesse aufspannen.

Auch bei Routinen kann man sich eine solche Drei-teilung vorstellen, wenn man Implementierungen vonRoutinen als Typen und deren Inkarnationen (= durchRoutinenaufrufe erzeugte

”stack frames“) als Instanzen

der Implementierungen betrachtet. Die Instanzen vonTypen sind also wiederum Typen, die Instanzen einerniedrigeren Ebene beschreiben:

Typ einerer Routine oder eines”

task“↑ (ist vom Typ)

Implementierung↑ (ist vom Typ)

Routinen-Inkarnation oder”

task“

Tatsachlich werden in einigen Sprachen, z.B. einigenDialekten von Lisp und Smalltalk, Inkarnationen vonRoutinen wie gewohnliche Daten behandelt. In einigenSprachen wird auch versucht, Prozeduren, Funktionenund Prozesse—und manchmal auch Module—zu einemeinzigen Konstrukt zu vereinigen, so dass die obendargestellte Hierarchie einheitlicher wird. Naturlichist es bei Verwendung polymorpher Typsysteme auchmoglich, dass eine Implementierung mehrere Typen hatund eine Inkarnation oder ein

”task“ mehreren Im-

plementierungen entspricht, obwohl letzteres recht un-

praktisch ist.Eine mehr als zweistufige Hierarchie ist als Typ-

Instanz-Beziehung aber oft hinderlich. Eine Losungbietet hier Vererbung, wobei eine konkrete Implemen-tierung von einem abstrakten Routinen- oder Prozess-typ erbt (siehe Kapitel 5).

3.3 Generische Pakete

Ada hat die Signaturen des theoretischen Konzepts derheterogenen Algebren mit Parametersorten (siehe Ab-

schnitt 2.3) fast unverandert zur Definition der Schnitt-stellen von abstrakten Datentypen ubernommen.

Das Beispiel in Abbildung 3.1 veranschaulicht dieDefinition eines generischen Paketes (= Ada-Termino-logie f ur Module mit Parametern). Das generische Pa-ket STACK hat zwei Parameter. Der erste gibt den Typder Stackelemente an, der zweite die maximale Anzahlvon Elementen in einem Stack. Der Typ eines Stacks,STACK T, ist, wie die im offentlichen Teil des Paketes de-klarierten Prozeduren und Funktionen, allgemein ver-wendbar. Da STACK T hier als limited private de-klariert ist, ist seine Struktur nach außen unsichtbar,und seine Instanzen konnen nur von den von STACK zurVerf ugung gestellten Prozeduren und Funktionen gese-hen und manipuliert werden. Im privaten Teil wird die

generic

type ELEMENT T is private;

MAX ELEM: CARDINAL;

package STACK istype STACK T is limited private;

procedure PUSH (EL: in ELEMENT T;

ST: in out STACK T);

procedure POP (EL: out ELEMENT T;

ST: in out STACK T);

function IS EMPTY (ST: in STACK T)

return BOOLEAN;

function IS FULL (ST: in STACK T)

return BOOLEAN;

private

type STACK T is

record

EINTRAEGE: array (1..MAX ELEM)of ELEMENT T;

INDEX: INTEGER range 0..MAX ELEM;

end record;

end STACK;

package body STACK is...

end STACK;

package I STACK is new STACK(INTEGER,100);

MY STACK: I STACK.STACK T;

Abbildung 3.1: Beispiel f ur generisches Paket

Typdefinition vervollstandigt, so dass sie der Compilersehen kann.

Die Implementierung der Prozeduren und Funktio-nen eines Paketes erfolgt im

”package body“, der eine

getrennte Ubersetzungseinheit sein kann. In Ada gibtes auch bei Modulen eine eins-zu-eins-Beziehung zwi-schen dem Modultyp (= Signatur des ADT = Dekla-rationsteil eines Paketes) und seiner Implementierung(= Gesetze einer freien Algebra =

”package body“).

Auch bei Modulen konnte man, wie in einigen objekt-

orientierten Sprachen, mehrere Implementierungen ei-nes Modultyps zulassen.

Ein generisches Paket ist nicht direkt verwendbar.Statt dessen muss daraus ein neues (nicht generisches)Paket, im obigen Beispiel I STACK, erzeugt werden, indem alle Parameter durch konkrete Typen bzw. Werteersetzt sind. Dieses Paket kann man verwenden; z. B.kann man eine Variable MY STACK vom von I STACK

exportierten Typ STACK T deklarieren. Die Moglich-keit, beliebig viele neue Pakete aus STACK zu erzeugen,kann man auf vielf altige Weise nutzen. Zum Beispielkommt die einfachere Variante in Abbildung 3.2 oh-ne STACK T aus, wodurch jede Prozedur und Funktionein Argument weniger hat. Die benotigten Datenstruk-turen werden nicht mehr uber Argumente ubergeben,

Page 30: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 30/64

30 KAPITEL 3. TYPEN IN IMPERATIVEN SPRACHEN

generic

type ELEMENT T is private;

MAX ELEM: CARDINAL;

package STACK1 isprocedure PUSH (EL: in ELEMENT T);

procedure POP (EL: out ELEMENT T);

function IS EMPTY return BOOLEAN;

function IS FULL return BOOLEAN;

end STACK1;

package body STACK1 is

EINTRAEGE: array (1..MAX ELEM)

of ELEMENT T;

INDEX: INTEGER range 0..MAX ELEM;

...

end STACK1;

package I STACK1 is new STACK1(INTEGER,100);MY STACK1: I STACK1;

Abbildung 3.2: Beispiel f ur vereinfachten ADT

sondern stehen in globalen Variablen. Durch Erzeugenneuer Pakete aus STACK1 werden auch neue Versionendieser globalen Variablen erzeugt, so dass sie sich nichtgegenseitig beeinflussen konnen.

In Ada gibt es in diesem Zusammenhang ein Pro-blem: Da f ur jede Variable wie MY STACK1 auch ein neu-es Paket erzeugt werden muss, ist es im Allgemeinennicht moglich, ein Feld von solchen Stacks zu erzeugen.Im Prinzip ware es nur notig, implizite Definitionenneuer Pakete aus STACK1 bei der Initialisierung vonZeigern zuzulassen, um dieses Problem zu vermeiden.Zum Beispiel konnte man dann ein Feld von Zeigernauf Stacks etwa so deklarieren:

type ST P is access STACK1;

F: array (1..10) of ST P :=

(1..10 => new STACK1(INTEGER,100));

In jenen objektorientierten Sprachen, in denen Modu-le und Klassen zusammenfallen, werden auf ahnliche

Weise neue Objekte erzeugt. (In Ada 95 wurde jedochein anderer Weg gewahlt.)

Die Entwickler von Ada 83 geben als Begrundungf ur die Nichtunterstutzung von Routinen als Parame-ter und Zeigern auf Module an, dass durch diese Kon-

zepte die statische (= textuelle) Programmsicherheitgef ahrdet ware, da der Programmierer sich nicht mehrauf die strikte eins-zu-eins-Beziehung zwischen Namenund Routinen bzw. Modulen verlassen konnte. Mitt-lerweile wurde diese Entscheidung teilweise revidiert:Ada 95 unterstutzt Routinen als Parameter und bie-tet neue Sprachkonstrukte mit ahnlicher Funktionalitatwie Zeiger auf Module (siehe Abschnitt 5.1.1). Wegender geforderten statischen Programmsicherheit gibt es jedoch auch weiterhin keine Zeiger auf Module.

Wahrend STACK einer Algebra sehr genau entspricht,kann STACK1 nicht einfach als Algebra aufgefasst wer-den. Der Grund liegt in den globalen Variablen, die inAlgebren unbekannt sind. Andererseits sind solche glo-bale Variablen zusammen mit der Moglichkeit, beliebigviele Versionen davon zu erzeugen, ein machtiges undhaufig verwendetes Werkzeug in modernen Program-miersprachen. Daher entwickelte man neben Algebrenauch andere theoretische Modelle, die den tatsachlichenGegebenheiten in vielen imperativen Sprachen besserentsprechen.

3.4 Literaturhinweise

Es gibt zahlreiche B¨ucher und Zeitschriftenartikel

¨uber

Ada. In der TU-Hauptbibliothek sind uber 80 relevan-te Bucher, Zeitschriften und Konferenzberichte vorhan-den. Der offizielle Sprachstandard von Ada 83 ist in [2]beschrieben, der von Ada 95 in [3]. Daruber hinausenthalt praktisch jedes Buch uber eine typisierte Spra-che auch ein (oder mehrere) Kapitel uber Typen in der jeweiligen Sprache.

Page 31: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 31/64

Kapitel 4

Modelle polymorpher Typsysteme und

Typen in funktionalen Sprachen

In diesem Kapitel werden die Beschreibungen ausKapitel 2 in Richtung universell quantifizierter poly-morpher Typsysteme verfeinert und Beispiele von Ty-pen in funktionalen Sprachen behandelt. Abschnitt 4.1f uhrt Subtyping in Algebren ein. (Parametrische alge-braische Typsysteme wurden bereits in Abschnitt 2.3eingef uhrt.) Abschnitt 4.2 gibt einen kurzen Uberblickuber Typinferenz und das Damas-Milner Typsystem,eine Erweiterung des einfachen typisierten Lambda-Kalkuls. In diesem Abschnitt wird auch das Typ-system der Sprache ML kurz erlautert. Abschnitt 4.3beschaftigt sich mit Ansatzen von Subtyping in funk-

tionalen Sprachen. Diese Ansatze werden auch alseine Grundlage f ur Typen in der objektorientiertenProgrammierung verwendet. Schließlich wird in Ab-schnitt 4.4 das PER-Modell kurz angerissen, um eintieferes Verstandnis des Typbegriffs zu vermitteln.

4.1 Order-sorted Algebras

Heterogene Algebren (siehe Abschnitt 2.3) sind ei-ne von mehreren theoretischen Grundlagen f ur dieEinf uhrung einer großen Zahl von Typen in Program-

miersprachen. Im vorigen Kapitel wurde die Flexibi-litat dieser Grundlage zur Definition von allgemeinenModulen und abstrakten Datentypen genutzt. Diesegroße Flexibilitat ist jedoch nicht immer erwunscht, dasie leicht zu unstrukturierten, komplexen Programmenf uhrt, in denen Routinen auf vielf altige und oft un-durchschaubare Weise uberladen sind. Man versuchtdaher, zusatzliche Strukturierungsmechanismen ein-zuf uhren, die einen

”Wildwuchs“ an Typen und Rou-

tinen vermeiden helfen. In heterogenen Algebren ge-schieht dies oft dadurch, dass eine Ordnung auf Sorteneingef uhrt wird. Man spricht dann von einer

”order-

sorted algebra“. Durch die zusatzliche Strukturierung

wird auch Subtyping ermoglicht.

Zur Wiederholung: Eine heterogene Algebra ist ein

Tupel (A1, . . . , An, Ω), wobei A1, . . . , An Tragermen-gen sind und Ω ein System von Operationen ist, derenBedeutungen durch eine Menge von Gesetzen ∆ festge-legt werden. Jede Tragermenge Ai entspricht genau ei-ner Sorte si aus der Menge der Sorten S = s1, . . . , sn.Jede Operation in Ω hat einen Typ und einen In-dex. Zum Beispiel kann man Typ und Index einer m-stelligen Operation ω ∈ Ω in der Form

ω : si1 × · · · × sim → sj

anschreiben, wobei die Indizes j, i1, . . . , im ∈ 1, . . . , nund sj , si1 , . . . , sim ∈ S . Die Signatur einer Algebra

entspricht in etwa der Schnittstelle eines ADT. Sie be-steht aus S , Ω und den Typen und Indizes der Opera-tionen in Ω. Sie enthalt weder die Tragermengen nochdie Gesetze.

4.1.1 Verbande uber Sortenmengen

Fur die Einf uhrung einer Ordnung auf einer Algebrabietet sich ihre Signatur an, da nur sie außerhalb ei-nes ADT sichtbar ist. Hier bietet sich vor allem dieMenge S an, da ihre Elemente den Typen in Program-miersprachen entsprechen. Wir nehmen an, dass S diebeiden Sorten und ⊥ enthalt und f uhren einen Ver-band S, ≤, , , , ⊥ uber S ein. Dieser hat folgendeEigenschaften:

1. ≤ ist eine reflexive, transitive und antisymmetri-sche Relation auf S (= Halbordnung von S ), sodass ⊥ ≤ s und s ≤ f ur alle s ∈ S gilt.

2. Fur jedes s1 ∈ S und s2 ∈ S sind das Supremums1 s2 = s3 ∈ S (s3 ist das kleinste Element vonS mit s1 ≤ s3 und s2 ≤ s3) und Infimum s1 s2 = s4 ∈ S (s4 ist das großte Element von S mits4 ≤ s1 und s4 ≤ s2) eindeutig definiert. Dabeigilt: s s = s ⇔ s ≤ s ⇔ s s = s.

Ein Verband kann leicht als ein zusammenhangenderGraph (Hasse-Diagramm ) dargestellt werden. In diesen

Page 32: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 32/64

32 KAPITEL 4. MODELLE POLYMORPHER TYPSYSTEME

beiden Beispielen stellt jeder Punkt ein Element von S dar:

t

t t t

t t t

t

t

t t

t t t t

t

e e e

¡ ¡ ¡

d d d

¡ ¡ ¡

e e e

¡ ¡ ¡

e e e

⊥ ⊥

Fur je zwei Sorten s und s gilt s ≤ s genau dann wennes im Graphen mindestens einen stets von unten nachoben f uhrenden Pfad zwischen s und s gibt. Auch Su-

premum und Infimum zweier Sorten sind im Graphenleicht zu finden, wenn man die Pfade nach oben bzw.unten verfolgt.

Die obigen Graphen kann man so deuten, dass sie dasEnthaltensein der Instanzenmengen von Typen bzw.Vererbung in objektorientierten Programmen darstel-len. Sorten entsprechen ja Typen in Programmierspra-chen. Fur s, s ∈ S mit s ≤ s entspricht s einem Un-tertyp von s und s einem Obertyp von s. Der linkeGraph stellt Mehrfach- und der rechte, abgesehen vonder Sorte ⊥, Einfachvererbung dar.

Einige wichtige Eigenschaften heterogener Algebren,auf deren Sortenmengen Verbande definiert sind, las-sen sich leicht verstehen. Zwischen den TragermengenA1, . . . , An (= Wertemengen) und den entsprechendenSorten s1, . . . , sn (= Typen) gelten folgende Beziehun-gen (f ur alle i,j,k ∈ 1, . . . , n):

si ≤ sj ⇔ Ai ⊆ Aj

si = sj sk ⇔ Ai ⊆ Aj ∩ Ak

Intuitiv bedeutet das, dass jeder Untertyp eine Mengevon Werten beschreibt, die eine Teilmenge der von sei-nem Obertyp beschriebenen Wertemenge ist. Weitersgilt, dass je zwei von Typen beschriebene Wertemen-

gen nur solche gemeinsame Werte enthalten, die auchin der Wertemenge eines gemeinsamen Untertyps dieserzwei Typen enthalten sind. Die durch ⊥ beschriebenenWerte sind in jeder Tragermenge enthalten; und be-schreibt die Menge aller Werte. Das heisst, f ur si = und sj = ⊥ gilt

Ai =

k=1,...,n

Ak und Aj =

k=1,...,n

Ak.

Meist enthalt Aj nur einen Wert, der oft ebenfallsdurch ⊥ dargestellt wird. Er reprasentiert undefinier-te Ergebnisse von Operationen, also (nicht existie-rende) Ergebnisse von Endlosrekursionen bzw. Fehler-zustande.

4.1.2 Polymorphe Operationen

Dadurch, dass Tragermengen teilweise ineinander ent-halten sind, sind auch die Operationen automatischpolymorph. Angenommen, eine Operation ω ∈ Ω mitω : si1 × · · · × sim → sj ist durch die Gesetze in ∆vollstandig definiert. Dann ist ω durch dieselben Ge-setze gleichzeitig auch als (f ur k ∈ 1, . . . , m)

ω : si1

× · · · × sim

→ sj mit sj ≤ sj und sik

≤ sik

vollstandig definiert. Das ist auch intuitiv klar, da je-de Operation eine Menge von Eingangswerten in eineMenge von Ergebniswerten abbildet. Naturlich ist dieOperation auch auf jeder beliebigen Teilmenge der Ein-gangswerte definiert, und das Ergebnis liegt stets auch

in jeder Obermenge der tatsachlich moglichen Ergeb-niswerte.

Wenn man typisierte Variablen betrachtet, bedeutetdas f ur eine Programmiersprache folgendes: In einemAufruf einer Routine kann an der Position eines Ein-gangsparameters jede Variable stehen, deren statischerTyp ein Untertyp des statischen Typs des entsprechen-den formalen Parameters ist. Der aktuelle Wert derVariable zum Zeitpunkt des Routinenaufrufs ist dannin jedem Fall eine Instanz des Parametertyps. An derPosition eines Ausgangsparameters kann jede Variablestehen, deren statischer Typ ein Obertyp des entspre-

chenden Parametertyps ist, damit der von der Proze-dur zuruckgegebene Wert in jedem Fall eine Instanz desstatischen Variablentyps ist. Ein Durchgangsparametermuss genau denselben statischen Typ haben wie eineVariable an dieser Position, da die Variable sowohl einEingangsargument liefert als auch einen Ergebniswertubernimmt. Ein Beispiel verdeutlicht dies. Die Typendes Programms seien folgendermaßen angeordnet:

r r r r r

¨ ¨ ¨ ¨

¨ ¨ ¨ ¨ ¨

r r r r r

¨ ¨ ¨ ¨ ¨

¨ ¨ ¨ ¨ ¨

r r r r r r r r r r r r r r

¨ ¨ ¨ ¨ ¨

Person

Wert

Student Angestellter

Geldbetrag

Werkstudent

Damit ware dieses Programmstuck in einer Pseudo-sprache erlaubt:

Page 33: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 33/64

4.2. TYPINFERENZ UND DAS TYPSYSTEM VON ML 33

procedure Beitrag(V: in Person;

S: in out Wert;

B: out Geldbetrag);

VERSICHERT: Student := ...;VERS LEIST: Wert := ...;

BEITRAG: Wert;

Beitrag(VERSICHERT, VERS LEIST, BEITRAG);

Nehmen wir an, dass in diesem Beispiel die ProzedurBeitrag f ur einen Versicherten (erstes Argument) dieVersicherungsleistungen (zweites Argument) anpasstund die Hohe des Versicherungsbeitrages (drittes Ar-gument) ermittelt. Die Prozedur berechnet die Ergeb-nisse aufgrund der Werte der ubergebenen Argumen-te. Das Ergebnis kann nur dann richtig berechnet wer-den, wenn alle Argumente in den von der Prozedur er-

warteten Wertemengen liegen. Nachdem jede Instanzvon Student auch eine Instanz von Person ist, kannman der Prozedur jeden beliebigen Wert in einer Va-riable vom statischen Typ Student als erstes Argumentubergeben. Die Prozedur liefert an dritter Argument-position einen Geldbetrag zuruck. Da jede Instanz vonGeldbetrag auch eine Instanz von Wert ist, kann eineVariable vom statischen Typ Wert das Ergebnis uber-nehmen. Fur den Durchgangsparameter an zweiter Po-sition kann nur eine Variable vom statischen Typ Wert

verwendet werden, da diese Variable sowohl das Ein-gangsargument liefert als auch das Ergebnis aufnimmt.

Zusammengefasst gilt in jeder stark typisierten Spra-che: Wird der Wert einer Variable x an eine andereVariable y zugewiesen, muss der statische Typ von xein Untertyp des statischen Typs von y sein. Entspte-chendes gilt auch f ur die Parameterubergabe. Durchdiese Regel kann bei der Zuweisung zwar Informati-on uber den Typ eines Wertes verloren gehen, aber esist niemals notwendig, Typuberprufungen zur Laufzeitvorzunehmen.

4.2 Typinferenz und

das Typsystem von MLIm typisierten Lambda-Kalkul (siehe Abschnitt 2.1)konnen monomorphe Funktionen auf einfache Weiseausgedruckt werden. Beispielsweise kann man leichtFunktionen der beiden Typen Integer → Integer undFloat → Float erzeugen. Man kann aber keine generi-sche Funktion schreiben, die ein Argument eines belie-bigen (unbekannten) Typs in ein Ergebnis vom Argu-menttyp abbildet. Wir hatten zum Beispiel gerne dieMoglichkeit, Funktionen des Typs a→a auszudrucken,wobei a ein Typparameter ist, f ur den jeder beliebigeTyp eingesetzt werden kann. Solche Typparameter wer-den implizit als universell uber die Menge aller Typenquantifiziert angesehen.

Als einen ersten Versuch zur Einf uhrung von Typpa-rametern in den Lambda-Kalkul erweitern wir die Kon-versionsregeln entsprechend. Zum Beispiel f ugen wir zu

den Regeln aus Abschnitt 2.1.2 unter anderem die Re-geln

((λx:v.e1:t1):v→t1 e2:t2):t1

⇐⇒ ([t2/v]([e2/x]e1:t1)):t1

((λx:v.e1:v):v→v e2:t):t⇐⇒ ([t/v]([e2/x]e2:t)):t

ein, wobei v jeweils ein Typparameter ist. Allerdingshaben wir jetzt ein Problem: Es ist im Allgemeinennicht feststellbar, ob ein Programm typkonsistent istoder nicht. Dieses Typuberprufungsproblem ist genau-so unentscheidbar wie viele ahnliche Ansatze.

4.2.1 Typinferenz

Statt zu versuchen, das schwierige Typuberprufungs-problem direkt zu losen, kann man einen Schritt weitergehen: Alle Typbezeichner werden aus den Lambda-Ausdrucken entfernt, so dass nur mehr der ursprung-liche untypisierte Lambda-Kalkul ubrig bleibt. Dannwird versucht, die Typen der Lambda-Ausdrucke ausdiesen untypisierten Ausdrucken zu berechnen. DiesenBerechnungsvorgang nennt man Typinferenz. Praktischbedeutet das, dass der Programmierer seine Program-

me schreibt, ohne Typen angeben zu mussen. Der Com-piler berechnet die fehlenden Typangaben aus der Ver-wendung der einzelnen Programmteile. Viele modernefunktionale Programmiersprachen, vor allem ML unddessen Dialekte sowie Miranda und Haskell, beruhentatsachlich auf dem Prinzip der Typinferenz. Hier istein kurzer Ausschnitt aus einer Interaktion mit einemStandard-ML-Interpreter:

Eingabe: fun pluszwei x = x + 2 ;Ergebnis: val pluszwei = fn : int -> int

Eingabe: fun ident x = x ;

Ergebnis: val ident = fn : ’a -> ’a

Die erste Eingabe definiert (aufgrund des Schlusselwor-tes fun) die Funktion pluszwei mit dem Argument x,die als Ergebnis x + 2 liefert, wobei + die ganzzahli-ge Addition bezeichnet. Das Ergebnis dieser Definitionin der zweiten Zeile besagt, dass pluszwei eine Funk-tion von int nach int ist. Der eingebaute Compilerhat also anhand der Addition mit 2 festgestellt, dasspluszwei nur auf int definiert ist und ein Ergebnisvom Typ int liefert. In der dritten Zeile wird die einfa-che Identitatsfunktion definiert. Hier erkennt der Com-piler, dass ident f ur alle Argumenttypen definiert istund das Funktionsergebnis denselben Typ wie das Ar-gument hat. Der Typ der Funktion ist also ’a -> ’a,wobei ’a einen Typparameter bezeichnet.

Page 34: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 34/64

34 KAPITEL 4. MODELLE POLYMORPHER TYPSYSTEME

4.2.2 Typen in ML

Neuere funktionale Sprachen wie ML bieten vielf alti-ge Moglichkeiten zur Typdefinition. Neben den ubli-chen vordefinierten Typen wie bool, int, real undstring gibt es zahlreiche Typkonstruktoren. Vordefi-nierte Typkonstruktoren erlauben die Definition vonListen, Verbunden, Tupeln (unter anderem Paare)und Funktionen mit einfacher Syntax. Zum Beispielsind [1,2,3], ["a","b"] und nil Listen der Ty-pen int list, string list bzw. ’t list. LetztererTyp is polymorph, da nil eine Instanz von int list,string list, bool list, etc. ist.

Tupel werden benutzt, um Instanzen unterschiedli-cher Typen miteinander zu kombinieren. Zum Beispielhaben die Tupel (5, 6), (true, "fact", 7) und

(true, nil) die Typen int*int, bool*string*intbzw. bool*(’t list). Typen von Verbunden undFunktionen haben die in Abschnitt 2.1.2 und 2.1.3 ein-gef uhrte Form. Funktionstypen konnen aber Typpara-meter enthalten. So ist z. B. name:string,id:int ein Verbundtyp und ’t list -> bool ein Funktions-typ. (Tupel sind eigentlich nur Verbunde mit den La-beln 1, 2, . . . in abgekurzter Schreibweise.)

Zusatzlich zu diesen vordefinierten Typen und Typ-konstruktoren gibt es drei weitere Moglichkeiten, Ty-pen zu definieren. Die erste Moglichkeit erlaubt einfachnur, neue Namen f ur existierende Typen einzuf uhren,

wie diese Beispiele zeigen:

type intpair = int*int;

type ’a pair = ’a * ’a;

type boolpair = bool pair;

Die zweite Moglichkeit, einen Typ zu definieren, be-schreibt, wie Werte des neuen Typs aussehen und er-zeugt werden. Hier sind einige Beispiele:

datatype color = red | green | blue;

datatype publications =

nopubs | journal of int | conf of int;

datatype ’t stack =empty | push of ’t * ’t stack;

Die erste Zeile definiert einfach nur einen Aufzahlungs-typ mit den gegebenen Instanzen. Der Typ in derzweiten Zeile spezifiziert, dass einige Instanzen desTyps mit Parametern versehen sind. Zum Beispiel sindjournal(3) und conf(7) Instanzen dieses Typs. Sol-che Datentypen konnen auch rekursiv sein, wie diedritte Zeile zeigt. Instanzen dieses Typs sind empty,push(2,empty), push(7,push(2,empty)), usw. Auchder vordefinierte Typ ’a list kann auf diese Weisedefiniert werden:

datatype ’a list = nil | :: of ’a * ’a list;

Die dritte Moglichkeit, einen Typ zu definieren, ver-steckt die interne Reprasentation eines ADT:

abstype ’a lifo = Stack of ’a list

with exception error;

val create = Stack nil;

fun push(x, Stack xs) = Stack(x::xs);

fun pop(Stack nil) = raise error;

| pop(Stack [e]) = Stack nil;

| pop(Stack(x::xs)) = Stack xs;

fun top(Stack nil) = raise error;

| top(Stack(x::xs)) = x;

end;

Der ADT ’a lifo exportiert nur die Ausnahme error,den Wert create und die Funktionen push, pop und

top. Die interne Darstellung (= die Liste im Argumentvon Stack) ist nach außen nicht sichtbar. Daher sindauch die Listenoperationen auf Instanzen von ’a lifo

nicht direkt anwendbar, sonder nur uber Stack.ML bietet auch ein umfangreiches Modulkonzept.

Zum Beispiel kann man einen Stack auch folgender-maßen definieren:

structure S = struct

exception error;

type ’t Stack = ’t list;

val create = Stack nil;

fun push(x, Stack xs) = Stack(x::xs);

fun pop(Stack nil) = raise error;| pop(Stack [e]) = Stack nil;

| pop(Stack(x::xs)) = Stack xs;

fun top(Stack nil) = raise error;

| top(Stack(x:xs)) = x;

end;

Im Gegensatz zu abstrakten Datentypen versteckenModule in ML die interne Datenrepresentation nicht.Auf alle Komponenten des Moduls kann mittels Punkt-Notation zugegriffen werden. Ein Stack mit einem Ele-ment kann also so erzeugt werden:

val v = S.push(2, S.create);

Als Alternative dazu kann ein Modul mittels open ex-plizit geoffnet werden, so dass alle Namen im Moduldaraufhin auch ohne Punkt-Notation zuganglich sind:

open S;

val v = push(2, create);

Sehr oft mochte man aber einen Teil des Inhalts ei-nes Moduls vor unberechtigten Zugriffen schutzen. Furdiesen Zweck bietet ML Signaturen, die einem Typ ei-nes Moduls entsprechen und die sichtbare Funktiona-litat des Moduls einschranken konnen. Zum Beispielbeschreibt die Signatur st, dass ein Modul dieses Typs

Page 35: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 35/64

4.2. TYPINFERENZ UND DAS TYPSYSTEM VON ML 35

einen nicht weiter beschriebenen Typ q und die Funk-tionen push und pop der Typen ’t*q->q bzw. q->q

exportiert:

signature st = sig

type q;

val push: ’t * q -> q;

val pop: q -> q;

end;

Ein neues Modul mit dieser Funktionalitat kann manleicht erzeugen, indem man st mit S in Beziehung setzt:

structure S1:st = S;

In S1 sind create und top nicht mehr zugreifbar. Auf

jedes Modul kann uber jede gewunschten Signatur zu-gegriffen werden, solange der Inhalt des Moduls mit derSignatur in Ubereinstimmung gebracht werden kann.Eine Signatur kann beispielsweise als Parametertyp ei-ner Funktion angegeben sein. Jedes Modul, das dieserSignatur entspricht, kann dann als Argument uberge-ben werden. Ein Modul entspricht auch mehreren Si-gnaturen, wie das nachste Beispiel zeigt:

signature stringStack = sig

exception error;

type string Stack;

val create: string Stack;

val push: string * string Stack-> string Stack;

val pop: string Stack -> string Stack;

val top: string Stack -> string;

end;

structure S2:stringStack = S;

Durch stringStack wird der Inhalt der Stacks in S2

auf Zeichenketten eingeschrankt. S2 ist also nicht mehrpolymorph.

4.2.3 Ein Typinferenz-Algorithmus

Fur eine einfache funktionale Sprache kann ein Algo-rithmus zur Typinferenz leicht formal definiert werden.Als Beispiel nehmen wir eine Sprache, in der es nebenParameternamen und Funktionen nur noch Konstanten(wie Zahlen und Zeichenketten, aber auch vordefinierteOperationen darauf) gibt; es werden also nur Basisty-pen und Funktionstypen, aber keine strukturierten Ty-pen unterstutzt. In der Literatur wird eine solche Spra-che oft durch λ→t bezeichnet. Typen in dieser Sprachesollen durch einen Typinferenz-Algorithmus berechnetwerden. Die Funktion PT, die diesen Algorithmus im-plementiert, ist folgendermaßen definiert, wobei c ei-ne Konstante, x einen Parameternamen, v einen Typ-parameter, s bzw. t einen Typ, e einen untypisierten

Lambda-Ausdruck und f einen typisierten Lambda-Ausdruck bezeichnet:

PT(c) = ∅ c:t (t enthalt keine Typparameter)

PT(x) = x:v x:v (v neu)PT(e e) = let Γ f :t = PT(e);

Γ f :t = PT(e);θ = unify(s = s | x:s ∈ Γ; x:s ∈ Γ

∪ t = t→v) (v neu)in θΓ ∪ θΓ θ(f f ):θv

PT(λx.e) = let Γ f :t = PT(e);in if x:s ∈ Γ f ur ein beliebiges s

then Γ \ x:s (λx:s.f ):s→telse Γ (λx:v.f ):v→t (v neu)

Γ (Gamma) ist eine Umgebung mit (also eine Menge

von) Typzuweisungen an Parameternamen. Die ersteGleichung ordnet einer Konstante c einfach nur den(parameterfreien) Typ dieser Konstante zu, wobei dieUmgebung leer bleibt. Dieser Typ muss eindeutig de-finiert sein. Zum Beispiel ist int der Typ von 1 undint → int der Typ von +, dem Operator zur Additionzweier ganzer Zahlen.

Die zweite Gleichung weist einem Parameternameneinen beliebigen neuen Typparameter als Typ zu. Wenndieser Typ spater im Algorithmus nicht weiter ein-geschrankt wird, kann der Parameter durch beliebigeAusdrucke ersetzt werden.

Die dritte Gleichung behandelt Anwendungen (=

Funktionsaufrufe). Sowohl f ur die Funktion als auchdessen Parameter werden durch rekursive Aufrufe vonPT entsprechende Umgebungen und typisierte Aus-drucke berechnet. Als Kern des Algorithmus mussendie den Parameternamen in den Umgebungen zugeord-neten Typen miteinander unifiziert werden. Es wird einallgemeinster Unifikator θ berechnet, der auf alle Ty-pen in den Umgebungen und Ausdrucken angewendetwird. Durch die Unifikation werden Typparameter, so-fern notwendig, aneinander und an Typen gebunden.

Die letzte Gleichung behandelt Abstraktionen. Fallsder Parameter x im Funktionsrumpf e vorkommt,

kommt eine Typzuweisung an x auch in der e entspre-chenden Umgebung vor. In diesem Fall entspricht derzugewiesene Typ dem Typ des Parameters. Die Typzu-weisung wird aus der Umgebung entfernt, da x durchdie Abstraktion gebunden wird, also weiter außen nichtin derselben Bedeutung vorkommt. Falls x nicht in evorkommt, ist der Parametertyp beliebig; es kann daf urein neuer Typparameter verwendet werden.

Da die Berechnung eines allgemeinsten Unifikatorseine zentrale Bedeutung f ur die Typinferenz hat, wollenwir im Folgenden die Funktion “unify” formal definie-ren. Das Ergebnis der Funktion ist entweder eine Listevon Typparameterersetzungen [t1/v1] · · · [tn/vn] oder“fail”. In letzterem Fall schlagt die Unifikation fehl; dasProgramm, auf das PT angewendet wird, ist in diesem

Page 36: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 36/64

36 KAPITEL 4. MODELLE POLYMORPHER TYPSYSTEME

Fall nicht typkonsistent.

unify(∅) = ∅unify(E ∪ b = b) = if b = b then fail

else unify(E )unify(E ∪ v = t) = if v = t then unify(E )

else if v occurs in t then failelse unify([t/v]E ) [t/v]

unify(E ∪ s→s = t→t) =unify(E ∪ s = t, s = t)

Nach demselben Prinzip wurden zahlreiche weitereTypinferenz-Algorithmen f ur komplexere Sprachen wieML entwickelt. Eine Analyse dieser Algorithmen wurdeaber den Rahmen des Skriptums sprengen.

4.2.4 Transparenz und Entscheidbar-keit von Typinferenz

Typinferenz f uhrt nicht immer zum gewunschten Er-gebnis. Sehr oft ist der Compiler nicht in der Lage,einen allgemeinsten Typ eines Ausdrucks zu bestim-men. In diesen Fallen gibt der Compiler eine Fehler-meldung aus. Zum Beispiel, in der Funktion

fun max(x:int, y) = if x > y then x else y;

kann der Compiler nur anhand der expliziten Typanga-be von x (: gefolgt von int) erkennen, dass eine Funkti-

on > vom Typ (int*int)->bool verlangt wird. Ohnediese Information ware auch (real*real)->bool einmoglicher Kanditat, aber nicht (’t*’t)->bool, da >

nicht von allen Typen ’t akzeptiert wird. Ohne expli-zite Typangabe von x wurde der Compiler eine Fehler-meldung liefern. In der Praxis ist f ur einen Program-mierer leider nicht immer gleich erkennbar wo der Feh-ler liegt, da die Ursache daf ur nicht unbedingt an derStelle liegen muss, an der der Fehler gemeldet wird.Eine scheinbar harmlose Anderung eines Programmskann zu einer Fehlermeldung an einer von der Ande-rung anscheinend gar nicht betroffenen Stelle f uhren.

Das Problem, dass ein Parameter mehr als nur einenkonkreten Typ aber doch nicht jeden beliebigen Typhaben kann (z.B. nur int oder real), wurde in Has-kell durch Typklassen zu einem großen Teil gelost: Ei-ne Typklasse definiert eine Klasse von Typen, die ge-meinsame Operationen haben. Beispielsweise gehorendie Typen Int und Real zur Typklasse Num, die Ope-rationen wie + und > definiert. Trifft der Typinferenz-algorithmus auf einen dieser Operatoren, dann f uhrter einen neuen Typparameter ein, beschrankt diesen jedoch auf Num. Solche eingeschrankten Typparame-ter sind nur mit Typen unifizierbar, die zur TypklasseNum gehoren (Instanzen davon sind), oder mit anderenTypparametern, auf denen es keine oder nur kompati-ble Einschrankungen gibt. Das Konzept der Typklas-

sen entspricht im Wesentlichen der gebundenen Gene-rizitat (siehe Kapitel 5). Ursachen f ur vom Compilergefundene Typfehler sind jedoch auch zusammen mit

Typklassen oft nur schwer zu verstehen und zu finden.Theoretische Ergebnise besagen, dass das Typinfe-renzproblem im Allgemeinen unentscheidbar ist. Je-doch gibt es ein etwas eingeschrankteres, daf ur aberentscheidbares polymorphes Typsystem, das fast somachtig wie das uneingeschrankte ist. Es wird in denoben genannten funktionalen Sprachen im Zusammen-hang mit Typinferenz verwendet. Die Einschrankungdieses Typsystems ist, dass rekursive Funktionen inner-halb des Rumpfes der Funktionsdefinition nur mono-morph verwendet werden durfen. Das heisst, alle direktrekursiven Aufrufe mussen dieselben Argumenttypenhaben. Alle anderen (rekursionsfreien oder indirekt re-kursiven) Aufrufe konnen polymorph sein, so dass jederAufruf andere Argumenttypen haben kann. Alle Versu-che, diese Einschrankung aufzuheben, f uhrten bislangzu Typsystemen, in denen das Typuberprufungspro-blem unentscheidbar ist. Es gibt aber Tendenzen, dieseunentscheidbaren Typsysteme in der Praxis einzuset-zen, da die Unentscheidbarkeit nur in wenigen prak-tisch verwendbaren Programmen zum Problem wird.Bei unentscheidbaren Programmen kommt der Compi-ler in eine Endlosschleife oder bricht ab weil er zu wenigSpeicher hat.

4.3 Funktionale Ansatze f ur

Subtyping

Allgebren konnen sowohl mit Generizitat als auch mitUntertypen umgehen, wie wir in Abschnitt 2.3 und4.1 gesehen haben. Aber Algebren haben einen Nach-teil: Es fehlt ihnen an Ausdrucksstarke. Einerseitskonnen Algebren, wie bereits erwahnt, nicht mit globa-len Zustanden (Variablen) umgehen, was besonders f urobjektorientierte Sprachen ein großer Nachteil ist. An-dererseits ist die Aquivalenz zweier Algebren innerhalb

der Theorie in der Regel nicht formal nachweisbar, auchwenn die Algebren (mit ausreichendem Meta-Wissen)ganz offensichtlich aquivalent sind.

Funktionale Ansatze haben diese Nachteile nicht; siesind daher f ur die meisten Anwendungen ausdrucks-starker. In Abschnitt 4.2 haben wir gesehen, dass funk-tionale Sprachen mit einem sehr machtigen generischenTypsystem ausgestattet sein konnen. Bisher fehlt je-doch jede Unterstutzung f ur Subtyping. Im Folgendenwerden funktionale Ansatze f ur Subtyping eingef uhrt.

4.3.1 Einfache Untertypbeziehungen

Ahnlich wie bei”

order-sorted algebras“ beschreibt einTyp im Wesentlichen eine Wertemenge, und uber den

Page 37: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 37/64

4.3. FUNKTIONALE ANS ATZE F UR SUBTYPING 37

Typen ist ein Verband definiert, der der Enthaltensein-Relation der Wertemengen entspricht.1 Dieser Verbandist jedoch nicht direkt vorgegeben, sondern leitet sich

aus den Eigenschaften der Typen ab. Wir mussen al-so f ur alle Arten von Typen Regeln einf uhren, die dieUntertypbeziehungen zwischen gegebenen Typen her-leiten. Eine Untertypbeziehung s ≤ t besagt, dass derTyp s ein Untertyp des Typs t ist. Die Untertypbezie-hungen mussen dem Ersetzbarkeitsprinzip entsprechen:

Ein Typ s ist ein Untertyp eines Typs t wenneine Instanz von s uberall verwendet werdenkann wo eine Instanz von t erwartet wird.

Dieses Ersetzbarkeitsprinzip definiert im Wesentlichenden Begriff Subtyping.

Die erste Regel besagt, dass jeder Typ t ein Untertypvon sich selbst ist; die Untertyprelation ist reflexiv:

Γ t ≤ t (4.1)

Diese Regel wird gelesen als”

Γ leitet t ≤ t ab“, wobeiΓ eine Umgebung mit Subtyping-Annahmen ist. DieseUmgebung wird erst in Abschnitt 4.3.2 gebraucht. Wirf uhren sie aber schon hier ein, damit wir die Regelndann nicht mehr andern mussen.

Jeder Typ t ist Untertyp des allgemeinsten Typs :

Γ t ≤ (4.2)

hat keinerlei interne Struktur, ebenso wie der Typ⊥, der Fehlerzustande bezeichnet. ⊥ ist ein Untertyp jedes Typs:

Γ ⊥ ≤ t (4.3)

Die Untertyprelation ist auch transitiv:

Γ s ≤ t Γ t ≤ t

Γ s ≤ t(4.4)

Diese Regel ist zu lesen als”

Γ leitet s ≤ t ab wennes einen Typ t gibt, so daß Γ auch s ≤ t und t ≤t ableitet“, wobei s und t beliebige Typen sind. Der

horizontale Strich steht f ur die logische Implikation.Mehrere Ausdrucke uber dem Strich sind konjunktiv(mittels Und) verknupft.

Wir f uhren nun eine Regel f ur Funktionstypen ein:

Γ s ≤ s Γ t ≤ t

Γ s→t ≤ s→t(4.5)

Ein Funktionstyp ist also ein Untertyp eines ande-ren Funktionstyps wenn die Einschrankungen auf die

1Genaugenommen muss es sich nicht nur um einen Verband,sondern um Ideale handeln, um wichtige Eigenschaften funk-tionaler Sprachen beibehalten zu konnen. Aber auf diese De-

tails wollen wir hier nicht eingehen, da wir eher am Prinzip und(spater) dessen Realisierung in imperativen Sprachen interessiertsind als an der Theorie.

Eingangsparameter des ersten Funktionstyps wenigerstreng als die auf die Eingangsparameter des zweitensind. Bei den Ergebnistypen ist es genau umgekehrt:

Ein Untertyp eines Funktionstyps hat auch einen Un-tertyp des Ergebnistyps des ersten Funktionstyps alsErgebnistyp. Dies kann man mit einem kleinen Bei-spiel rechtfertigen: Der ganzzahlige Bereich 4, . . . , 6ist offensichtlich ein Untertyp von 3, . . . , 7, da eineZahl im Bereich von 4 bis 6 uberall verwendet wer-den kann wo eine Zahl im Bereich von 3 bis 7 erwartetwird. Eine Funktion vom Typ 3, . . . , 7 → 4, . . . , 6kann uberall verwendet werden, wo eine Funktion vomTyp 4, . . . , 6 → 3, . . . , 7 erwartet wird, da das Er-gebnis der Funktion auf jeden Fall im erwarteten Be-reich liegt und zumindest der erwartete Argumenttyp-Bereich abgedeckt ist. Diese Art der Untertypbezie-hung f ur Eingangsparameter heisst kontravariant , daFunktionen in Untertypen Obertypen als Eingangspa-rameter erlauben—die Typen variieren in entgegenge-setzte Richtungen. Ergebnistypen sind dagegen kova-riant , da Funktionen in Untertypen auch Untertypenzuruckgeben—Typen variieren in dieselbe Richtung.

Fur Verbundtypen gilt diese Regel:

(∀ i ≤ m) Γ si ≤ tiΓ l1:s1, . . . , ln:sn ≤ l1:t1, . . . , lm:tm

(m ≤ n)

(4.6)Auch dies wird durch ein Beispiel klar:

t = name:string, alter:0, . . . , 120s = name:string, alter:18, . . . , 65, mnr:int

Der Verbundtyp t konnte allgemeine Informationen zueiner Person enthalten, s zu einem Studenten. Offen-sichtlich gilt s ≤ t, da es bei Verwendung einer Instanzvon s (wo eine Instanz von t erwartet wird) folgendeMoglichkeiten gibt: Es kann nur auf die Komponentenname und alter zugegriffen werden. Auf mnr wird nichtzugegriffen, da diese Komponente in t nicht vorkommt.Ein lesender Zugriff auf eine dieser beiden Komponen-ten liefert auf jeden Fall ein Ergebnis vom in t spezi-fizierten Typ. Schreibende Zugriffe auf einen Verbund

gibt es in einer rein funktionalen Sprache nicht; diesenFall brauchen wir also nicht betrachten. Aber Achtung:Wenn die Regel 4.6 f ur imperative bzw. objektorientier-te Sprachen adaptiert wird, werden auch Schreibzugrif-fe moglich. Dann mussen schreib- und lesbare Kompo-nenten des Unter- und Obertyps jeweils gleiche Typenhaben; s ≤ t wurde also nicht gelten, wenn alter eineschreibbare Komponente ware.

Die Regel f ur Tupel entspricht der f ur Verbunde, daTupel als Spezialfall von Verbunden gesehen werden.

Fur Variantentypen gilt diese Regel:

(∀ i ≤ m) Γ si ≤ tiΓ [l1:s1, . . . , lm:sm] ≤ [l1:t1, . . . , ln:tn] (m ≤ n)

(4.7)

Page 38: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 38/64

38 KAPITEL 4. MODELLE POLYMORPHER TYPSYSTEME

Einen Spezialfall dieser Regel haben wir schon eini-ge male verwendet. Zum Beispiel ist [Di:Tag, Mi:Tag]ein Untertyp von [Mo:Tag, Di:Tag, Mi:Tag, Do:Tag]. An-

genommen, es wird eine Variante eines Untertyps ineiner Case-Anweisung verwendet wo eine Instanz (Va-riante) eines Obertyps erwartet wird. Dann enthalt dieCase-Anweisung Alternativen f ur alle Label im Ober-typ. Die Menge der Label im Untertyp ist eine Teil-menge davon, so dass die Case-Anweisung auf jedenFall eine Alternative f ur das in der Variante vorhan-dene Label enthalt. Der Typ des Wertes mit diesemLabel ist ein Untertyp des erwarteten Typs. Da nurlesend auf diesen Wert zugegriffen wird, erf ullt dieserWert auf jeden Fall die Erwartungen.

Obige Regeln definieren implizit auch eine struktu-relle Typaquivalenz: Zwei Typen s und t sind genaudann aquivalent wenn sowohl s ≤ t als auch t ≤ s gilt.

4.3.2 Rekursive Typen

Typen, vor allem Verbundtypen, sind in der Praxis sehrhaufig rekursiv. Zum Beispiel ist der Typ eines Knotensin einem Baum rekursiv, da der Name des Typs imVerbundtyp selbst vorkommt:

baum = i:int, l:baum, r:baum

Rekursive Typen werfen einige Probleme auf. Ein sehr

offensichtliches Problem ist das der Typdarstellung:Wir wollen einen Typ unabhangig von dessen Namenbeschreiben, um strukturelle Typaquivalenz nutzen zukonnen. Das ist in der oben gezeigten Form aber nichtohne weiteres moglich, da der Name im Typ verwendetwird. Es konnen nicht alle Vorkommen dieses Namensdurch den entsprechenden Typ ersetzt werden; der Typware sonst unendlich groß.

Ein einfacher”

Trick“ hilft bei der Beseitigung diesesProblems: Statt des Typnamens wird ein Typparame-ter zur Kennzeichnung aller rekursiver Vorkommen desTyps verwendet. Obiger Typ wird zum Beispiel so dar-gestellt:

µv.i:int, l:v, r:v

Der Fixpunktoperator µ bindet den Typparameter van den Typausdruck.

Auf den ersten Blick mag es scheinen, als ob sich da-durch nicht sehr viel geandert hatte: Der Name, mitdessen Hilfe Rekursion ausgedruckt wird, ist nur auf syntaktisch andere Weise eingef uhrt worden. Bedeu-tung bekommt diese Anderung erst durch das Hin-zuf ugen zweier Regeln, uber die Aquivalenzen rekur-siver Typen definiert werden:

1. α-Konversionsregel:

µv.t = µu.[u/v]t (u /∈ free(µv.t))

2. Faltungsregel:

µv.t = [µv.t/v]t

Ersetzungen und die Funktion free sind so wie f ur denLambda-Kalkul in Abschnitt 2.1 definiert, jedoch wirdstatt λ stets µ verwendet, und Namen stehen stattf ur formale Parameter stets f ur Typparameter. Ahn-lich wie im Lambda-Kalkul konnen durch Anwendungder ersten Regel Typparameter beinahe beliebig um-benannt werden. Der neue Typparameter darf im Typ-ausdruck t nur noch nicht frei (also nicht durch µ ge-bunden) vorkommen. Die zweite Regel hat mehrere Be-deutungen:

• Wenn der Typparameter v in t nicht vorkommt,

ist µv.t aquivalent zu t.

• Sonst (v kommt in t vor) konnen alle Vorkommenvon v in t beliebig durch den Typ selbst (µv.t)ersetzt werden; das heisst, der Typ wird aufge-faltet. Dieser Vorgang ist beliebig oft wiederhol-bar, so dass der Typ im Extremfall (kleinster Fix-punkt) unendlich groß wird. In umgekehrter Rich-tung wird die Regel zum Falten rekursiver Typeneingesetzt.

Die folgenden Typausdrucke sind aquivalent, wie sichdurch wiederholte Anwendungen obiger Regeln leicht

zeigen lasst:

• µv.i:int, l:v, r:v

• i:int, l:µu.i:int, l:u, r:u,r:µv.i:int, l:v, r:µw.v

• i:int, l:µu.i:int, l:u, r:u,r:i:int, l:µv.i:int, l:v, r:v,

r:µv.i:int, l:v, r:v

Ein weiteres Problem ist die Definition von Subty-ping f ur rekursive Typen. Es ist oft nicht gleich ein-sichtig, wie das Ersetzbarkeitsprinzip und die oben de-

finierten Regeln auf rekursive Typen anwendbar sind,da hierbei moglicherweise unendliche Strukturen mit-einander verglichen werden mussen. Trotzdem gibt eseine uberraschend einfache und entscheidbare Regel zurDefinition von Subtyping f ur rekursive Typen, die da-von ausgeht, dass auch nicht identische Typparameterzueinander in einer Untertypbeziehung stehen konnen.Dazu benotigen wir die bereits oben eingef uhrte Umge-bung Γ. Diese enthalt Subtyping-Annahmen f ur Typ-parameter: Wenn u ≤ v in Γ enthalten ist kann mandavon ausgehen, dass jeder Typ, f ur den u steht, einUntertyp jeden Typs ist, f ur den v steht. Dies wird

durch folgende Regel ausgedruckt:

Γ ∪ u ≤ v u ≤ v (4.8)

Page 39: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 39/64

4.4. DAS PER-MODELL 39

Die Subtyping-Regel f ur rekursive Typen ist:

Γ ∪ u ≤ v s ≤ t

Γ µu.s ≤ µv.t

(u /∈ free(t); v /∈ free(s);u,v /∈ free(Γ))

(4.9)

Die Untertypbeziehung zwischen zwei rekursiven Ty-pen ist also genau dann gegeben, wenn die rekursions-freien Varianten dieser Typen (mit freien Typparame-tern statt der gebundenen Typparameter) zueinanderin einer Untertypbeziehung stehen, wobei Subtyping-Annahmen f ur die Typparameter gelten. Die Neben-bedingungen der Regel besagen, dass f ur die Typpa-rameter Namen zu wahlen sind, die weder im anderenTyp frei vorkommen noch in Γ bereits enthalten sind.Mit Hilfe dieser Regel ist die Untertypbeziehung zwi-schen rekursiven Typen entscheidbar, ohne auf unend-

liche Strukturen zuruckgreifen zu mussen.Beispielsweise gilt die Untertypbeziehung

µu.i:int, f:int→u, next:u ≤ µv.i:int, f:int→v.

Aber die beiden Typen µu.i:int, f:u→u, next:u undµv.i:int, f:v→v konnen durch obige Regeln nichtin eine Untertypbeziehung gesetzt werden. Der Grunddaf ur ist, dass die Untertyprelation zwischen Funkti-onstypen kontravariante Eingangs- und kovariante Er-gebnisparametertypen verlangt. Tatsachlich wurde eineUntertypbeziehung zwischen diesen beiden Typen auchdem Ersetzbarkeitsprinzip widersprechen. Wie wir in

Kapitel 5 sehen werden, sind kontravariante Eingangs-parametertypen ein Grund daf ur, dass Subtyping nichtso machtig sein kann wie man es sich in der Praxiswunschen wurde.

An dieser Stelle des Skriptums ware die Diskussi-on einer Sprache angebracht, die Typinferenz (mit pa-rametrischen Typen) wie in ML mit Subtyping ver-bindet. Leider haben sich bisher alle Ansatze f ur eineVerbindung von Typinferenz mit Subtyping als nichtzielf uhrend erwiesen. Aus theoretischen Grunden durf-te es kaum moglich sein, eine sinnvolle Verbindung die-ser Sprachkonzepte zu erzielen. Es gibt zwar funktiona-le Sprachen mit Typinferenz, die auch objektorientier-

te Erweiterungen mit Subtyping anbieten, aber in die-sen Sprachen ist Typinferenz dort nicht anwendbar, woSubtyping verwendet wird. Die Kombination von pa-rametrischem Polymorphismus mit Subtyping ist abermoglich, wie wir in Kapitel 5 sehen werden.

4.4 Das PER-Modell

Im Großteil dieses Skriptums steht die statische Uber-prufbarkeit von Typkonsistenz im Mittelpunkt der Be-trachtungen. In diesem Abschnitt wollen wir aber kurzauf die semantische Bedeutung von Typen eingehen.Bisher sind wir davon ausgegangen, dass ein Typ ein-fach nur eine Menge von Instanzen beschreibt. Das

PER-Modell beschreibt die Semantik von polymorphenTypen etwas genauer. PER steht f ur

”partial equiva-

lence relation“ . Eine Relation R auf einer Menge W

ist eine Menge von Paaren (v, w) mit v, w ∈ W ; manschreibt v R w f ur (v, w) in R. Eine Relation R ist einepartielle Aquivalenzrelation (PER) auf W genau dannwenn R eine symmetrische (v R w ⇔ w R v) und tran-sitive (u R v ∧ v R w ⇒ u R w), aber nicht notwendi-gerweise reflexive (w R w) Relation auf W ist. Offen-sichtlich gilt f ur eine PER R: v R w impliziert v R vund w R w. Jeder Typ A entspricht einer PER, unddie Menge der durch A beschriebenen Werte ist dieMenge der Aquivalenzklassen von A, das ist die MengewA | w A w, wobei wA = v | v A w.

Um zu verstehen wozu PERs dienen betrachten wirUntertypbeziehungen in funktionalen Sprachen. Wiewir gesehen haben ist bei Verbunden ein Typ ein Un-tertyp eines anderen Typs wenn

1. die Instanzen des Untertyps eine Teilmenge derdes Obertyps sind und/oder

2. der Untertyp alle Komponenten seines Obertypsund moglicherweise zusatzliche Komponenten hat.

Der erste Fall lasst sich auf einfache Weise durch Teil-mengen von Wertemengen beschreiben, wie dies inden vorhergehenden Abschnitten gemacht wurde. Beimzweiten Fall stoßen wir dabei aber auf Probleme. Mit

Hilfe des PER-Modells lassen sich beide Falle, wennauch weniger anschaulich, problemlos beschreiben. An-genommen, A und B beschreiben Typen als PER, sodass B ≤ A gilt, B also ein Untertyp von A ist.Wenn, wie im ersten Fall, B eine Teilmenge der In-stanzen von A beschreiben soll, kann man f ur B ein-fach eine Teilmenge der Paare in A nehmen, so dassB nur eine Teilmenge der Aquivalenzklassen von Abeschreibt. Wenn z. B. B = (a, a), (a, b), (b, a), (b, b)und A = B ∪ (c, c), (c, d), (d, c), (d, d), dann ist dieMenge der Aquivalenzklassen von B gleich a, b unddie der von A gleich a, b, c, d.

Der zweite Fall ist etwas komplizierter: Wenn B ≤ Agilt weil A weniger Komponenten als B hat, dann un-terscheidet B genauer zwischen verschiedenen Wertenals A, da B mehr Information als A hat. Daher trifftB genauere Einteilungen in Aquivalenzklassen. Wennv und w f ur B in derselben Aquivalenzklasse liegen,dann mussen v und w naturlich auch f ur A in derselbenAquivalenzklasse liegen. Wenn v und w f ur A in un-terschiedlichen Aquivalenzklassen liegen, dann liegensie auch f ur B in unterschiedlichen Aquivalenzklassen.Trotzdem ist es moglich, dass v und w f ur B in verschie-denen Aquivalenzklassen liegen, obwohl sie f ur A, auf-grund der eingeschrankteren Information, zur selbenAquivalenzklasse gehoren. Auch in diesem Fall ist B ei-ne Teilmenge von A. Wenn z. B. B = A \ (c, d), (d, c)

Page 40: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 40/64

40 KAPITEL 4. MODELLE POLYMORPHER TYPSYSTEME

(A wie oben), dann ist die Menge der Aquivalenzklas-sen von B gleich a, b, c, d. Zusammengefasstgilt daher B ≤ A genau dann wenn B ⊆ A gilt. Al-

lerdings gilt im Allgemeinen nicht, dass die Menge derInstanzen von B eine Teilmenge der von A ist, da In-stanzen Aquivalenzklassen sind, die sich f ur A und Bunterscheiden konnen.

Um die Unterschiede zwischen den Modellen von Ty-pen als Mengen bzw. Relationen klarer zu machen ge-hen wir davon aus, dass W die Menge aller Werte imMengen-Modell ist. Ein Wert im PER-Modell ist ei-ne Teilmenge von W . Im Mengen-Modell konnte maneinen Verbundtyp so interpretieren, dass seine Instan-zen beliebig viele Komponenten haben konnen, von de-nen aber nur die im Verbundtyp spezifizierten sichtbarsein mussen. Ein Untertyp dieses Verbundtyps enthaltdaher auch eine Teilmenge dieser Instanzen. In Ab-schnitt 5.3 werden wir eine objektorientierte Erweite-rung einer Logik-Sprache sehen, die auf genau dieserTypinterpretation aufbaut. In klassischen imperativenSprachen ist diese Typinterpretation jedoch nur teil-weise richtig, da jede Instanz eines Typs ja nur einengewissen, vom Typ der Instanz abhangigen Speicher-platz verbrauchen darf. In objektorientierten Sprachenwie Smalltalk, Eiffel und Java, in denen bei der Para-meterubergabe nicht die Objekte selbst kopiert werdensondern nur Zeiger darauf, ist diese Typinterpretationaber durchaus verwendbar.

Im PER-Modell wird dieses Problem dadurch gelost,dass man den Begriff Wert als die Menge aller Elemen-te von W auffasst, die die gleichen sichtbaren Kompo-nenten haben. Die unsichtbaren Komponenten spielenkeine Rolle. Daher ist das PER-Modell f ur klassischeimperative Sprachen, in denen Objekte bei der Para-meterubergabe (teilweise) kopiert werden, besser geeig-net als das Mengen-Modell. Vor allem lassen sich mitdieser Typinterpretation explizite und implizite Typ-umwandlungen besser beschreiben. Aber mit Typum-wandlungen kann man nie die Machtigkeit erzielen, dieman mit Subtyping und dem Ubergeben von Zeigern

bei der Parameterubergabe hat.

4.5 Literaturhinweise

Die meisten polymorphen Typsysteme f ur funktiona-

le Sprachen haben ihren Ursprung im Damas-Milner-Typsystem. Eine Einf uhrung in dieses Typsystem undseine Anwendung in modernen funktionalen Sprachenwird in [14] gegeben. Eine detailiertere theoretischeDarstellung und neuere Ergebnisse werden z. B. in [13]und [23] geboten. Es gibt zahlreiche Bucher uber ML,Miranda, Haskell und ahnliche Sprachen. Die meistendieser Bucher beschreiben Typen in funktionalen Spra-chen aus einer eher praktischen Sichtweise.

Leider gibt es keine einfach lesbare und trotzdemumfassende Einf uhrung in Subtyping f ur funktiona-le Sprachen. Eine umfangreiche theoretische Abhand-lung uber polymorphe Typen in funktionalen Sprachen

ist [23]. Mehrere Verfeinerungen und Erweiterungensolcher Typen, einschließlich des PER-Modells, wer-den unter anderem in [12] formal behandelt. Zahlrei-che Aspekte von Subtyping und Generizitat vor allemin Zusammenhang mit objektorientierter Programmie-rung werden in der (leider nur teilweise gut lesbaren)theoretischen Abhandlung [1] angesprochen.

Page 41: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 41/64

Kapitel 5

Typen in objektorientierten Sprachen

Objektorientierte Sprachen mit polymorphen Typsy-stemen gehoren zu den wesentlichsten Errungenschaf-

ten der Informatik in den letzten Jahren. Ein großerTeil dieses Fortschritts ware ohne entsprechende Ent-wicklungen auf dem Gebiet der Typsysteme unvorstell-bar. In diesem Kapitel werden einige dieser Entwicklun-gen vorgestellt, wobei der Schwerpunkt auf Subtyping,Vererbung und ahnlichen Konzepten liegt.

5.1 Vererbung in Beispielen

In Kapitel 3 wurde das Typsystem von Ada 83 als Bei-spiel f ur ein Typsystem in einer imperativen Sprachevorgestellt. Mittlerweile hat sich Ada weiterentwickelt,

und ein neuer Standard, Ada 95, wurde verabschiedet.Ada 95 enthalt objektorientierte Konstrukte, die sichauch in einem Typsystem, das Vererbung unterstutzt,niederschlagen. Ada 95 wird in Abschnitt 5.1.1 vor-gestellt, da sich an dieser Sprache die Entwicklungvon Typsystemen in den letzten Jahren nachvollzie-hen lasst. Ahnliche Entwicklungen fanden auch beianderen Sprachen statt, zum Beispiel beim Ubergangvon C nach C++ bzw. Objective C, von Modula-2nach Modula-3 bzw. Oberon-2 und so weiter. In Ab-schnitt 5.1.2 wird mit Eiffel eine Sprache vorgestellt,in der Module und Typen im Wesentlichen zu einemSprachkonstrukt, namlich Klassen, zusammengefasstsind. Ahnliche Konstrukte gibt es zum Beispiel auchin C++ und Objective C. Eiffel wurde gewahlt, da dieSprache die Festlegung von Zusicherungen (engl.

”as-

sertions“ ) erlaubt, mit deren Hilfe sich grundlegendeProbleme der Typspezifikation erklaren lassen.

5.1.1 Ada 95

Vererbung in Ada 95 lasst sich auf folgende Kurzformelbringen:

Vererbung = Typableitung

+¨Uberschreiben+ Typerweiterung

+ dynamische Typerkennung

Abgeleitete Typen wurden bereits in Abschnitt 3.1.3vorgestellt. Dieser Mechanismus ist die Grundlage f ur

Vererbung in Ada 95. Zur Wiederholung: Aus jedembereits existierenden Typ kann ein neuer Typ abgelei-tet werden, der dieselbe Struktur und dieselben darauf anwendbaren Routinen wie sein Elterntyp hat, jedocheinen eigenstandigen Typ mit einem eigenen Namendarstellt. Eine Instanz eines abgeleiteten Typs kanndurch explizite Typumwandlung in eine Instanz des El-terntyps umgewandelt werden und umgekehrt.

type Int is range 0..10000;

function "+"(Links, Rechts: Int)

return Int;

type Laenge is new Int;

-- function "+"(Links, Rechts: Laenge)-- return Laenge;

In diesem Programmstuck ist + implizit auch f urLaenge deklariert. Die entsprechende Deklaration istdaher auskommentiert (durch --). Auch die Implemen-tierung der Funktion wird ererbt. Ada 95 erlaubt auchdas ¨ Uberschreiben von Funktionen:

type Winkel is new Int;

function "+"(Links, Rechts: Winkel)

return Winkel;

Hier bewirkt die explizite Angabe der Typdeklaration,dass die ererbte Implementierung von + durch eine neueImplementierung ersetzt wird. Auf diese Weise konnenauch neue Routinen, die im Elterntyp nicht deklariertsind, dazugef ugt werden. Es ist aber nicht moglich, er-erbte Routinen ganzlich zu entfernen.

Zur Wiederholung: Kontravarianz bedeutet, dassdie Parametertypen von Routinen durch Vererbung(bzw. Subtyping) gleich bleiben oder allgemeiner wer-den. Kovarianz bedeutet, dass Parametertypen durchVererbung gleich bleiben oder spezieller werden. UndInvarianz bedeutet, dass Parametertypen durch Verer-bung nicht verandert werden.

Die Schnittstellenbeschreibungen von Routinen auf einem abgeleiteten Typ unterscheiden sich von den

Page 42: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 42/64

42 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

Schnittstellenbeschreibungen der entsprechenen Routi-nen auf dem ursprunglichen Typ genau dadurch, dassalle Vorkommen des ursprunglichen Typbezeichners

durch den Bezeichner des abgeleiteten Typs ersetztwerden. Alle anderen Typbezeichner bleiben gleich.Abgeleitete Typen sind Untertypen des ursprunglichenTyps. Sowohl Laenge als auch Winkel sind Unterty-pen von Int. In Ada sind daher sowohl Eingangs- alsauch Ausgangsparametertypen kovariant (oder invari-ant). Routinen auf abgeleiteten Typen widersprechenaus diesem Grund dem Ersetzbarkeitsprinzip. Kovari-ante Typen von Eingangsparametern decken nur einenTeil des Eingangswertebereiches ab. In Ada ist es da-her auch nicht uneingeschrankt moglich, eine Instanzeines Untertyps uberall zu verwenden, wo eine Instanzeines Obertyps erwartet wird. Statt dessen muss eineInstanz des erwarteten Typs (also keines Untertyps da-von) ubergeben werden.

Unter Typerweiterung versteht man in Ada das Hin-zuf ugen neuer Komponenten zu abgeleiteten Verbund-typen. Solche erweiterbaren Verbunde mussen mit demWort tagged gekennzeichnet werden:

type Person is tagged record

Vorname: String (1..20);

end record;

type Mann is new Person with record

Barttraeger: Boolean;

end record;

type Frau is new Person with null record;

Verbundtypen, die nicht durch tagged gekennzeichnetsind, konnen nicht erweitert werden. Abgeleitete er-weiterbare Verbundtypen mussen entweder eine Klau-sel with record . . . end record oder, falls man oh-ne Zusatze auskommt, with null record enthalten.Typumwandlung ist f ur Instanzen erweiterbarer Ver-bundtypen nur in Richtung Vorgangertyp moglich, daInstanzen nicht genugend Information f ur Umwandlun-gen in die andere Richtung enthalten. Routinen auf er-

weiterbaren Verbundtypen konnen auf die genau glei-che Weise uberladen werden wie die auf anderen Typen:

function Anrede (P: in Person)

return String; -- ""

function Anrede (M: in Mann)

return String; -- "Herr "

function Anrede (P: in Frau)

return String; -- "Frau "

Erweiterbare Verbundtypen sind eine Voraussetzungf ur dynamische Typerkennung. Zu jedem erweiterbarenVerbundtyp t ist implizit ein sogenannter klassenwei-ter Typ t’Class definiert. Die durch t definierte Klas-se (in Ada-Terminologie) umfasst t und alle von t ab-geleiteten Typen. Zu Person’Class gehoren demnach

Person, Mann und Frau, und zu Frau’Class gehort nurFrau. Jede Instanz eines klassenweiten Typs enthalteine unsichtbare Komponente, ein

”tag“ , das den spe-

zifischen (= nicht klassenweiten) Typ der Instanz an-gibt. Da diese Komponente wahrend der Programm-ausf uhrung gebraucht wird, muss jede Variable einesklassenweiten Typs mit einer Instanz eines entspre-chenden spezifischen Typs initialisiert werden:

P: Person’Class := Frau’(Vorname => "Anna");

Der spezifische Typ von P kann zur Laufzeit nichtgeandert werden, da sich dadurch der Platzbedarf f ur P

andern wurde. Wenn man den spezifischen Typ andernwill, muss man auf Zeiger zuruckgreifen:

type PersonRef is access Person’Class;

P Ref: PersonRef;P Ref := new Frau’(Vorname => "Anna");

P Ref := new Mann’(Vorname => "Stefan",

Barttraeger => False);

Wenn die spezifischen Typen von Argumenten demCompiler bekannt sind, kann die auszuf uhrende uberla-dene Operation statisch festgelegt werden—statischesBinden. Bei Verwendung klassenweiter Typen kanndie auszuf uhrende Operation in vielen Fallen jedocherst zur Laufzeit anhand der

”tag“-Komponente be-

stimmt werden—dynamisches Binden. Diese”

tag“-Komponente ist auch direkt uber den in-Operator ab-

fragbar:

procedure Display (P: in Person’Class) is

begin

Put (Anrede (P));

Put (P.Vorname);

if P in Mann’Class then

Put (" (maennlich)")

elsif P in Frau’Class then

Put (" (weiblich)")

end if;

end Display;

Fur jeden spezifischen Typ in Person’Class gibt eseine Funktion Anrede. Es muss dynamisch entschiedenwerden, welche dieser Funktionen auszuf uhren ist. Je-de Instanz eines derartigen Typs hat die KomponenteVorname.

Ein klassenweiter Typ gilt als vom entsprechendenspezifischen Typ verschieden. Daher bleiben bei einerTypableitung klassenweite Parametertypen stets un-verandert. Alle klassenweiten Parametertypen sind da-her invariant und entsprechen dem Ersetzbarkeitsprin-zip.

Beim Aufruf einer Routine durfen Eingangsargu-mente einen klassenweite Typ haben, obwohl die ent-sprechenden formalen Parameter einen spezifischen er-weiterbaren Typ haben. Uber die dynamischen Typen

Page 43: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 43/64

5.1. VERERBUNG IN BEISPIELEN 43

der Argumente erfolgt in diesem Fall dynamisches Bin-den. In Ada geht (im Gegensatz zu C++, Java undEiffel) aus der Syntax nicht hervor, uber welches Argu-

ment einer Routine statisches bzw. dynamisches Bin-den erfolgt. Es gibt daher eine Einschrankung die si-cherstellt, dass die Semantik trotzdem eindeutig defi-niert ist: Alle spezifischen erweiterbaren Parameterty-pen einer Routine mussen gleich sein. Beim dynami-schen Binden mussen alle diese Argumente denselbendynamischen Typ haben. Dies wird zur Laufzeit kon-trolliert bevor dynamisch gebunden wird. In der Funk-tion Anrede ist diese Bedingung z. B. einfach dadurcherf ullt, dass es nur einen einzigen Parameter gibt.

Ada 95 unterstutzt im Zusammenhang mit Verer-bung auch abstrakte Typen —nicht zu verwechseln mitabstrakten Datentypen. Abstrakte Typen definierenklassenweite Typen, ohne selbst als spezifische Typenauftreten zu konnen. Diese Typen werden durch dasSchlusselwort abstract gekennzeichnet:

type A is abstract tagged null record;

procedure Q (X: in A) is abstract;

type B is new A with record

I: Integer;

end record;

procedure Q (X: in B) is ...

A ist ein abstrakter und B ein davon abgeleiteter kon-kreter Typ. Q ist auf A als abstrakte Prozedur dekla-riert. Jeder von A abgeleitete konkrete Typ muss Q

uberschreiben. Abstrakte Typen verwendet man zu-sammen mit klassenweiten Typen, wenn z. B. Prozedu-ren f ur Mann und Frau, nicht aber f ur Person benotigtwerden.

Schließlich soll ein weiterer Mechanismus von Ada 95vorgestellt werden, der eher mit Generizitat als mitVererbung zu tun hat. Das nachste Beispiel zeigt ei-ne generische Funktion:

generic

type T is private;

with function "<"(X,Y:T) return Boolean;function Max(X,Y:T) return Boolean is

begin

if X < Y

then return Y;

else return X;

end if;

end Max;

Diese generische Funktion nimmt eine Funktion < alsgenerischen Parameter. < muss auf dem anderen ge-nerischen Parameter definiert sein. Damit die generi-sche Funktion verwendbar wird, muss man eine nicht-

generische Funktion daraus ableiten:

function MaxI is new Max(Integer,"<");

MaxI ist also eine Funktion auf ganzen Zahlen, die dasMaximum der beiden Argumente zuruckliefert. < ist dieVergleichsfunktion auf Integer. Es lasst sich aus Max

aber auch leicht MinF ableiten:function MinF is new Max(Float,">");

In diesem Fall wird f ur den generischen Parameter < dieFunktion > auf Fließkommazahlen eingesetzt, so dassMinF die kleinere der beiden Zahlen zuruckliefert.

In Ada sind die Mechanismen f ur Vererbung und Da-tenkapselung getrennt. Fur Vererbung werden abgelei-tete Typen und f ur Datenkapselung Pakete verwendet.Eine ahnliche Trennung zwischen Vererbung und Kap-selung gibt es zum Beispiel in Oberon-2. In Sprachenwie Java, C++ und Eiffel sind diese Mechanismen je-

doch zu einem Konzept kombiniert.

5.1.2 Eiffel

Die Deklarationseinheiten in Eiffel sind Klassen. EineKlasse enspricht einem (parametrischen) Modul, spe-zifiziert gleichzeitig aber auch einen Typ oder eine Fa-milie von Typen. Hier ist ein Beispiel einer Klasse inEiffel:

class KONTO [T -> GELD BETRAG]

feature ANY

guthaben: T;

ueberziehungsrahmen: T;

besitzer: PERSON;

einzahlen (summe: T) is

do addieren (summe)

end; -- einzahlen

abheben (summe: T) is

do addieren (-summe)

end; -- abheben

feature NONE

addieren (summe: T) is

do guthaben := guthaben + summe

end; -- addieren

end -- class KONTO

Das Programmstuck beschreibt eine generische Klas-se namens KONTO. Typparameter werden in eckigenKlammern geschrieben. KONTO hat einen TypparameterT, der universell uber GELD BETRAG und dessen Unter-typen quantifiziert ist. Man nennt diese Art von Po-lymorphismus gebundene Generizit at (oder gebunde-nen parametrischen Polymorphismus) da der Typpa-rameter nicht frei gewahlt werden kann, sondern einUntertyp eines gegebenen Typs sein muss. Es han-delt sich um Generizitat, da Typen f ur Typparame-ter einzusetzen sind. Geeignete Werte f ur T sind etwaSCHILLING BETRAG und DOLLAR BETRAG. Es wird ange-nommen, dass GELD BETRAG die binaren Operationen

Page 44: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 44/64

44 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

+ und - als Infixoperatoren sowie die unare Operati-on - als Prafixoperator definieren. Auch alle Unter-typen von GELD BETRAG stellen diese Operationen zur

Verf ugung. Gebundener parametrischer Polymorphis-mus wird also dann verwendet, wenn von einem Typ-parameter erwartet wird, dass Instanzen des daf ur ein-gesetzten Typs bestimmte Operationen und Verbund-Komponenten bereitstellen. In Ada wird daf ur, wie wirgesehen haben, ein anderer Mechanismus eingesetzt.

Der Rest der Klassendefinition sollte, abgesehenvon der feature-Klausel, selbsterklarend sein. DemSchlusselwort feature kann eine Liste von Klassenna-men in geschwungenen Klammern folgen. Alle nach-folgend definierten Variablen und Routinen sind nurinnerhalb der Klassen in dieser Liste und deren Unter-klassen sichtbar. ANY ist die allgemeinste Klasse, vonder alle anderen Klassen erben. Daher sind im Bei-spiel die Variablen guthaben, ueberziehungsrahmen

und besitzer sowie die Funktionen einzahlen undabheben uberall sichtbar. NONE ist dagegen eine vor-definierte Klasse ohne Instanzen, von der keine Klasseerbt. Daher gibt es im Beispiel keine Objekte außerden Instanzen von KONTO, die addieren sehen. Genau-genommen sind in anderen Klassen, die keine Unter-klassen sind, hochstens die Schnittstellen von Routi-nen, aber nicht deren Implementierungen sichtbar. Ob-wohl Schnittstelle und Implementierung textuell nichtgetrennt sind, werden sie konzeptuell sehr wohl unter-

schieden.An der Beschreibung des Programmstucks im vori-

gen Absatz f allt auf, dass es schwierig ist, die Begrif-fe

”Klasse“ und

”Typ“ klar voneinander zu trennen.

Zum Beispiel wurde”

Unterklasse“ in aquivalenter Be-deutung zu

”Untertyp“ verwendet. Tatsachlich gibt es

in Eiffel-Terminologie eine eins-zu-eins-Beziehung zwi-schen parameterfreien Klassen und Typen. Eine generi-sche Klasse beschreibt entsprechende nicht-generischeTypen. Zum Beispiel sind KONTO[SCHILLING BETRAG]

und KONTO[DOLLAR BETRAG] zwei Typen, die durch dieKlasse KONTO beschrieben werden.

Alle Objekte in Eiffel sind Instanzen eines Typs. Je-de Variable enthalt einen Zeiger auf ein Objekt undhat einen statischen Typ. Das Objekt, auf das gezeigtwird, muss auf jeden Fall einen (dynamischen) Typ ha-ben, der ein Untertyp des statischen Typs der Variableist. Beim Aufruf einer Routine, die zu einem Objektgehort, wird das Objekt explizit durch Punktnotationangegeben:

konto: KONTO[SCHILLING BETRAG];

betrag: SCHILLING BETRAG;

...

konto.einzahlen (betrag);

Dynamisches Binden erfolgt nur uber dieses Objekt, indiesem Fall dem Inhalt der Variable konto. Dieses Ob-

jekt kann als spezielles, implizites Argument der Rou-tine angesehen werden.

Eine Besonderheit von Eiffel sind Zusicherungen.

Sie werden als Vorbedingungen (engl. ”preconditions,als require-Klauseln in Eiffel) und Nachbedingungen (engl.

”postconditions“, ensure-Klauseln) zu Imple-

mentierungen von Routinen (do-Klauseln) dazugef ugt.Daneben gibt es noch Invarianten. Jede Zusicherungist eine Liste Boolscher Ausdrucke, die durch Strich-punkte getrennt sind. Der Strichpunkt steht implizitf ur eine Konjunktion (Und-Verknupfung). Daher kann jede Zusicherung entweder zu

” ja“ oder

”nein“ ausge-

wertet werden. Wenn eine Zusicherung zu”

nein“ aus-gewertet wird, erfolgt eine Fehlermeldung bzw. Aus-nahmebehandlung. Eine Vorbedingung beschreibt dieMenge der erlaubten Eingangswerte genauer als diesdurch Parametertypen alleine moglich ware. Ebensobeschreiben Nachbedingungen mogliche Mengen vonErgebniswerten. Invarianten mussen stets gelten wennder Zustand eines Objekts von außen sichtbar ist. InNachbedingungen ist die Bezugnahme auf Variablen-und Argumentwerte zum Zeitpunkt des Funktionsauf-rufs erlaubt. Zum Beispiel bezeichnet old guthaben

den Wert der Variable guthaben zum Zeitpunkt desAufrufs von einzahlen oder abheben. Zusicherungenauf diesen beiden Routinen konnen beispielsweise soaussehen:

einzahlen (summe: T) is

require summe >= 0do addieren (summe)

ensure guthaben = old guthaben + summe

end; -- einzahlen

abheben (summe: T) is

require summe >= 0;

guthaben >= summe -

ueberziehungsrahmen

do addieren (-summe)

ensure guthaben = old guthaben - summe

end; -- abheben

Zusicherungen konnen als Teil des Typsystems ange-sehen werden. Konzeptuell gehort eine Zusicherung zurSchnittstelle und nicht zur Implementierung. Daher istsie dem Aufrufer einer Routine bekannt. Zusicherun-gen mussen in der Regel zur Laufzeit uberpruft werden.Vorbedingungen werden vor dem Aufruf einer Routineuberpruft, Nachbedingungen am Ende der Ausf uhrungeiner Routine und Invarianten sowohl vor als auch nachder Ausf uhrung. In einigen Fallen konnen Zusicherun-gen zur Optimierung eingesetzt werden.

Eiffel unterstutzt Vererbung von Klassen und damitauch von Typen:

class SCHILLING BETRAGinherit GELD BETRAG

feature ...

Page 45: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 45/64

5.2. ALLGEMEINE KONZEPTE 45

Dies entspricht der Ableitung des Typs (bzw. der Klas-se) SCHILLING BETRAG aus GELD BETRAG. Ebenso wiein Ada gibt es die Moglichkeit, Routinen zu uber-

schreiben, Klassen—entsprechend den erweiterbarenVerbunden—zu erweitern und abstrakte Typen zu de-finieren. Außerdem entsprechen in Eiffel alle Variablenohne zusatzliche Deklarationen Zeigern auf klassenwei-te Typen in Ada. (Beachten sie, dass sich die Bedeutun-gen des Begriffs

”Klasse“ in Eiffel und Ada wesentlich

voneinander unterscheiden.) Im Gegensatz zu Ada bie-tet Eiffel Mehrfachvererbung auf der Sprachebene, dasheisst, eine Klasse kann von mehr als nur einer ande-ren Klasse erben. Um Namenskonflikte zu vermeiden,bietet Eiffel umfangreiche Moglichkeiten, ererbte Va-riablen und Routinen umzubenennen.

Die Frage, ob Vererbung in Eiffel ko-, kontra- oder in-variant ist, lasst sich nicht so einfach beantworten. Be-trachten wir die Vererbung etwas genauer. Die forma-len Parameter der Routinen in einer Unterklasse haben,falls die Routinen nicht uberschrieben sind, genau die-selben Typen wie die entsprechenden Parameter im da-zugehorigen Obertyp. Das heisst, im Gegensatz zu Adawerden Typen nicht automatisch verandert. Sie sind indiesem Fall invariant. Eiffel unterstutzt aber auch Ko-varianz auf allen Parametern: Beim Uberschreiben er-erbter Routinen konnen die Typen formaler Parameterund Ergebnistypen durch beliebige Untertypen der ur-sprunglichen Typen ersetzt werden. Dadurch, dass alle

Parameter kovariant sein konnen, kann der Compilernicht mehr garantieren, dass der gesamte Eingangswer-tebereich abgedeckt ist; das Ersetzbarkeitsprinzip istverletzt. In alteren Implementierungen von Eiffel wirdbeim Auftreten nicht abgedeckter Eingangswerte eineAusnahmebehandlung durchgef uhrt. Es ist aber auchmoglich, die erlaubten Aufrufe so einzuschranken, dassder Compiler (bzw. Linker) Typkonsistenz garantierenkann. Dynamisches Binden ist dann nur in den Fallenerlaubt, in denen alle Parametertypen invariant sind.Aufrufe, bei denen nur statisches Binden erlaubt ist,heissen

”CAT calls“. Diese Bedingung kann statisch

uberpruft werden. Jedoch ist keine separate¨Uberset-zung der Klassen moglich, da die Entscheidung, ob ein

Aufruf ein”

CAT call“ ist oder nicht, erst getroffen wer-den kann wenn die ganze Typhierarchie bekannt ist.Außerdem muss der Programmierer in vielen Fallen voreinem

”CAT call“ einen expliziten (dynamischen) Typ-

vergleich einf ugen um sicherzustellen, dass eine Varia-ble eine Instanz des erwarteten Typs enthalt. (Dyna-misches Binden wird also durch bedingte Anweisungensimuliert.)

Die Nachbedingungen uberschriebener Funktionensind in Eiffel strenger oder gleich streng wie dieder ursprunglichen Funktionen. Dadurch beschreibenuberschriebene Nachbedingungen kleinere Wertemen-gen. Bei den Vorbedingungen ist es genau umgekehrt:

Uberschriebene Vorbedingungen durfen nicht strenger,konnen aber allgemeiner sein und beschreiben dahergroßere Wertemengen. Vererbung in Eiffel ist hinsicht-

lich der Zusicherungen auf den Eingangsparameternkontravariant. Invarianten sind im Wesentlichen invari-ant. Daher entsprechen Zusicherungen dem Ersetzbar-keitsprinzip.

5.2 Allgemeine Konzepte

Nach Beispielen f ur Sprachen mit einem objektorien-tierten Typkonzept sollen nun einige wichtige Konzepteunabhangig von konkreten Sprachen behandelt werden.

5.2.1 Varianz von ParametertypenDie Begriffe Kovarianz, Kontravarianz und Invarianzwurden bereits des ofteren verwendet. Da sie bei Typenin objektorientierten Sprachen eine große Rolle spielen,wollen wir sie hier noch einmal untersuchen. Zur Wie-derholung: Wir schreiben s ≤ t wenn s ein Untertypvon t ist. Fur Typen von Routinen schreiben wir

s1 × · · · × sk → sk+1 × · · · × sn ≤t1 × · · · × tk → tk+1 × · · · × tn.

Die Typen von Eingangsparametern stehen links vom

Pfeil, solche von Ausgangsparametern rechts vom Pfeil.Durchgangsparameter werden dupliziert und deren Ty-pen stehen sowohl links als auch rechts vom Pfeil. Deri-te Parameter (f ur 1 ≤ i ≤ n) in diesem Typ ist ko-variant wenn si ≤ ti, kontravariant wenn ti ≤ si undinvariant wenn si = ti. Da die Ausgangsparameterty-pen praktisch immer kovariant (oder invariant) sind,sagt man oft, eine Routine (bzw. Subtyping oder Ver-erbung) sei kontra- bzw. kovariant wenn die Eingangs-parametertypen kontra- bzw. kovariant sind. Von inva-riantem Subtyping spricht man wenn alle Parameter-typen invariant sind.

Eine Programmiersprache sollte sicherstellen, dass

nur solche Varianzen erlaubt sind, die mit dem Ersetz-barkeitsprinzip kompatibel sind:

Eine Instanz eines Untertyps kann uberallverwendet werden wo eine Instanz eines ent-sprechenden Obertyps erwartet wird.

Insbesondere sollte keine Anderung des Aufrufs einerzu einem Objekt gehorenden Routine notig sein wenndieses Objekt durch ein anderes Objekt eines Untertypsersetzt wird.

Wie in Abschnitt 4.1.2 erklart, muss der Aufruf einerRoutine vom Typ

t1 × · · · × tk → tk+1 × · · · × tn

Page 46: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 46/64

46 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

Variablen der statischen Typen u1, . . . , un als Argu-mente ubergeben, wobei ui ≤ ti f ur 1 ≤ i ≤ k undtj ≤ uj f ur k < j ≤ n. Wenn ti und tj gemeinsam

den Typ eines Durchgangsparameters beschreiben giltzwangslaufig ui = ti = tj = uj . Diese Beziehungenzwischen Variablen- und Parametertypen mussen auchdann gelten wenn die Routine durch eine andere Rou-tine eines beliebigen Untertyps

s1 × · · · × sk → sk+1 × · · · × sn

ersetzt wird. Fur alle Typen u1, . . . , un und s1, . . . , snmuss Folgendes gelten: ui ≤ si f ur 1 ≤ i ≤ k undsj ≤ uj f ur k < j ≤ n. Da wir bereits wissen, dassui ≤ ti f ur 1 ≤ i ≤ k und tj ≤ uj f ur k < j ≤ n,sind diese Beziehungen erf ullt wenn ti ≤ si f ur 1 ≤

i ≤ k und sj ≤ tj f ur k < j ≤ n. Eingangsparameter-typen mussen demnach kontravariant und Ausgangs-parametertypen kovariant sein. Durchgangsparameter-typen sind zwangslaufig invariant.

Eine andere Sichtweise von Subtyping beruht auf Fa-milien von Einzelroutinen, die zusammengesetzt eineGesamtroutine ergeben. Eine Gesamtroutine vom Typ

t = tin → tout = t1 × · · · × tk → tk+1 × · · · × tn

kann als eine Familie von Einzelroutinen der Typen

ti = tiin → tiout = ti1 × · · · × tik → tik+1 × · · · × tin

(f ur 1 ≤ i ≤ m) aufgefasst werden, wobei tij ≤ tj f ur

1 ≤ j ≤ n. Wenn W (s) die Wertemenge (= Menge allerInstanzen) jedes Typs s bezeichnet, muss gelten:

W (tin) =

1≤i≤m

W (tiin).

Dadurch deckt die Vereinigung aller m Einzelroutinenden ganzen Eingangswertebereich der Gesamtroutineab. Aufgrund der dynamischen Typen der Eingangs-argumente kann zur Laufzeit entschieden werden, wel-che der Einzelroutinen ausgef uhrt zu werden hat. Fallses mehrere Moglichkeiten f ur die Auswahl der Einzel-

routine gibt, werden weitere Kriterien zur Entschei-dung herangezogen. In der Regel wird die passendeRoutine mit den spezifischsten Typen gewahlt. Wenndie Auswahl der Einzelroutinen nur uber ein einzigesEingangsargument erfolgt, spricht man von Einfach-Binden (engl.

”single dispatching“). Wenn die Auswahl

uber das erste Argument erfolgt, gilt z. B. tj = tij f ur al-le 1 ≤ i ≤ m und 1 < j ≤ k. Einfach-Binden liegt (wiein Ada) auch vor wenn in jeder Einzelroutine mehre-re gekennzeichnete Eingangsparameter einen gemein-samen Typ haben und die entsprechenden Parame-ter der Gesamtroutine einen entsprechenden gemein-samen Obertyp haben. Es muss dynamisch uberpruftwerden ob tatsachlich alle Argumente denselben dy-namischen Typ haben. Die Auswahl erfolgt dann uber

den gemeinsamen dynamischen Typ dieser Eingangs-argumente. Im allgemeinen Fall, wenn die Auswahl derEinzelroutine uber die dynamischen Typen mehrerer

beliebiger Argumente erfolgen kann, spricht man vonMehrfach-Binden (engl.”

multiple dispatching“).

Den Aufruf einer zu einem Objekt gehorenden Rou-tine kann man auch so auffassen, dass eine um einenParameter erweiterte globale Routine aufgerufen wird,wobei dieses Objekt als zusatzliches Argument, das dieUmgebung (engl.

”environment“) beschreibt, uberge-

ben wird. Der zusatzliche Parameter ist in der Re-gel ein Durchgangsparameter. Man kann die globaleRoutine als Gesamtroutine verstehen. Durch Einfach-Binden uber den zusatzlichen Parameter wird jene Ein-zelroutine bestimmt, die dem Typ der Umgebung ent-spricht. Damit kann ein alternatives Regelsystem f urein dem Ersetzungsprinzip entsprechendes Subtypingaufgestellt werden. Der Typ der Gesamtroutine sei

× t1 × · · · × tk → tk+1 × · · · × tn × .

Der erste Eingangs- und letzte Ausgangsparameterstellen den durchgereichten zusatzlichen Parameterdar. Fur jeden Typ si (1 ≤ i ≤ m), der eine entspre-chende Routine unterstutzt, gibt es eine Einzelroutinevom Typ

si × ti1 × · · · × tik → tik+1 × · · · × tin × si.

In der einfachsten Form gilt tij = tj f ur alle 1 ≤ i ≤ mund 1 ≤ i ≤ k. Diese Form von Subtyping mit invarian-ten Eingangsparametertypen—oft auch mit invarian-ten Ausgangsparametertypen—wird in vielen Sprachenwie z. B. C++, Java und Oberon-2 verwendet. Die ent-sprechenden Typuberprufungen konnen in jedem Fallvom Compiler durchgef uhrt werden. Allerdings ist die-se Form von Subtyping starker eingeschrankt als not-wendig. Die zusatzlichen Moglichkeiten von kontrava-riantem Subtyping werden aber kaum benotigt.

Die in Ada verwendete Form ist eine Erweiterung da-

von: t

i

j = s

i

falls s

i

ein von tj abgeleiteter Typ ist, sonsttij = tj . Auch f ur diese Form ist nur Einfach-Bindennotwendig, aber im Allgemeinen muss man dabei Ty-pen dynamisch prufen: Bei einem Aufruf muss garan-tiert werden, dass alle Argumente an Parameterposi-tionen des Typs si einen dynamischen Typ si haben.Die dynamischen Typen der Argumente bzw. Variablensind in der Regel nicht zur Compilezeit bekannt.

Recht kompliziert sind die Verhaltnisse bei der in Eif-fel verwendeten Form des kovarianten Subtyping, wo-bei das Binden nur uber den Typ eines Objekts erfolgt.Die einzige Regel ist tij ≤ tj f ur 1 ≤ j ≤ n. Die Be-

dingung W (tin) = 1≤i≤m

W (ti

in

) und das Ersetzbar-keitsprinzip sind im Allgemeinen nicht erf ullt. Bei einerTypuberprufung zur Compilezeit muss eine statische

Page 47: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 47/64

5.2. ALLGEMEINE KONZEPTE 47

Analyse des gesamten Programms durchgef uhrt wer-den um sicherzustellen, dass alle moglicherweise auf-gerufenen Routinen tatsachlich vorhanden sind. Der

damit verbundene Aufwand wird haufig geschaut. Au-ßerdem ist es f ur einen Programmierer sehr oft nichtleicht zu verstehen, wodurch ein moglicher Typfehlerverursacht wird; eine kleine Anderung an einer Stelleeines Programms kann einen Typkonflikt an einer ganzanderen, mit der Anderung scheinbar in keinerlei Zu-sammenhang stehenden Stelle verursachen.

Kovariantes Subtyping lasst sich gut mittels”

mul-tiple dispatching“ realisieren. Ein Problem dabei ist jedoch, dass die Anzahl der Einzelroutinen rasch ex-plodieren (ubermaßig groß werden) kann. Diese Code-Explosion kann man nur dadurch umgehen, dass manbei der Vererbung Heuristiken verwendet, die abernicht in jedem Fall den Erwartungen entsprechen. Au-ßerdem wird durch

”multiple dispatching“ die Zuord-

nung von Einzelroutinen zu Klassen bzw. Typen er-schwert; die auszuf uhrende Einzelroutine wird ja nichtmehr uber ein ausgewahltes si, sondern uber eine Men-ge gleichwertiger dynamischer Typen bestimmt.

Fur einen Softwareentwickler bietet kovariantes Sub-typing oft eine gute Losung, da sich damit einige inder Praxis haufige Beziehungen zwischen Typen relativeinfach ausdrucken lassen. Zum Beispiel gibt es einenTyp Tier mit einer Routine futtere, die als Eingangs-argument eine Instanz von Nahrung erwartet. Im Un-

tertyp Lowe von Tier soll futtere eine Instanz vonFleisch erwarten, wobei Fleisch ein Untertyp vonNahrung ist. Diese Beziehung lasst sich nur mittels Ko-varianz ausdrucken. Wie wir gesehen haben, kann Ko-varianz in uneingeschrankter Form von einer Program-miersprache nicht zur Verf ugung gestellt werden, wennstatische Typuberprufung und getrennte Ubersetzbar-keit von Modulen gefordert wird und die Nachteile von

”multiple dispatching“ nicht in Kauf genommen wer-

den sollen. Die Kunst des Programmiersprachendesi-gns liegt auch hierbei zu einem großen Teil darin, guteKompromisse zu finden.

Ein moglicher Ausweg besteht vielleicht darin, Ty-pen in eingeschrankter Weise als Werte zu betrach-ten. (Dabei ist Vorsicht geboten, da dies leicht zu un-entscheidbaren Typsystemem f uhren kann.) Der TypTier kann z. B. eine Konstante frisst enthalten, de-ren Wert der Typ Nahrung ist. Die Routine futtere

erwartet sich dann eine Instanz von frisst als Ar-gument. In Lowe kann frisst den Wert Fleisch ha-ben; futtere bleibt unverandert. Dieser Ansatz lostdas Problem nur scheinbar: futtere in Lowe akzeptiertnoch immer nicht alle Werte, die von futtere in Tier

als Argument erwartet werden. Dennoch kann dieserAnsatz unter Umstanden Vorteile bringen, da die Be-ziehung zwischen Tier und Nahrung uber frisst vonvornherein festgelegt ist. Damit kann eine notwendige

dynamische Typuberprufung bereits fruher, vor demAufruf erfolgen. In einigen Fallen, aber nicht immer,kann uberhaupt darauf verzichtet werden.

5.2.2 Untertypen und Vererbung

In vielen objektorientierten Sprachen, z. B. in Ada, Ja-va, C++ und Eiffel, werden Subtyping und Vererbungals zusammengehorige Mechanismen verstanden. Beigenauerer Betrachtung stellt sich aber heraus, dass sichdadurch Probleme ergeben konnen. In objektorientier-ten Sprachen entspicht ein Typ in etwa der Spezifikati-on der Schnittstelle und des nach außen sichtbaren Ver-haltens eines Objekts. Alle Instanzen eines Typs bietendeshalb nach außen hin dieselbe Schnittstelle an undzeigen dasselbe Verhalten. Ein Untertyp bietet mehrMoglichkeiten in der Schnittstelle und beschreibt dasVerhalten genauer als ein entsprechender Obertyp. DerBegriff

”Vererbung“ bezieht sich dagegen auf Imple-

mentierungen. Zum Beispiel erbt ein abgeleiteter Typdie Implementierungen seiner Routinen, falls sie nichtuberschrieben werden. Man konnte meinen, dass dieImplementierung das Verhalten formal beschreibe undVererbung daher eine direkte Folge der Einf uhrung vonUntertypen darstelle. Wenn dem so ware, musste mandas Uberschreiben von Routinen verbieten, da Uber-schreiben mit einer Verhaltensanderung gleichzusetzenware. Uberschreiben ist aber f ur die praktische Pro-

grammierung wichtig, so dass man es nicht einfach ver-bieten kann. Implementierung und Festlegung des nachaußen sichtbaren Verhaltens sind daher zwei verschie-dene Dinge. Wahrend sich die Beschreibung des Ver-haltens bei Typen nur auf die nach außen hin sichtba-ren und relevanten Aspekte beschrankt, beschreibt dieImplementierung auch alle anderen Aspekte im Detail.Untertypen beziehen sich daher auf eine konzeptuelleund Implementierungen auf die konkrete Ebene. Manspricht daher auch haufig von Vererbung von Konzep-ten (gleichbedeutend mit Subtyping) im Gegensatz zurVererbung von Implementierungen.

Den wesentlichen Unterschied zwischen Vererbungvon Konzept und Implementierung versteht man ambesten anhand eines Beispiels, in dem sich die beidenHierarchien unterscheiden:

Collection

Set IntegerSet

Bag

¨ ¨ ¨ ¨ r r r r

Collection

Bag Set

IntegerSet

¨ ¨ ¨ ¨ r r r r

Vererbung von Impl. Subtyping

Ziel der Vererbung von Implementierungen ist die di-rekte Wiederverwendung so vieler Programmteile wie

Page 48: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 48/64

48 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

moglich. Die Implementierungen von Set und Bag zei-gen so starke Ahnlichkeiten, dass sich die Wiederver-wendung von Programmteilen lohnt. In diesem Fall

erbt Bag große Teile der Implementierung von Set. Furdiese Entscheidung ist nur der pragmatische Gesichts-punkt, dass sich Bag einfacher aus Set ableiten lasst alsumgekehrt, ausschlaggebend. Fur IntegerSet wurdeeine von Set unabhangige Implementierung gewahlt,da durch die Beschrankung auf ganze Zahlen eine we-sentlich effizientere Implementierung moglich ist.

Wenn wir uns von Konzepten (bzw. Typen) leitenlassen, schaut die Hierarchie anders aus. Bag und Set

stehen in keinem Verhaltnis zueinander, da die Rou-tinen f ur das Hinzuf ugen bzw. Loschen von Elemen-ten einander ausschließende Bedeutungen haben, ob-wohl Set und Bag dieselbe Schnittstelle haben konnen.Die Operationen f ur Set und IntegerSet haben dage-gen dieselbe Bedeutung. IntegerSet kann, aufgrundder Einschrankung auf ganze Zahlen, eine gegenuberSet erweiterte Schnittstelle anbieten und ist daherein Untertyp von Set. Beispielsweise kann man f urIntegerSet eine Routine definieren, die die großteZahl in der Menge findet. Eine vergleichbare Opera-tion kann es in Set nicht geben, da auf den Elementendieser Menge keine Ordnung definiert ist.

Obiges Beispiel demonstriert unterschiedliche Argu-mentationen f ur die Vererbung von Implementierun-gen und Konzepten. Die Unterschiede zwischen den

Argumentationen sind wichtiger als jene zwischen denHierarchien, da die Hierarchien selbst letztendlich vonDetails der Sprache und beabsichtigten Verwendungenabhangen. Beim Implementierungsbeispiel wurde da-von ausgegangen, dass sich Bag relativ einfach durchVerwendung von Set implementieren lasst, was nichtf ur jede Sprache zutreffen muss. Bei der Vererbung vonKonzepten wurde implizit ein kovariantes Typsystemvorausgesetzt, das eine entsprechende Spezialisierungzwischen Set und IntegerSet erlaubt.

In den letzten Jahren wurden experimentelle Spra-chen entwickelt, in denen Subtyping von einem Me-

chanismus f ur die Vererbung von Implementierungengetrennt ist. Wahrend in der Typhierarchie das Er-setzbarkeitsprinzip beachtet werden muss, braucht die-ses in der Vererbungshierarchie nicht zu gelten. Damitist es einfach, Vererbung sehr allgemein zu definieren.Zum Beispiel kann man auch erlauben, dass bei derVererbung nur Teile einer Klasse in die neue Klasseubernommen werden. Das folgende Beispiel ist in derexperimentellen Sprache Portlandish geschrieben:

type Person signature

Name (): String,

Versnr (): Integer

endType

Diese Typdefinition legt nur eine Schnittstelle fest. Die

entsprechende Implementierung wird getrennt davonspezifiziert:

implementation PersonImpl of Person

fieldsN: String;

V: Integer;

methods

Name (): String return N

Versnr (): Integer return V

endImplementation

Durch diese Trennung, vor allem durch getrennte Na-men f ur Typen und Implementierungen, ist es moglich,alternative Implementierungen zu definieren:

implementation AnderePersonImpl of Person

fields ... methods ...

endImplementation

Vererbung erfolgt getrennt f ur Typdefinitionen und Im-plementierungen:

type Student

supertypes Person

signature ...

endType

implementation StudentImpl of Student

superImplementations PersonImpl

fields ... methods ...

endImplementation

In diesem Beispiel stimmen die Hierarchien von Typenund Implementierungen uberein. Da dieser Fall haufigauftritt, stellt sich die Frage, ob sich der zusatzlicheAufwand lohnt. Eine andere Moglichkeit ist, dass mannur Subtyping verwendet. Zur Vermeidung des Dupli-zierens von Programmcode konnen Sprachkonstruktegenutzt werden, die, wie z. B. Prozeduraufrufe, in derSprache ohnehin vorhanden sind. Vor allem Sprachen,die Module und Typen zu Klassen vereinen, machen

daf ur aber gezielte Lockerungen bei den Sichtbarkeits-regeln notwendig.

Obiges Beispiel konnte man in einer sehr ahnlichenForm auch in Java oder C# schreiben. Diese Sprachenerlauben die Definition von Schnittstellen unabhangigvon Klassen. Allerdings mussen auch Klassen in Javaund C#, soweit dies uberpruft werden kann, dem Er-setzbarkeitsprinzip entsprechen. Damit ist es in diesenSprachen kaum moglich, Typ- und Vererbungshierar-chien klar voneinander zu trennen.

Neben Subtyping und Vererbung von Implementie-rungen gibt es noch eine dritte Hierarchie, eine

”is-

a“-Hierarchie, die ublicherweise in der Analyse- bzw.Designphase eines Softwareprojekts erstellt wird. Da-bei handelt es sich um eine konzeptuelle Hierarchie. Im

Page 49: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 49/64

5.2. ALLGEMEINE KONZEPTE 49

Gegensatz zu Subtyping sind in der”

is-a“-Hierarchiedie Schnittstellen und das Verhalten nur ansatzweisefestgelegt, und auf Einschrankungen wie Ko- und Kon-

travarianz wird verzichtet. Diese Hierarchie stellt ofteinen Vorlaufer der Subtyping-Hierarchie dar.

Typen werden in objektorientierten Sprachen durchihre Schnittstellen und sichtbaren Verhaltensweisenfestgelegt. (Typen in funktionalen Sprachen beschrei-ben nur Schnittstellen.) Es stellt sich die Frage, wieman das Verhalten eines Typs beschreiben kann wennImplementierungen daf ur nicht geeignet sind. Standder Technik ist, das Verhalten eines Typs in der dazu-gehorigen Dokumentation zu beschreiben. In manchenFallen werden dazu auch formale Spezifikationsspra-chen verwendet. Ein Ziel sollte sein, die formale Spe-zifikation eines Typs dem Compiler zuganglich zu ma-chen, damit die Ubereinstimmung zwischen Spezifika-tion und Implementierung uberpruft werden kann. Einerster Ansatz daf ur sind die Zusicherungen in Eiffel.Allerdings erlauben diverse Einschrankungen auf die-sen Zusicherungen nur die Darstellung eines Teils dergesamten Spezifikation eines komplexeren Typs im Pro-grammcode. Die Darstellung und Uberprufung komple-xerer Spezifikationen, die vor allem auf logischen undalgebraischen Methoden beruhen, ist Gegenstand ak-tueller Forschungsaktivitaten.

Untertypen mussen das selbe Verhalten wie Ober-typen beschreiben, konnen dabei aber mehr Details

berucksichtigen. Da Verhaltensbeschreibungen zur Zeiteinem Compiler kaum zuganglich sind, muss irgendwieanders festgelegt werden, welche Typen aufgrund ih-res Verhaltens kompatibel sind und welche nicht. Amhaufigsten geschieht dies durch die explizite Angabeder Obertypen in jedem Typ. Z. B. folgen Ada, Eiffel,C++ und Java diesem Ansatz, der aber nur funktio-niert wenn Typkompatibilitat durch Namensgleichheitfestgelegt ist. Wenn Typkompatibilitat durch Struk-turgleichheit bestimmt ist, kann ein Ansatz wie inModula-3 verwendet werden: Neben global sichtbarenVariablen und Signaturen von Routinen spezifiziert ein

Typ auch eine Menge zusatzlicher Namen, die das Ver-halten in einer sehr abstrakten Form beschreiben. EinTyp B ist ein Untertyp eines Typs A wenn es f ur jedeVariable, jede Signatur und jeden verhaltensbeschrei-benden Namen in A eine entsprechende Variable oderSignatur bzw. den gleichen Namen in B gibt. In jedemFall kann das Verhalten durch einen beliebig gewahltenNamen abstrahiert werden.

5.2.3 Subtyping und Verhalten

Liskov und Wing [16] haben eine vielfach zitierte De-finition von Subtyping erarbeitet. Diese Definition be-ruht darauf, dass Typen formale Spezifikationen dessichtbaren Verhaltens aller Instanzen sind. Eine solche

Typspezifikation sieht zum Beispiel so aus:

bag = type

uses BBag (bag for B)

for all b: bagput = proc (i : int)

requires |bpre.elems| < bpre.bound modifies bensures bpost .elems = bpre.elems ∪ i

∧ bpost .bound = bpre.bound

get = proc () returns (int)requires bpre.elems = modifies bensures bpost .elems = bpre.elems − result

∧ result ∈ bpre.elems∧ bpost .bound = bpre.bound

Die uses-Klausel gibt eine Sorte B—im algebraischenSinn—mit dessen Spezifikation BBag an. Damit wer-den grundlegende mathematische Symbole wie |x| (An-zahl der Elemente in der Menge x), ∪, =, =, ≤, etc.eingef uhrt, die in der Typspezifikation gebraucht wer-den. Die mit for all beginnende Klausel besagt, dassdie folgende Spezifikation f ur alle Instanzen b des Typsbag gilt. Es werden zwei Routinen spezifiziert. Fur jedeRoutine gibt es eine Vorbedingung (requires-Klausel),Invariante (in diesem Fall als modifies-Klausel angege-ben) und eine Nachbedingung (ensures-Klausel). DieInstanz b tritt in den Zusicherungen in zwei Versionen

auf: bpre steht f ur den Wert von b vor Ausf uhrung derRoutine, bpost f ur den Wert nach der Ausf uhrung. DieSemantik dieser Spezifikation sollte intuitiv klar sein.

Eine (informale) Definition von Subtyping f ur solcheTypspezifikationen lautet wie folgt:

s ist ein Untertyp von t wenn

1. jede Invariante von s eine entsprechene Invariantevon t impliziert; Invarianten in s sind also minde-stens so stark wie die in t;

2. die Routinen von s sich wie die Routinen von tverhalten:

(a) Eingangsparametertypen sind kontravariant;

(b) Ergebnistypen sind kovariant;

(c) die Menge der von s erlaubten Ausnahme-behandlungen liegt in der Menge der von terlaubten Ausnahmebehandlungen;

(d) Vorbedingungen von t implizieren die ent-sprechenden Vorbedingungen von s (Kontra-varianz); Vorbedingungen in s konnen alsonur schwacher sein als die in t;

(e) Nachbedingungen von s impizieren die ent-sprechenden Nachbedingungen von t (Kova-rianz); Nachbedingungen in s sind also min-destens so stark wie die in t;

Page 50: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 50/64

50 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

3. die Einschr ankungen auf s entsprechende Ein-schrankungen auf t implizieren; Einschrankungenauf s sind also mindestens so stark wie die auf t.

Diese Definition verwendet Einschr ankungen (engl.

”constraints“), auf die wir bis jetzt noch nicht einge-

gangen sind. Solche Einschrankungen verhindern Pro-bleme, die durch

”aliasing“ entstehen konnen: Ange-

nommen, ein Objekt wird uber mehrere Variablen re-ferenziert. In einer Sprache mit einem polymorphenTypsystem konnen diese Variablen verschiedene Ty-pen haben. Jeder dieser Typen kann eine unterschied-liche Menge von Routinen anbieten. Jedoch werdenVeranderungen auf dem Objekt, die durch die Sicht-weise der einen Variable erfolgen, auch uber die andereVariable sichtbar. In einigen Fallen konnen diese ver-

schiedenen Sichtweisen problematisch sein. Zum Bei-spiel, wenn t der Typ einer Menge ist, die nur Routinenzum Einf ugen von Elementen in die Menge und zumLesen der Elemente in der Menge anbietet, kann f alsch-lich angenommen werden, dass ein eingef ugtes Elementimmer in der Menge bleibt. Man kann einen Untertyps von t definieren, der eine zusatzliche Routine zumLoschen anbietet. Nun kann eine Instanz von s aberauch uber eine Variable vom Typ t referenziert wer-den. Die uber den Typ t gemachte Annahme, dass alleElemente stets in der Menge bleiben, ist also nicht not-wendigerweise erf ullt.

Durch die explizite Spezifikation von Einschrankun-gen sind solche Falle ausgeschlossen. Einschrankungbeschreiben, welche Eigenschaften von Instanzen desTyps erwartet werden durfen. Zum Beispiel ist die An-nahme, dass alle Elemente stets in der Menge blei-ben, nur erlaubt wenn es eine entsprechende Ein-schrankung, etwa bpre.elems ⊆ bpost .elems, auf t gibt.Wenn es jedoch eine solche Einschrankung gibt ist esnicht moglich, einen Untertyp s von t einzuf uhren,der diese Einschrankung verletzt; eine Routine zumLoschen von Elementen kann es in s daher nicht ge-ben. Die ublichen Zusicherungen (vor allem Vorbedin-gungen) konnen nicht alle notigen Einschrankungen

ausdrucken, da Aufrufer nur den nach außen sichtba-ren Objektzustand sehen und sich Zustande durch Ne-benlaufigkeit zwischen Uberprufung der Vorbedingun-gen und Ausf uhrung einer Routine andern konnen.

5.2.4 Untertypen und Generizitat

Die beiden Arten des universell quantifizierten Poly-morphismus, namlich Subtyping und Generizitat (bzw.parametrischer Polymorphismus), wurden auf sehr un-terschiedliche Weise eingef uhrt. Subtyping ist nachdem Ersetzbarkeitsprinzip so definiert, dass Instanzeneines Untertyps uberall eingesetzt werden konnen, woInstanzen eines entsprechenden Obertyps erwartet wer-den. Generizitat ist ein Machanismus zur Beschreibung

einer Familie von Typen, die sich nur durch die f ur Typ-parameter eingesetzten Typen unterscheiden. Es stelltsich daher die Frage, wie sich diese beiden Arten von

Polymorphismus zueinander verhalten.Betrachten wir beispielsweise Listen von Elemen-ten gleichf ormiger Typen. Dies lasst sich in Ada auf naturliche Weise durch die Verwendung von Generi-zitat ausdrucken:

generic

type Data is private;

package Lists is

type Elem is private;

function Append (A,B: access Elem)

return access Elem;

private

type Elem is recordItem: Data;

Next: access Elem

end record;

end Lists;

type Person is ...

package PersonLists is new Lists (Person);

Fur jeden Typ kann damit generisch ein Listentyp mitden notwendigen Operationen erzeugt werden. Abgese-hen von der Kapselung im Paket lasst sich ein entspre-chender Effekt auch durch Vererbung erreicht:

type Elem is tagged record

Next: access Elem

end record;

function Append (A,B: access Elem)

return access Elem;

type PersonElem is new Elem with ...

Dieses Beispiel veranschaulicht, dass zumindest in ei-nigen Fallen Subtyping und Generizitat gegeneinan-der austauschbar sind. Kann man vielleicht mit einemder beiden Mechanismen alles ausdrucken, was manim jeweils anderen ausdrucken kann? Ohne Subtyp-

ing, also nur mit Generizitat, ist es zum Beispiel nichtmoglich, heterogene Listen auszudrucken; das sind Li-sten, die Elemente von mehr als nur einem Typ enthal-ten konnen. Heterogene Listen kommen in der Praxisaber vor. Daher ist Generizitat sicher nicht machtigerals Subtyping.

Dagegen hat man mit Subtyping alleine andere Pro-bleme. Wenn man kontravariantes Subtyping verwen-det, kann man beispielsweise die Schnittstelle der Funk-tion Append in obigem Beispiel nicht in einem Unter-typ ubernehmen, da die Eingangs- und Ausgangspara-meter jeweils denselben Typ haben sollen. Jede Formvon kovariantem Subtyping unterliegt aber, wie in Ab-schnitt 5.2.1 erklart, verschiedenen unerwunschten Ein-schrankungen, die es bei Generizitat nicht gibt. Dar-

Page 51: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 51/64

5.2. ALLGEMEINE KONZEPTE 51

aus folgt, dass Generizitat und Subtyping einandererganzen.

Die Kombination von Generizitat und Subtyping

druckt sich sehr anschaulich in gebundener Generi-zitat aus. Der Ausdruck KONTO[T->GELDBETRAG] inEiffel besagt zum Beispiel, dass f ur den Typparame-ter T in der Klasse KONTO jeder beliebige Untertyp vonGELDBETRAG eingesetzt werden kann. Durch die Be-schrankung auf Untertypen von GELDBETRAG sind ei-nige der Eigenschaften aller Instanzen von T, namlich jene, die auch alle Instanzen von GELDBETRAG haben,bekannt und konnen verwendet werden. Zum Beispielkonnen je zwei Instanzen von T addiert werden undliefern als Ergebnis wieder eine Instanz von T.

Gebundene Generizitat verwendet Subtyping als einHilfsmittel. Wir fragen uns nun, unter welchen Be-dingungen ein generischer Typ ein Untertyp eines an-deren generischen Typs ist, also uberall, wo eine In-stanz des zweiten generischen Typs erwartet wird,auch eine Instanz des ersten generischen Typs ver-wendet werden kann. Im Fall von Eiffel ist die Fra-ge einfach zu beantworten: Ein Typ (bzw. eine Klas-se) x[a1->s1, . . . , an->sn] ist ein Untertyp (bzw. ei-ne Unterklasse) von y[b1->t1, . . . , bn->tn], wenn f uralle 1 ≤ i ≤ n gilt, dass si ein Untertyp von ti undx[s1, . . . , sn] ein Untertyp von y[s1, . . . , sn] ist. Esist klar, dass bei der Ersetzung der Parameter immernur die spezifischeren konstanten Typausdrucke si ver-

wendet werden konnen, da f ur alle anderen Typen eineBedingung in x verletzt ware.

Mit dieser Version von gebundenen Typparameternbzw. Subtyping f ur generische Typen gibt es aber einProblem: Sie ist nur dann vollstandig bzw. anwendbar,wenn alle Parametertypen kovariant sind, was in derRegel nicht gilt. Trotzdem werden solche Beziehungenin der Praxis f ur wichtig erachtet. Beispielsweise sindauf Arrays in Java und C# implizit solche Untertyp-beziehungen definiert; Student[] ist ein Untertyp vonPerson[] wenn Student ein Untertyp von Person ist,obwohl es dadurch zu Typfehlern zur Laufzeit kom-

men kann: Enthalt eine Variable vom deklarierten TypPerson[] ein Array des Typs Student[], dann erlaubtdie statische Typuberprufung das Schreiben einer In-stanz von Person in das Array; zur Laufzeit wird eineAusnahmebehandlung ausgelost, falls der dynamischeTyp des in das Array geschriebenen Objekts ungleichStudent ist.

Fur Sprachen mit kontravariantem Subtyping wer-den wesentlich machtigere Mechanismen benotigt, umkomplexe Beziehungen zwischen den Typparameternausdrucken zu konnen. Mit herkommlichen Mittelnkann nur ausgedruckt werden, dass ein f ur einen Typ-parameter eingesetzter Typ ein Untertyp einer gegebe-nen Typkonstante ist. Man will zumindest auch fest-legen konnen, dass ein eingesetzter Typ ein Obertyp

einer Typkonstante ist, und das nicht nur bei derDeklaration eines Typparameters. Daf ur erforderlicheSprachmechanismen wurden in der Version 1.5 zusam-

men mit Generizitat zu Java hinzugef ugt: Die Aus-drucke ? extends T (beliebiger Untertyp von T) und? super T (beliebiger Obertyp von T) konnen in vie-len Fallen statt konkreter Typen Typparameter erset-zen. Das geht aber nur, wenn auf entsprechende Va-riablen nur lesend (im Fall von ? extends T, also Zu-griffe, f ur die kovariante Typen gefordert werden) bzw.nur schreibend (im Fall von ? super T, kontravarianteTypen) zugegriffen wird. Dazu ein Beispiel in Java:

class List<T>

copyFrom (List<? extends T> f) ...

copyTo (List<? super T> t) ...

...

Beide Methoden kopieren den Inhalt generischer Li-sten, copyFrom von der Liste f in die aktuelle Listethis vom Typ T und copyTo von this nach t. Auf f

wird nur lesend und auf t nur schreibend zugegriffen.Dabei wird List<Student> nur dann als Untertyp vonList<Person> aufgefasst, wenn auf den ersten Typ nurlesend oder auf den zweiten nur schreibend zugegriffenwerden darf.

In der Theorie gibt es im Wesentlichen zwei Moglich-keiten, Klassen bzw. generische Typen zueinander in

Beziehung zu setzen, namlich F-gebundene Generizitatund Subtyping hoherer Ordnung. Angenommen, T be-zeichnet einen generischen Typ und T [S ] einen Typ,der durch Ersetzung eines Typparameters in T durchden Typ S aus T abgeleitet wird. Im F-gebundenen Polymorphismus gilt die Beziehung S ≤ T [S ], derUntertyp kann also den Typparameter des (generi-schen) Obertyps ersetzen. Die meisten aktuellen Spra-chen mit Generizitat verwenden F-gebundene Generi-zitat und ermoglichen damit rekursive Definitionen wiein S ≤ T [S ]. In Java 1.5 sind beispielsweise viele Klas-sen und Schnittstellen folgendermaßen definiert:

interface Comparable<T> boolean equal (T that);

class Integer extends Comparable<Integer>

public boolean equal (Integer that) ...

Der Typ des formalen Parameters that wird durcheinen Typparameter bestimmt, nicht durch Subtyping.Damit werden Einschrankungen beim Subtyping (ko-variante bzw. binare Routinen) vermieden und es isttrotzdem sichergestellt, dass this in equal denselbendynamischen Typ wie that hat.

Eine alternative Beziehung mit ahnlicher Ausdrucks-starke ist Subtyping h oherer Ordnung : Zwei generische

Page 52: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 52/64

52 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

Typen S und T sind in einer Untertypbeziehung hoher-er Ordnung, wenn S [U ] ≤ T [U ] f ur alle Typen U gilt.Subtyping hoherer Ordnung scheint mathematisch ein-

facher handhabbar zu sein als F-gebundener Polymor-phismus, hat sich in der Praxis aber kaum durchge-setzt. Man kann nicht-generische Typen einfach nur alsSpezialfall generischer Typen (mit einer leeren Men-ge von Typparametern) ansehen. Dann braucht manin einer Sprache nicht zwischen nicht-generischen undgenerischen Typen unterscheiden. Als Grundlage f urdie Vererbung kann man dann <#—man nennt die-se Beziehung in diesem Fall Matching definiert durchS <# T wenn S [U ] ≤ T [U ] f ur alle Typen U —anstattder Untertypbeziehung verwenden. Leider erf ullt aber<# das Ersetzbarkeitsprinzip nicht. Der Grund daf urliegt darin, dass gebundene Typparameter in rekursi-ven Typen (siehe Abschnitt 4.3.2) kovariant als Ein-gangsparametertypen verwendet werden durfen. ZumBeispiel gilt die Beziehung

i:int, f:u→u, next:u <# i:int, f:v→v.

Eine derartige Beziehung ist in vielen Fallen durchauserwunscht. Durch die Verletzung des Ersetzbarkeits-prinzips durfen Aufrufe von Routinen aber nur dannpolymorph sein wenn garantiert ist, dass kein Argu-ment einen Typ mit moglicherweise kovarianten Ein-gangsparametertypen hat. Die Situation ist also sehrahnlich der von

”CAT calls“ in Eiffel.

5.3 Logik und Subtyping

Typen und Subtyping konnen nicht nur in imperativenSprachen gewinnbringend eingesetzt werden. In diesemAbschnitt wird dies an der Programmiersprache LIFEdemonstriert, deren Wurzeln in der logischen Program-mierung liegen.

ψ-Terme kombinieren logische Terme (siehe Ab-schnitt 2.2) mit Typen, auf denen ein Verband defi-niert ist (siehe Abschnitt 4.1). Stark vereinfacht ausge-

druckt erweitern ψ-Terme gewohnliche Terme so, dasserweiterbare verbundahnliche Datenstrukturen in logi-schen Sprachen einfacher reprasentiert werden konnen.Ein Beispiel veranschaulicht dies:

X:person(name =>

nameT(first => string,

last => S:string),

married =>

person(name => nameT(last => S),

married => X))

person, nameT und string sind Typsymbole. In LI-FE werden sie, in Anlehnung an Algebren, Sorten ge-nannt. Der Begriff Typ wird vermieden, da ψ-Termeselbst, in gewisser Weise, Typen sind. name, first,

last und married sind Label zur Bezeichnung von Ver-bundkomponenten, die

” features“ oder Attribute heis-

sen. Die dritte Klasse von Symbolen sind Variablen-

namen, zu der auch X und S gehoren. Wie in Prologwerden Variablennamen mit großem Anfangsbuchsta-ben und alle anderen Symbole klein geschrieben. JedeSorte entspricht ungef ahr einem Verbundtyp, wobei dieKomponenten und deren Typen jedoch nicht festgelegtsind, sondern stets neue Komponenten dazugef ugt wer-den konnen. Die Sruktur des obigen ψ-Terms lasst sichgrafisch veranschaulichen:

person

person

string

nameT

nameT

string

T

E c

E

©

d d

d d

name

name

married

married last

lastfirst

X:

S:

Jedes Rechteck stellt einen Teilausdruck des ψ-Termsdar und enthalt die Sorte dieses Teilausdrucks. Eini-gen Teilausdrucken ist ein Variablenname zugeordnet.Pfeile mit den entsprechenden Bezeichnungen veran-schaulichen Attribute. Der ψ-Term besagt, dass X eine

Instanz von person ist, die zumindest die Komponen-ten name und married der Sorten nameT bzw. person

enthalt, wobei der Name der Person aus je einem Vor-und Nachnamen besteht. Jene Person, mit der X verhei-ratet ist, ist auch mit X verheiratet und hat einen Na-men, dessen Nachnamen-Komponente S mit dem Nach-namen von X ident ist.

Ein ψ-Term ist ein Ausdruck der Form x oder x:toder t, wobei x eine Variable und t ein

”untagged“ ψ-

Term ist. Ein”

untagged“ ψ-Term ist ein Ausdruck derForm s oder s(a1⇒t1, . . . , an⇒tn), wobei s eine Sor-te ist und a1, . . . , an paarweise verschiedene Attributeund t1, . . . , tn ψ-Terme sind. Eine Variable x darf ineinem ψ-Term hochstens einmal in der Form x:t auf-treten.

Auf ψ-Termen gibt es eine Halbordnung , die sichaus der Halbordnung ≤ auf der Menge der Sorten ab-leiten lasst. t t zwischen zwei ψ-Termen t und t giltgenau dann, wenn

1. s ≤ s f ur die außersten Sorten s und s von t bzw.t gilt, und

2. alle Attribute a1, . . . , an von t auch Attribute vont sind, und

3. ti ti f ur i ∈ 1, . . . , n und jedes ai⇒ti in t undai⇒ti in t gilt, und

Page 53: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 53/64

5.4. TYPEN F UR AKTIVE OBJEKTE 53

4. Teilausdrucke in t identisch sind falls die entspre-chenden Teilausdrucke in t identisch sind.

Wenn ≤ auf der Menge der Sorten einen Verband be-

schreibt, dann beschreibt auch auf der Menge allerψ-Terme, abgesehen von Variablenbenennungen, einenVerband. Das bedeutet, dass es f ur je zwei ψ-Termeein eindeutig bestimmtes Infimum gibt, das dem Ergeb-nis der Unifikation zwischen diesen beiden Termen ent-spricht. Fur das folgende Beispiel verwenden wir diesenAusschnitt aus einem Verband uber Sorten:

person

student angestellter

werkstudent professor

p1 p2 p3 p4

¨ ¨ ¨ ¨ ¨

r r r r r

r r r r r ¨ ¨ ¨ ¨ ¨ r r r r r

Die Unifikation von

student(betreuer =>

professor(assistent =>

angestellter),

freund => person)

und

X:person(betreuer =>

p4(assistent => X),

vermieter => person)

liefert das Ergebnis

X:werkstudent(betreuer =>

p4(assistent => X),

freund => person,

vermieter => person)

p1 bis p4 sind spezielle Sorten, die jeweils genau ei-ne Instanz beschreiben. Man kann p1 bis p4 selbst alsWerte auffassen. In LIFE werden Werte und Sortennicht unterschieden. Jede Sorte stellt auch einen Wertdar und umgekehrt.

LIFE-Programme sehen wie Prolog-Programme ausund werden, abgesehen von der unterschiedlichen Unifi-kation, gleich ausgef uhrt. Zum Beispiel liefert das Pro-grammstuck

prueft(p4, p1).

fleissig(angestellter).

fleissig(X:student) :- prueft(professor, X).

auf die Anfrage”

fleissig(Y:student)“ nacheinanderdie beiden Antworten

”Y=werkstudent“ und

”Y=p1“.

5.4 Typen f ur aktive Objekte

Nebenlaufige (parallele) objektorientierte Sprachen be-

ruhen manchmal auf aktiven Objekten. Ein aktives Ob- jekt ist im Wesentlichen ein Prozess, der mit anderenObjekten uber

”message passing“ kommuniziert. Wie

jedes Ob jekt besitzt auch ein aktives Objekt eine Iden-titat, einen veranderlichen Zustand und ein Verhalten,das durch ein Programmstuck festgelegt ist. Aktive Ob- jekte konnen, genauso wie passive Objekte, typisiertsein. Uber den Typen der aktiven Objekte lasst sichauch Subtyping realisieren, wie in Abschnitt 5.4.1 be-schrieben. Allerdings ist die Vererbung des Programm-codes aktiver Objekte oft schwierig, wie wir in Ab-schnitt 5.4.2 sehen werden.

5.4.1 Prozesstypen

Man kann”

tasks“ in Ada als aktive Objekte ansehen,denen ein Typ zuordenbar ist. Abbildung 5.1 gibt einkleines Beispiel f ur einen Prozess und dessen Typ. DerTyp beschreibt die Nachrichten, die von einem Prozessakzeptiert werden konnen. Die in der Typdeklarationbereitgestellte Information ist jedoch sehr luckenhaft.Vor allem fehlt die Information, wann und unter wel-chen Umstanden eine Nachricht vom Prozess akzep-tiert wird. Das Beispiel beschreibt einen Puffer, derzu jedem Zeitpunkt nur ein einziges Element enthalten

kann. Das heisst, die Nachrichten Put und Get werdennur abwechselnd akzeptiert, wobei die erste NachrichtPut sein muss. Das Programmstuck

B: Buffer; E1, E2: Element;

B.Put(E1); B.Put(E2); B.Get(E1);

wurde also zu einem”

deadlock“ f uhren, falls es keinenweiteren Prozess gibt, der entsprechende Nachrichtenan B sendet. Aus dem Typ von Buffer ist dieses Ver-halten aber nicht ersichtlich.

Ein geeignetes Typsystem f ur aktive Objekte musstein der Lage sein, das Verhalten seiner Instanzen (Pro-zesse) so weit zu beschreiben, wie es f ur ihre richti-

ge Verwendung notwendig ist. Prozesstypen , die auf einer Prozessalgebra (siehe Abschnitt 2.3.2) beruhen,konnen die notwendige Information beschreiben. Alleatomaren Aktionen in Prozesstypen beschreiben Nach-richten, die erwartet werden. Atomare Aktionen wer-den mittels sequentieller Komposition (∗), parallelerKomposition () und alternativer Komposition (+) zuProzesstypen zusammengesetzt. Der folgende Prozess-typ entspricht obigem Ada-Beispiel:

Buffer = Put (in Element) ∗Get (out Element) ∗Buffer

Eine Instanz dieses Typs erwartet zuerst eine Nachrichtmit der Schnittstelle Put(in Element), danach eine

Page 54: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 54/64

54 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

task type Buffer is

entry Put (E: in Element);

entry Get (E: out Element);

end Buffer;task body Buffer is

X: Element;

begin

loop

accept Put (E: in Element)

do X := E;

end;

accept Get (E: out Element)

do E := X;

end;

end loop;

end Buffer;

Abbildung 5.1: Beispiel f ur einen Task in Ada

mit Get(out Element). Wegen des rekursiven Aufrufsvon Buffer wird diese Sequenz unbeschrankt oft wie-derholt. Daher ist obige Definition von Buffer aquiva-lent zu dieser:

Buffer = Put (in Element) ∗Get (out Element) ∗Put (in Element) ∗

Get (out Element) ∗...

Das nachste Beispiel zeigt den Prozesstyp eines Puf-fers, der beliebig viele Elemente enthalten kann:

IBuffer = ( Put (in Element) ∗Get (out Element) )

IBuffer

Ein aktives Objekt dieses Typs erwartet also gleichzei-tig beliebig viele Put-Nachrichten, aber hochstens soviele Get-Nachrichten wie gerade Elemente im Puffersind. Beliebig viele und beliebig geordnete Put- undGet-Nachrichten werden von den Instanzen des folgen-den Prozesstyps erwartet:

UBuffer = Put (in Element) Get (out Element) UBuffer

Bei der Verwendung von Variablen eines Prozess-typs ist Vorsicht geboten: Durch bestimmte Zugriffsar-ten auf solche Variablen andern sich die Typen. Wirdeine Nachricht an den durch die Variable referenzier-ten Prozess geschickt, andert sich der Typ implizit;der neue Typ ist jener, den der Prozess nach Verar-beitung der Nachricht haben wird. Hat die VariableB zum Beispiel den Prozesstyp Buffer, so wird durch

Ausf uhrung des Ausdrucks B.Put(E1) der Typ von B

zu Get(out Element) ∗ Buffer verandert. B erwartetdanach also eine Get-Nachricht.

Wenn mehrere Prozesse gleichzeitig Nachrichten aneinen weiteren Prozess schicken, konnen sie sich gegen-seitig beeinflussen. Um sicherzustellen, dass dadurchkeine Typinkompatibilitaten auftreten, werden Rechtef ur das Senden von Nachrichten an Variablen vergeben.Diese Rechte werden durch Prozesstypen ausgedruckt.Uber eine Variable darf nur dann eine Nachricht an dendurch die Variable referenzierten Prozess gesendet wer-den, wenn der Prozesstyp der Variable diese Nachrichtals nachste erwartet. Ein neu aufgespannter Prozesswird nur uber eine Variable referenziert, deren Pro-zesstyp das nach außen sichtbare Verhalten des Pro-zesses vollst

¨andig beschreibt. Wird eine neue Variable,

die auf denselben Prozess zeigt, angelegt (z. B. durchVerwendung der Variable als Argument einer Routi-ne), muss der ursprungliche Prozesstyp auf die beidenVariablen aufgeteilt werden; das heisst, wenn der ur-sprungliche Prozesstyp von der Form τ τ ist, dannist der neue Prozesstyp der einen Variable τ und je-ner der anderen τ . Die Aufspaltung darf nur an einerdurch den Operator

”“ gekennzeichneten Stelle erfol-

gen. Dadurch konnen sich die Nachrichten, die gemaßτ und τ gesendet werden, beliebig uberlappen, ohnesich gegenseitig zu storen.

Auf Prozesstypen lasst sich durch einige Regeln eine

Untertyp-Relation ≤ definieren. Mit Hilfe der folgen-den Regeln lasst sich zum Beispiel zeigen, dass UBuffer

ein Untertyp von sowohl Buffer als auch IBuffer istund IBuffer ein Untertyp von Buffer ist. Dies ist auchintuitiv klar, da man auch einen unendlich großen Puf-fer so verwenden kann als ob er nur ein Element bein-halten konnte. Einen unbeschrankten Puffer kann manauch so verwenden, als ob er durch eine Bedingungeingeschrankt ware. Bei solchen Verwendungen wer-den zwar nicht alle Moglichkeiten ausgeschopft, aberes konnen dabei keine Inkonsistenzen auftreten.

τ ≤ τ

τ ≤ ε (ε entspricht dem leeren Typ )

τ 1 ≤ τ 1 . . . τ n ≤ τ nm(τ 1, . . . , τ n) ≤ m(τ 1, . . . , τ n)

τ ≤ τ

τ ∗ τ ≤ τ ∗ τ

τ ≤ τ τ ≤ τ

τ τ ≤ τ ∗ τ

τ ≤ τ τ ≤ τ

τ τ ≤ τ τ

τ ≤ τ τ ≤ τ

τ + τ ≤ τ + τ

Page 55: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 55/64

5.5. EIGENSCHAFTEN AUSGEW AHLTER OBJEKTORIENTIERTER SPRACHEN 55

Die Typkonsistenz von Prozesstypen, die auf Pro-zessalgebren beruhen, lasst sich (abgesehen von einigenrekursiven Typen) statisch uberprufen. In einem typ-

konsistenten Programm ist garantiert, dass alle gesen-deten Nachrichten vom Empf anger verstanden werden,und zwar in der Reihenfolge, in der die Nachrichtengesendet wurden. Eine neuere Generation von Prozess-typen, deren Syntax nicht mehr auf Prozessalgebrenberuht sondern eher der von konventionellen Typenahnelt, beseitigt die Probleme mit rekursiven Typenund bietet dem Programmierer mehr Flexibilitat. Dieoben erlauterten Grundprinzipien wie Anderung einesTyps beim Senden von Nachrichten und Typaufspal-tung beim Erzeugen neuer Referenzen blieben auch inder neueren Generation erhalten.

5.4.2 Vererbungsanomalie

Wahrend Typen f ur aktive Objekte ein relativ jungesForschungsgebiet darstellen, gibt es bereits zahlreicheErgebnisse bezuglich der Vererbung von Programmco-de f ur aktive Objekte. Bereits sehr fruh wurde erkannt,dass die Wiederverwendung von Code f ur nebenlaufi-ge Programme viel schwieriger und weniger erfolgreichrealisiert werden kann als f ur sequentielle Programme.Ein Grund daf ur liegt vermutlich in der notwendigenSynchronisation nebenlaufiger Prozesse: Wenn eine Un-terklasse (bzw. ein abgeleiteter Typ) eine Klasse durch

das Dazuf ugen neuer Operationen erweitert, ergebensich meist ganzlich neue Synchronisationsbedingungen,so dass der Synchronisationscode f ur viele ansonstennicht veranderte Operationen umgeschrieben werdenmuss. Und die Erweiterung der Funktionalitat durchdas Dazuf ugen von Operationen ist eine haufige Ursa-che f ur die Verwendung von Unterklassen. Dieses Pro-blem wird haufig als Vererbungsanomalie in nebenlaufi-gen, objektorientierten Sprachen bezeichnet.

Die Trennung des Synchronisationscodes vom restli-chen Code kann bei der Umgehung der Vererbungsan-omalie hilfreich sein, da damit wenigstens der restliche

Code uneingeschrankt wiederverwendet werden kann.Auch Synchronisationscode kann durch diese Trennungeinfacher wiederverwendet werden, da daf ur eine ei-genstandige Darstellung in einer unabhangigen Klas-senhierarchie eingef uhrt werden kann. In der Praxiszeigt sich jedoch, dass eine klare Trennung zwischendiesen Code-Arten oft nur schwer moglich ist.

Ein bekannter Vorschlag zur Losung des Problemsder Vererbungsanomalie ist, die Synchronisationsbe-dingungen in Unterklassen gegenuber den entsprechen-den Synchronisationsbedingugen in Oberklassen ein-zuschranken. Eine Operation in einer Unterklasse hatdemnach sicher nicht mehr Freiheiten als eine entspre-chende Operation in einer Oberklasse. Dieser Vorschlagist aber nur von begrenzter praktischer Bedeutung, da

Typen auf aktiven Objekten (z. B. Prozesstypen) genaudie umgekehrte Struktur haben, Untertypen also mehrFreiheiten erlauben als die entsprechenden Obertypen.

5.5 Eigenschaften ausgewahlter

objektorientierter Sprachen

In diesem Abschnitt sind einige charakteristische Merk-male der Typsysteme einiger bekannter objektorien-tierter Programmiersprachen kurz zusammengefasst.Ziel dieser Zusammenfassung ist, die in diesem Skrip-tum verwendete Terminologie sowie generelle Eigen-schaften und Einschrankungen von Typsystemen an-hand einiger ausgewahlter Sprachen, von denen ange-

nommen wird, dass sie den Studenten teilweise bekanntsind, noch einmal zu demonstrieren. Es wird keinerleiAnspruch auf Vollstandigkeit erhoben. Die Beurteilun-gen der Sprachen erfolgten hauptsachlich nach subjek-tiven Kriterien.

5.5.1 Smalltalk

Smalltalk ist eine bereits recht alte, aber noch immerbeliebte objektorientierte Sprache, die in eine sehr um-fangreiche Bibliothek von Klassen und Objekten ein-gebettet ist. Jede Klasse definiert implizit auch einenTyp. Variablen sind in (den meisten Dialekten von)

Smalltalk nicht typisiert; d. h. eine Variable kann einenZeiger auf ein Objekt jedes beliebigen Typs enthal-ten. Deswegen—und aufgrund des interpretativen Cha-rakters der Sprache—sind Typuberprufungen im We-sentlichen nur zur Laufzeit durchf uhrbar. Im Prinzipkann jede beliebige Nachricht an jedes beliebige Ob- jekt geschickt werden. Das Objekt entscheidet auf-grund der Nachricht, ob eine der Nachricht entspre-chende Methode oder eine speziell f ur unverstande-ne Nachrichten vorgesehene Methode ausgef uhrt wird.Es wird Einfachvererbung unterstutzt. Klassen habenhauptsachlich die Aufgabe, abstrakte Datentypen zu

realisieren. Wegen der untypisierten Variablen hat Ge-nerizitat keine Bedeutung; jede Klasse kann prinzipiellals generisch angesehen werden, wobei Typkonsistenzaber nicht vom System, sondern nur vom Programmie-rer sicherzustellen ist. Ahnlich verhalt es sich mit Sub-typing: Da jedes Objekt prinzipiell mit jeder Nachrichtumgehen kann, spielt Subtyping aus der Sicht des Sy-stems keine Rolle. Um das gewunschte Verhalten zuerreichen, mussen sich Programmierer selbst ohne Sy-stemunterstutzung darum kummern, dass das Ersetz-barkeitsprinzip erf ullt ist.

Zusammenfassend kann man sagen, dass Smalltalkeine sehr vielseitige objektorientierte Sprache ist, inder Typen eine (im Vergleich zu anderen objektori-entierten Sprachen) eher untergeordnete Rolle spie-

Page 56: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 56/64

56 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

len. Allerdings gibt es keine Locher im Typsystem;Smalltalk ist also vollstandig typisiert. Eine ahnlich ne-bensachliche Rolle spielen Typen in Objective C, einer

objektorientierten Erweiterung von C, ebenso wie inSelf, einer Sprache, in der sogar die Vererbungshierar-chie (bzw.

”delegation“) dynamisch anderbar ist, und

in CLOS, einer vielzitierten Erweiterung von Lisp. Indiesen Sprachen mussen sich Programmierer ohne Sy-stemunterstutzung darum kummern, dass nur erwarte-te Nachrichten an Objekte geschickt werden.

5.5.2 C++

In C++ sind, wie in vielen ahnlichen Sprachen, Ty-pen, Module und erweiterbare Verbunde zum Kon-zept der Klassen vereint. Die Sichtbarkeit und Ver-erbbarkeit von Verbundkomponenten wird durch dieSchlusselworter public, private und protected fest-gelegt. Zwischen Vererbung und Subtyping wird auf der Sprachebene nicht unterschieden. Parametertypensind stets invariant. Um einige Einschrankungen durchdie fehlende Trennung zwischen Typen und Modulenzu umgehen, wurden

”friends“ eingef uhrt. Sie erlauben

den benannten”

Freunden“ einer Klasse, auch auf sol-che Klassenkomponenten zuzugreifen, auf die sie sonstkeinen Zugriff hatten. Der Zugriff auf uberschriebeneImplementierungen von Funktionen ist moglich.

C++ hat sich aus der Sprache C entwickelt, in

der Typumwandlungen eine große Rolle spielen. Die-se Eigenschaft ist von C++ beibehalten und sogar un-eingeschrankt auf Klassen ausgeweitet worden. DurchTypumwandlungen kann der Programmierer die Ein-schrankungen des Typsystems beinahe beliebig umge-hen. Die formale Uberprufung der Typkonsistenz durchden Compiler hat daher in manchen Fallen nur sehrwenig mit der Konsistenz der semantischen Bedeutun-gen der Typen zu tun. Typuberprufungen zur Laufzeitsind in C bzw. C++ nicht vorgesehen. Neben beliebi-gen expliziten Typumwandlungen zahlen auch fehlen-de Uberprufungen der Bereichsgrenzen bei Feldzugrif-

fen, Varianten ohne Angabe der verwendeten Labels so-wie Probleme bei der dynamischen Speicherverwaltung(keine

”garbage collection“) zu den bekannten Lucken

im Typsystem, mit denen der Programmierer umgehenmuss.

Demgegenuber bietet C++ ein relativ reichhaltigesTypsystem. Es werden alle Arten von Polymorphismusunterstutzt: Neben Typumwandlungen gibt es uberla-dene Operatoren, Subtyping (zusammen mit Mehrfach-vererbung) und Generizitat durch

”templates“. Letzte-

re nutzen”

macros“ zur typkonformen Ersetzung von(gebundenen) Typparametern durch Typen. In C++haben auch Funktionen Typen. Weiters gibt es abstrak-te Typen, die durch Vererbung zu konkreten Typen er-weitert und in Schnittstellenbeschreibungen eingesetzt

werden konnen. Auch die explizite dynamische Typer-kennung wird durch RTTI (

”Run Time Type Informa-

tion“) unterstutzt.

Insgesamt ergibt sich ein etwas zwiespaltiges Bild:Einerseits bietet C++ ein sehr umfangreiches Typ-system, das bis auf einige Konstrukte Typuberprufun-gen zur Compilezeit ermoglicht. Andererseits ist es demProgrammierer freigestellt, dieses Typsystem beliebigzu umgehen—sicherlich oft unabsichtlich bzw. ohne dieKonsequenzen abschatzen zu konnen. Zur Nutzung derVorteile des Typsystems in der Softwareentwicklungund -wartung ist daher sehr viel Erfahrung und einestrenge Programmierdisziplin notwendig. Ein wichtigerPunkt auf der Negativseite ist ein kaum vorhandenesModulkonzept, woraus sich eine schlechte Uberprufbar-keit der Modulintegritat bei unabhangiger Uberset-zung und (trotz eigener Sprachkonstrukte) schlechteUnterstutzung getrennter Namensraume ergibt.

5.5.3 Java und C#

Die Programmiersprache Java hat wegen seiner In-ternetf ahigkeiten viel Interesse auf sich gezogen. DieTypsysicherheit von Java wird des ofteren als großerFortschritt gegenuber C++ angepriesen. Tatsachlichsind in Java die Lucken im Typsystem geschlossen.Grunde daf ur sind der Verzicht auf Typumwandlun-gen zugunsten von dynamischer Typerkennung, dyna-

mische Uberprufungen der Bereichsgrenzen bei Feldzu-griffen und eine komplette Reorganisation der dynami-schen Speicherverwaltung. Dadurch kann der Program-mierer Typuberprufungen nicht mehr umgehen, son-dern hochstens zur Laufzeit hin verschieben. Funktio-nen in Unterklassen konnen im Normalfall uberschrie-ben werden. Durch Einfachvererbung werden alle Pro-bleme der Mehrfachvererbung vermieden.

Diesen Fortschritten stand bis vor kurzer Zeit diefehlende Unterstutzung von Generizitat als bedeuten-de Unvollstandigkeit gegenuber. Durch die starke Typi-siertheit und nicht vorhandene

”macros“ ging die Gene-

rizitat besonders ab. Man musste dynamische Typuber-prufungen auch f ur solche Aufgaben einsetzen, die inanderen Sprachen nur mit statischen Typuberprufun-gen leicht losbar sind. Mit der Version 1.5 wurde zwarein ausgefeiltes Konzept f ur Generizitat in Java (undalle Java-Standardbibliotheken) eingef uhrt, jedoch gibtes noch sehr viel alten Java-Code und viele Program-mierer, die nicht auf die neue Version umsteigen wollen.

Ein weiteres Problem ist das Fehlen der Moglichkeit,Komponenten von Klassen umzubenennen, was leichtzu Namenskonflikten f uhren kann. Das Konzept derPakete ist in Java nicht sehr ausgefeilt und f uhrt gele-gentlich zu Problemen. Die fehlenden Funktionstypenstellen dagegen ein kleineres Problem dar, da sie sichdurch entsprechende Klassen einfach simulieren lassen.

Page 57: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 57/64

5.6. LITERATURHINWEISE 57

Einige vielgepriesene Eigenschaften des Typsystemserweisen sich bei genauerem Hinsehen als weniger uber-zeugend als erwartet. Zum Beispiel sind Subtyping und

Vererbung zwar auf der Ebene der Sprachbeschrei-bung getrennt, in der Realisierung jedoch sehr engmiteinander verknupft. Die

”interfaces“ in Java sind

nichts anderes als eine eingeschrankte Form abstrak-ter Typen. Dass Mehrfachvererbung zwar auf

”inter-

faces“ aber nicht auf Klassen definiert ist, stellt eineEinschrankung gegenuber anderen Sprachen wie z. B.C++ dar.

Zusammengefasst kann man sagen, dass Javas Typ-system zwar in mancher Hinsicht sicher einen Fort-schritt gegenuber dem von C++ darstellt, insgesamt jedoch weit hinter den heutigen Moglichkeiten zuruckbleibt. Es gibt zahlreiche praktikable Vorschlage zurVerbesserung von Java, welche die meisten gegenwarti-gen Unzulanglichkeiten beseitigen, die Sprache aberauch komplizierter machen wurden.

Die Programmiersprache C# ist Java sehr ahnlich.In Details bietet C# gelegentlich kleine Verbesserungengegenuber dem ursprunglichen Java, vor allem hinsicht-lich der Unterstutzung von Paketen. Generizitat ist inC# noch nicht vorhanden, wird aber in absehbarer Zeitin einer anderen Form als in Java eingef uhrt werden.Der großte Unterschied zwischen C# und Java liegtin den verwendeten Standard-Bibliotheken. Der Zwi-schencode und die Bibliotheken von C# (.NET) wer-

den von mehreren (von den Konzepten her ahnlichen)Programmiersprachen auf gleiche Weise verwendet undsind ganz auf das Betriebssystem Windows zugeschnit-ten, wahrend Java-Zwischencode nur auf eine Spracheoptimiert ist und großen Wert auf Kompatibilitat zwi-schen ganz unterschiedlichen Betriebssystemen legt.

5.5.4 Ada

Ada hat ein recht umfangreiches Typsystem, bei demsehr viele Typuberprufungen bereits vom Compilerdurchgef uhrt werden konnen und alle anderen Uber-

prufungen (bei entsprechenden Compileroptionen) zurLaufzeit erfolgen. Typen und Implementierungen sindzu einem Konzept vereint, Module aber unabhangigdavon. Einfachvererbung wird direkt unterstutzt, wo-bei Parametertypen auf eingeschrankte Weise kova-riant sind. Es werden alle Arten von Polymorphis-mus unterstutzt. Zwischen Subtyping und Vererbungwird nicht unterschieden. Abstrakte Typen, die Un-terstutzung abstrakter Datentypen und Bereichsein-schrankungen, die eine einfache Form von Zusicherun-gen darstellen, gehoren zu den Starken von Ada.

Insgesamt macht das Typsystem von Ada 95 einenausgereiften Eindruck. Allerdings mussten aus Kompa-tibilitatsgrunden zu Ada 83 Kompromisse eingegangenwerden, welche die Qualitaten des Typsystems nicht

auf den ersten Blick erkennen lassen. Ada verwen-det eine Terminologie, die sich teilweise drastisch vonder ublichen Terminologie unterscheidet. Außerdem ist

Ada eine sehr umfangreiche Sprache, so dass viel Erfah-rung notwendig ist, um die Starken von Ada ausnutzenzu konnen. Nicht zuletzt erfordert die Verwendung ob- jektorientierter Techniken in Ada einen, im Vergleichzu anderen Sprachen, relativ hohen Schreibaufwand.

5.5.5 Eiffel

Auch Eiffel hat ein recht umfangreiches Typsystem, beidem uberraschend viele Typuberprufungen zur Compi-lezeit durchf uhrbar sind, obwohl Subtyping mit kovari-anten Eingangsparametertypen verwendet wird. Trotz-

dem mussen noch viele¨Uberprufungen zur Laufzeitvorgenommen werden. Dynamische Typuberprufungen

lassen sich ausschalten. Typen, Module und erweiter-bare Verbunde sind zu Klassen vereint. Die fehlendeTrennung zwischen Typen und Modulen wird durchdas gezielte Sichtbarmachen von

”features“ (Routi-

nen und Variablen) in anderen Klassen großteils kom-pensiert. Zwischen Mehrfachvererbung und Subtypingwird nicht unterschieden. Allgemeine Probleme bei derMehrfachvererbung werden durch komplexe Mechanis-men zum Umbenennen und Uberschreiben von

”fea-

tures“ weitgehend beseitigt. Abstrakte Typen und ge-bundene Generizitat werden unterstutzt. Die wichtig-

ste Besonderheit von Eiffel ist ein ausgefeiltes Konzeptvon Zusicherungen, die sich ausreichend gut mit Sub-typing vertragen, aber zum großten Teil nur dynamischuberprufbar sind.

Das Typsystem von Eiffel gehort zu den modern-sten, die derzeit praktisch eingesetzt werden. Der se-mantischen Bedeutung eines Typs wird große Bedeu-tung beigemessen. Durch kovariante Eingangsparame-tertypen und fehlende Trennung zwischen Typen undModulen bzw. Subtyping und Vererbung werden je-doch manchmal recht diffizile Regeln notwendig, de-ren Auswirkungen f ur den Programmierer nicht immer

leicht verstandlich sind. Außerdem wird die getrenn-te Ubersetzung von Programmteilen nur unzureichendunterstutzt. Demnachst wird die Sprache standardi-siert, und der Standard enthalt zahlreiche kleine Ver-besserungen auch im Bereich des Typsystems.

5.6 Literaturhinweise

Der offizielle Sprachstandard von Ada 95 ist auch imInternet verf ugbar [3]. Daneben gibt es umfangreicheweitere Literatur daruber. Als Beispiel sei eine Arti-kelserie uber Ada in der Zeitschrift

”Communications

of the ACM“ erwahnt [28]. Eiffel ist in [19] beschrie-ben, der neue Standard in [10]. Zur Unterscheidung

Page 58: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 58/64

58 KAPITEL 5. TYPEN IN OBJEKTORIENTIERTEN SPRACHEN

zwischen Vererbung von Implementierung und Subtyp-ing gibt es eine große Zahl an Publikationen. Eine kur-ze und verstandliche Beschreibung des Problems findet

sich in [15]. Die Sprache Pordlandish wird in [25] vor-gestellt.Es gibt zahlreiche Publikationen zu den Themen

Subtyping und Vererbung. Zum Beispiel geben Liskovund Wing [16] eine aktuelle, auf Verhaltensspezifika-tionen basierende und relativ gut lesbare Definitionvon Subtyping. Viele leicht zugangliche Artikel zu die-sem Thema finden sich in der Zeitschrift

”The Journal

of Object-Oriented Programming“ und in den Procee-dings der jahrlich abgehaltenen Konferenzen ECOOP(”

European Conference on Object-Oriented Program-ming“, Proceedings meist bei Springer in der Serie

”Lecture Notes in Computer Science“ erschienen) und

OOPSLA (”

Object-Oriented Programming Systems,Languages and Applications“, meist als Oktoberaus-gabe der

”ACM SIGPLAN Notices“ erschienen).

Auch zur Vererbungsanomalie in nebenlaufigen ob- jektorientierten Sprachen wurden zahlreiche Artikelveroffentlicht. Eine gut Beschreibung des Problems

wird in [18] gegeben.Die Sprache LIFE [4] beruht auf ψ-Termen. Da diesesThema einige theoretisch interessante Probleme auf-wirft, sind die meisten Artikel dazu, wie der oben zi-tierte, sehr formal und eher schwer verstandlich.

Prozesstypen (der neueren Generation) sind z. B. in[26] beschrieben. Da es sich dabei um ein aktuelles For-schungsthema am Institut f ur Computersprachen, Ar-beitsgruppe Programmiersprachen und Ubersetzerbauhandelt, sind Sie herzlich eingeladen, sich entsprechen-de weiterf uhrende Literatur direkt dort zu besorgenoder uber ein Praktikum oder eine Diplomarbeit selbstmitzumachen.

Page 59: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 59/64

Literaturverzeichnis

[1] Martin Abadi and Luca Cardelli. A Theory of Objects. Springer, 1996.

[2] The Programming Language Ada: Reference Ma-nual. ANSI/MIL-STD-1815A-1983, American Na-tional Standards Inst., Inc., Springer, 1983.

[3] Annotated Ada Reference Manual. InternationalStandard ISO/IEC 8652:1995(E). Verf ugbar unter

”http://lglwww.epfl.ch/Ada/“.

[4] Hassan Aıt-Kaci and Andreas Podelski. Towardsa meaning of LIFE. The Journal of Logic Pro-gramming, 16:195–234, 1993.

[5] J. C. M. Baeten and W. P. Weijland. Process Al-gebra. Cambridge University Press, 1990.

[6] Luca Cardelli and Peter Wegner. On understan-ding types, data abstraction, and polymorphism.

ACM Computing Surveys, 17(4):471–522, 1985.

[7] W. F. Clocksin and C. S. Mellish. Programming in Prolog. 2nd edition, Springer, 1984.

[8] Scott Danforth and Chris Tomlinson. Type theo-ries and object-oriented programming. ACM Computing Surveys, 20(1):29–72, March 1988.

[9] H. Ehrig and B. Mahr. Fundamentals of AlgebraicSpecification 1. Springer, 1985.

[10] Standard ECMA-367. Eiffel analysis, design and

programming Language. Download: ”http://www.ecma-international.org/publications/standards/Ecma-367.htm“.

[11] Carlo Ghezzi and Mehdi Jazayeri. Programming Language Concepts. 3rd edition, Wiley, 1998.

[12] Carl A. Gunter and John C. Mitchell (eds.). Theo-retical Aspects of Object-Oriented Programming.Types, Semantics, and Language Design. The MITPress, 1994.

[13] Fritz Henglein. Type inference with polymorphicrecursion. ACM Transactions on Programming Languages and Systems, 15(2):253–289, 1993.

[14] Paul Hudak. Conception, evolution, and applica-tion of functional programming languages. ACM Computing Surveys, 21(3):360–411, 1989.

[15] Wilf LaLonde and John Pugh. Subclassing = sub-typing = is-a. Journal of Object-Oriented Pro-gramming, 3(5):57–62, January 1991.

[16] Barbara H. Liskov and Jeannette M. Wing. Abehavioral notion of subtyping. ACM Transac-tions on Programming Languages and Systems,16(6):1811–1841, November 1994.

[17] John W. Lloyd. Foundations of Logic Program-ming. 2nd extended edition, Springer, 1987.

[18] Satoshi Matsuoka and Akinori Yonezawa. Analy-sis of inheritance anomaly in object-oriented con-current programming languages. In Research Di-

rections in Concurrent Object-Oriented Program-ming , G. Agha et al. (eds.), The MIT Press, Cam-bridge, 1993.

[19] Bertrand Meyer. Eiffel: The Language. PrenticeHall, 1992.

[20] Robin Milner. Communication and Concurrency.Prentice Hall, 1989.

[21] Robin Milner. The Polyadic π-Calculus: A Tutori-al. In Logic and Algebra of Specification, pp. 203–246, Springer-Verlag, 1992.

[22] R. Milner, J. Parrow und D. Walker. A Calculusof Mobile Processes (Parts I and II). Information and Computation, Vol. 100, pp. 1–77, 1992.

[23] John C. Mitchell. Foundations for Programming Languages. The MIT Press, 1996.

[24] Frank Pfenning (editor). Types in Logic Program-ming. The MIT Press, 1992.

[25] Harry H. Porter. Separating the subtype hierarchyfrom the inheritance of implementation. Journal of Object-Oriented Programming, 4(9):20–29, Fe-bruary 1992.

Page 60: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 60/64

60 LITERATURVERZEICHNIS

[26] Franz Puntigam. Coordination Requirements Ex-pressed in Types for Active Ob jects. In Procee-dings ECOOP’97, Springer-Verlag, LNCS 1241,

Jyvaskyla, Finland, June 1997.[27] Leon Sterling and Ehud Shapiro. Prolog: fort-

geschrittene Programmiertechniken. (Deutsch)Addison-Wesley, 1988.

[28] S. Tucker Taft. Ada 9X: A technical summary.Communications of the ACM, 35(11):77–82, No-vember 1992.

[29] Peter Wegner. Concepts and paradigms of ob ject-oriented programming. OOPS Messenger, 1(1):7–87, August 1990.

Page 61: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 61/64

Glossar

abgeleiteter Typ (engl.”

derived type“). DurchTypableitung wird ein neuer Typ erzeugt, der die-selbe Struktur, aber einen anderen Namen wie einvorgegebener Typ hat. → 3.1.3, 5.1.1.

abstrakter Datentyp (engl.”

abstract data type“,abgek. ADT). Ein ADT ist ein Typ, dessen In-

stanzen abgeschlossene Einheiten sind; auf einerInstanz sind nur die vom ADT exportierten Ope-rationen anwendbar. → 1.2.2.

abstrakter Typ (engl.”

abstract type“). Ein ab-strakter Typ ist ein, im Gegensatz zu einem kon-kreten Typ, unvollstandig spezifizierter Typ. →1.2.2, 5.1.1.

Attribut (engl.”

attribute“). In vielen objektorien-tierten Sprachen versteht man unter einem Attri-but den Namen einer Komponente zur Beschrei-bung des Objektzustands; oft ist das einfach derName einer Verbundkomponente. In Ada ist einAttribut eine einem Typ oder Wert zugeordneteFunktion. → 3.1.1.

Aufzahlungstyp (engl.”

enumeration type“). EinAufzahlungstyp beschreibt eine geordnete, end-liche Menge nichtstrukturierter Werte. Beispielesind Wahrheitswerte (engl.

”Boolean“) und Zei-

chen (engl.”

character“). → 3.1.1.

Ausnahmebehandlung (engl.”

exception hand-ling“). Das ist die Ausf uhrung eines speziell daf urvorgesehenen Programmstuckes beim Auftreteneiner Ausnahmesituation wahrend der Program-

mausf uhrung. Eine solche Ausnahmesituation istz. B. das Erkennen eines Typfehlers zur Laufzeit.

definite Horn-Klausel (engl.”

definite horn clau-se“). Ein Ausdruck in der von Prolog verwendetenUntermenge der Pradikatenlogik. → 2.2.2.

diskreter Typ (engl.”

discrete type“). Zu den dis-kreten Typen zahlen die Typen von ganzer Zahlenund Aufzahlungen. Instanzen diskreter Typen eig-nen sich zur Indizierung von Feldern. → 3.1.

Diskriminante (engl.”

discriminant part“). Das istin Ada eine spezielle Komponente eines Verbunds,die dazu beitragt, die Struktur des entsprechendenVerbundtyps zu bestimmen. → 3.1.2.

dynamisches Binden (engl.”

dynamic binding“).Es wird erst zur Laufzeit festgelegt, welche Rou-tine an einen Aufruf

”gebunden“ wird. Gegenteil:

statisches Binden. → 1.2.3.

Einfach-Binden (engl.”

single dispatching“). DieAuswahl der auszuf uhrenden Routine erfolgt uber

den dynamischen Typ eines einzigen Parameters.Gegenteil: Mehrfach-Binden. → 5.2.1.

Ersetzbarkeit (engl.”

substitutability“). Prinzip derErsetzbarkeit:

”Eine Instanz eines Untertyps kann

uberall dort verwendet werden, wo eine Instanzeines entsprechenden Obertyps erwartet wird.“ →4.3.1, 5.2.1.

Feld (engl.”

array“). Das ist eine indizierte Mengevon Werten oder Objekten gleichen Typs. Die In-dexmenge ist durch einen diskreten Typ beschrie-ben. → 3.1.2.

freie Algebra (engl. ”free algebra“). Das ist eineAlgebra, in der neben den durch eine Menge ∆ be-schriebenen und aus ∆ ableitbaren Gesetzen keineweiteren Gesetze gelten. → 2.3.

generischer Typ (engl.”

generic type“). Siehe”

pa-rametrischer Typ“.

heterogene Algebra (engl.”

multi-sorted algebra“).Das ist eine mathematische Struktur bestehendaus einem Tupel von durch Sorten benanntenTragermengen und einem System von Operatio-nen darauf. → 2.3.

indiziert (engl.”

indexed“ oder”

labeled“). Mit einemIndex bzw.

”label“ versehen. Z. B. sind die Kom-

ponenten eines Verbunds mit den Komponenten-namen indiziert.

Instanz (engl.”

instance“). Ein durch einen Typbeschriebenes Element (Wert oder Objekt) ist eineInstanz dieses Typs.

Invariante (engl.”

invariant“). Eine formal festge-legte Bedingung, die wahrend der Ausf uhrung ei-nes bestimmten Programmstuckes stets erf ullt seinmuss. Meist wird nur gefordert, dass eine Invari-ante am Beginn und Ende der Ausf uhrung einesProgrammstucks erf ullt ist.

61

Page 62: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 62/64

62 GLOSSAR

klassenweiter Typ (engl.”

class-wide type“). InAda: Der klassenweite Typ t’Class eines erweiter-baren Typs t umfasst alle Instanzen von t und der

von t abgeleiteten Typen. Gegenteil: spezifischerTyp. → 5.1.1.

Lambda-Kalkul (engl.”

lambda-calculus“). Der λ-Kalkul (λ ist der griechische Buchstabe

”Lamb-

da“) ist ein formales Modell zur Beschreibung vonFunktionen. → 2.1.

Mehrfach-Binden (engl.”

multiple dispatching“).Die Auswahl der auszuf uhrenden Routine erfolgtuber die dynamischen Typen mehrerer Argumen-te. Gegenteil: Einfach-Binden. → 5.2.1.

monomorphes Typsystem (engl. ”monomorphictype system“). Ein monomorphes Typsystem istein Typsystem, in dem jeder Wert bzw. jedes Ob- jekt eine Instanz von genau einem Typ ist. Gegen-teil: polymorphes Typsystem. → 1.2.3.

Nachbedingung (engl.”

postcondition“). Das isteine Zusicherung, die nach der Ausf uhrung einerbestimmten Operation erf ullt sein muss. → 5.1.2.

Obertyp (engl.”

supertype“). Ein Typ, der ei-ne Obermenge der Instanzen eines entsprechendenUntertyps beschreibt; der Obertyp ist allgemei-ner als der Untertyp. Fur t ist ein Obertyp vons schreiben wir s ≤ t.

Objekt (engl.”

object“). Ein Objekt ist eine abge-schlossene Einheit mit eigenstandiger Identitat, ei-nem (veranderlichen) Zustand und einem (manch-mal veranderlichen) Verhalten. Ein passives

Objekt wird meist als Verbund dargestellt; dasVerhalten wird durch eine Menge von Methoden(Routinen) auf diesem Objekt beschrieben. Einaktives Objekt entspricht einem Prozess; dasVerhalten wird durch das vom Prozess ausgef uhrte

Programmstuck beschrieben.

parametrischer Typ (engl.”

parametric type“, auchgenerischer Typ genannt). Das ist ein Typ bzw.Typausdruck, in dem Typparameter verwendetwerden, welche durch Typausdrucke ersetzt wer-den konnen. Ein gebundener parametrischer

Typ schrankt die Menge der Typausdrucke, dief ur Typparameter eingesetzt werden konnen, ein.→ 1.2.3, 2.3.1, 3.3, 4.2, 5.2.4.

PER-Modell. Ein auf partiellen Aquivalenzrelatio-nen (engl.

”partial equivalence relation“ = PER)

beruhendes semantisches Modell f ur Typen in ob- jektorientierten Sprachen. → 4.4.

polymorphes Typsystem (engl.”

polymorphic ty-pe system“). In einem polymorphen Typsystemkann jeder Wert bzw. jedes Objekt eine Instanz

von mehr als nur einem Typ sein. Die Operatio-nen polymorpher Typen sind auf Operanden meh-rerer Typen anwendbar. Gegenteil: MonomorphesTypsystem. → 1.2.3, 4, 5.

Prozessalgebra (engl.”

process algebra“). Das isteine Algebra, die dazu geeignet ist, das Verhaltennebenlaufiger (paralleler) Prozesse zu beschreiben.→ 2.3.2.

Prozesstyp (engl.”

process type“). Allgemein: DerTyp eines Prozesses (z.B. in Ada). Speziell: EinTyp, der das dynamische Verhalten eines Prozes-ses partiell beschreibt. → 3.2.2, 5.4.1.

Psi-Term (engl.”

ψ-term“). Term in der logischenProgrammiersprache LIFE. → 5.3.

Signatur (engl.”

signature“). Eine Signatur istdie Schnittstellenbeschreibung einer Routine oderKlasse in einer Programmiersprache bzw. einerOperation in einer Algebra. → 2.3.

skalarer Typ (engl.”

scalar type“). Ein skalarerTyp beschreibt eine Menge von Werten ohne sicht-bare innere Struktur und die auf diesen Wertenausf uhrbaren Operationen. → 3.1.1.

spezifischer Typ (engl. ”specific type“). In Ada:Ein (nicht klassenweiter, erweiterbarer) Typ. Ge-genteil: klassenweiter Typ. → 5.1.1.

statisches Binden (engl.”

static binding“). Es wirdbereits vom Ubersetzer festgelegt, welche Funktionoder Prozedur an einen Aufruf

”gebunden“ wird.

Gegenteil: dynamisches Binden. → 1.2.3.

Subtyping. Ein Konzept eines polymorphen Typsy-stems, in dem ein Untertyp eine Teilmenge der In-stanzen beschreibt, die durch einen entsprechen-den Obertyp beschrieben sind. → 1.2.3, 4, 5.

Typ (engl.”

type“). Allgemeine Definition: Ein Typbeschreibt eine Menge von Instanzen. Es gibt zahl-reiche unterschiedliche konkretere Definitionen. Inobjektorientierten Sprachen: Ein Typ ist eine Spe-zifikation der Schnittstelle und (in vielen F allen)des nach außen sichtbaren Verhaltens seiner In-stanzen (= Objekte). → 1.1, 5.2.2.

Typaquivalenz (engl.”

type equivalence“). Dasist eine Relation, die angibt, ob je zwei Ty-pen als gleich betrachtet werden, d. h. aquivalentsind. Typaquivalenz kann auf Namensgleichheit(zwei Typen haben denselben Namen) oder Struk-turgleichheit (zwei Typausdrucke haben dieselbeStruktur) beruhen. → 1.2.2.

Page 63: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 63/64

GLOSSAR 63

Typerweiterung (engl.”

type extension“). In Ada:Die Erweiterung eines erweiterbaren (

”tagged“)

Verbundtyps um neue Verbundkomponenten. →

5.1.1.

Typfehler (engl.”

type error“). Ein Typfehler liegtvor, wenn ein Ausdruck mehrere Typen hat, dienicht zusammenpassen. Zum Beispiel, wenn maneiner Funktion vom Typ Integer → Integer dasArgument true vom Typ Boolean ubergibt, be-kommt true die nicht zusammenpassenden TypenInteger und Boolean.

Typinferenz (engl.”

type inference“). Typinferenzist der Vorgang, bei dem aus einem Programm derallgemeinste Typ jedes Ausdrucks errechnet wird.

→ 4.2.

typisierte Sprache (engl.”

typed language“). Ei-ne Sprache ist typisiert wenn es in der Spra-che Typen gibt; sonst ist sie untypisiert. Sieist vollstandig typisiert wenn jeder Ausdruckspatestens zur Laufzeit auf Typkonsistenz uber-pruft werden kann. Eine Sprache ist stark ty-

pisiert (engl.”

strongly typed“) wenn alle mogli-chen Typfehler bereits wahrend der Ubersetzungerkannt werden konnen; sonst ist sie schwach

typisiert (engl.”

weakly typed“). Eine typisierteSprache ist statisch typisiert (engl.

statically

typed“) wenn der Typ jedes in einem Programmvorkommenden Ausdrucks bereits bei der Uber-setzung bekannt ist; sonst heisst sie dynamisch

typisiert (engl.”

dynamically typed“). → 1.2.2.

typisierte Variable (engl.”

typed variable“). Ei-ne Variable ist typisiert, wenn auf ihr eine Typ-einschrankung definiert ist. Der deklarierte Typ

einer Variable ist jene Typeinschrankung, die ex-plizit im Programmtext steht. Der statische Typ

ist jene Typeinschrankung, die bei einer statischenProgrammanalyse erwittelt wird. Der dynami-

sche Typist der Typ des zur Laufzeit in der Va-riable enthaltenen Wertes.

Typkonsistenz (engl.”

type consistency“). Das istdie Eigenschaft eines Programms, frei von Typfeh-lern zu sein. → 1.2.2.

Typqualifikation (engl.”

type qualification“).Ada: Durch Typqualifikation wird der Typ einesWertes angegeben; dadurch werden Namenskon-flikte aufgelost. → 3.1.1.

Typsystem (engl.”

type system“). Ein Typsystemist eine Menge von Regeln, die Typen zueinanderund zu Ausdrucken in einer Programmiersprachein Beziehung setzen.

Typuberprufung (engl.”

type checking“). Das ist jener Vorgang, bei dem ein Programm auf Typkon-sistenz uberpruft wird. Die dynamische Typ-

uberprufung erfolgt zur Laufzeit; die statischeTypuberprufung zur Compilezeit. Eine Typ-uberprufung ist konservativ wenn nur garantierttypkonsistente Programme akzeptiert werden; sieist optimistisch wenn nur sicher nicht typkon-sistente Programme zuruckgeworfen werden. →1.2.2.

Typumwandlung (engl.”

type coercion“). DurchTypumwandlung wird ein Argument einer Routi-ne automatisch in ein Argument jenes Typs um-gewandelt, der von der Routine erwartet wird. →1.2.3.

Uberladen (engl.”

overloading“). Ein und dersel-be Name kann verschiedene Routinen beschreiben,die sich nur durch die Typen der Parameter unter-scheiden. → 1.2.3.

Uberschreiben (engl.”

overwriting“). Das Ersetzeneiner ererbten Operation durch eine neue Opera-tion. → 5.1.1.

universelle Algebra (engl.”

universal algebra“ oder

”single-sorted algebra“). Das ist eine mathema-

tische Struktur bestehend aus einer Tr agermenge

und einem System von Operationen . Die Operatio-nen sind durch eine Menge von Gesetzen beschrie-ben. → 2.3.

Unterbereichstyp (in Ada engl.”

subtype“; Vor-sicht:

”subtype“ steht ublicherweise f ur

”Unter-

typ“). Ein Unterbereichstyp enthalt zusatzlicheEinschrankungen gegenuber dem Typ, dessen Un-terbereichstyp er ist. Es konnen Wertebereicheund Genauigkeiten eingeschrankt bzw. bei FeldernBereichsangaben und bei Verbunden Diskriminan-ten festgelegt werden. → 3.1.3.

Untertyp (engl. ”subtype“). Ein Typ, der eine Teil-menge der Instanzen eines entsprechenden Ober-typs beschreibt; der Untertyp ist spezifischer alsder Obertyp. Fur s ist ein Untertyp von t schrei-ben wir s ≤ t.

Variante (engl.”

variant“). Eine Variante ist eineInstanz eines Variantentyps. → 2.1.3.

Variantentyp (engl.”

variant type“). Ein Varianten-typ ist eine Menge indizierter Typen. Jede Instanzhat genau einen Typ in dieser Menge. → 2.1.3.

Varianz (engl.”

variance“). Die Varianz einesParametertyps einer Routine, die zu einem Ob- jekt gehort, beschreibt, wie sich der Parametertyp

Page 64: Skrip Tum 05

5/16/2018 Skrip Tum 05 - slidepdf.com

http://slidepdf.com/reader/full/skrip-tum-05 64/64

64 GLOSSAR

beim Ersetzen des Objekts durch ein Objekt ei-nes entsprechenden Untertyps verhalt. Bei Kova-

rianz (engl.”

covariance“) wird der Parametertyp

spezifischer, bei Kontravarianz (engl. ”contrava-riance“) allgemeiner und bei Invarianz (engl.”

in-variance“) bleibt er unverandert. → 5.2.1.

Verband (engl.”

lattice“). Ein Verband ist eine alge-braische Struktur in der auf einer Menge eine re-flexive, transitive und antisymmetrische Relationdefiniert ist, so dass je zwei Elemente der Mengeein eindeutig definiertes Supremum und Infimum haben. → 4.1.1.

Verbund (engl.”

record“). Ein Verbund ist eine In-stanz eines Verbundtyps. → 2.1.3.

Verbundtyp (engl.”

record type“). Ein Verbundtypist eine Menge indizierter Typen. Jede Instanz desVerbundtyps ist eine Menge indizierter Instanzender entsprechend indizierten Typen im Verbund-typ. → 2.1.3.

Vererbung (engl.”

inheritance“). Vererbung ist einMechanismus, der die Erstellung von Programm-teilen durch gezielte Modifikation bereits existie-

render Programmteile unterstutzt. → 5.2.2.

Vererbungsanomalie (engl.”

inheritance anoma-ly“). Das ist die Bezeichnung eines durch die Not-wendigkeit von Synchronisationscode verursachtenProblems, welches die Vererbung in nebenlaufigenobjektorientierten Sprachen behindert. → 5.4.2.

Vorbedingung (engl.”

precondition“). Das ist ei-ne Zusicherung, die vor der Ausf uhrung einer be-stimmten Operation erf ullt sein muss. → 5.1.2.

Wert (engl.”

value“). Ein Wert ist ein Symbol oder ei-ne aus Werten konstruierte Struktur, das bzw. diekeinen inneren Zustand besitzt. Zwei Werte sindgenau dann gleich, wenn sie dasselbe Symbol bzw.dieselbe Struktur darstellen.

Zusicherung (engl.”

assertion“). Eine Zusicherungist eine formal festgelegte Bedingung, die bei derAusf uhrung eines festgelegten Programmstuckserf ullt sein muss. → 5.1.2.