Abstrakte Datentypen II

Ähnliche Dokumente
Abstrakte Datentypen I

Programmierkurs II. Typsynonyme & algebraische Datentypen

Algebraische Datentypen

ALP I. Funktionale Programmierung

Funktionale Programmierung ALP I. Algebraische Datentypen und Abstrakte Datentypen. SS 2013 Prof. Dr. Margarita Esponda. Prof. Dr.

Programmieren in Haskell Programmiermethodik

Programmieren in Haskell Abstrakte Datentypen

WS 2011/2012. Georg Sauthoff 1. November 11, 2011

Gliederung. Algorithmen und Datenstrukturen I. Binäre Bäume: ADT Heap. inäre Bäume: ADT Heap. Abstrakte Datentypen IV. D. Rösner

Programmieren in Haskell Einstieg in Haskell

Gliederung. Algorithmen und Datenstrukturen I. Binäre Bäume: AVL-Bäume. inäre Bäume: AVL Bäume. Abstrakte Datentypen V: AVL Bäume. D.

Programmieren in Haskell

Programmieren in Haskell. Abstrakte Datentypen

Programmieren in Haskell

Einfache binäre Suchbäume können entarten, so dass sich die Tiefen ihrer Blattknoten stark unterscheiden

HASKELL KAPITEL 8. Bäume

Typklassen. Natascha Widder

Typ-Polymorphismus. November 12, 2014

Haskell Seminar Abstrakte Datentypen. Nils Bardenhagen ms2725

9 Algebraische Datentypen

Algorithmen und Programmieren 1 Funktionale Programmierung - Musterlösung zu Übung 9 -

Vorlesung Datenstrukturen

3. Funktionales Programmieren 3.2 Algorithmen auf Listen und Bäumen. Heapsort verfeinert die Idee des Sortierens durch Auswahl:

Agenda. 1 Einleitung. 2 Binäre Bäume. 3 Binäre Suchbäume. 4 Rose Trees. 5 Zusammenfassung & Ausblick. Haskell Bäume. Einleitung.

Programmieren in Haskell

Wiederholung. Bäume sind zyklenfrei. Rekursive Definition: Baum = Wurzelknoten + disjunkte Menge von Kindbäumen.

Bemerkung: Heapsort. Begriffsklärung: (zu Bäumen) Begriffsklärung: (zu Bäumen) (2) Heapsort verfeinert die Idee des Sortierens durch Auswahl:

13 Abstrakte Datentypen

Vorlesung Informatik 2 Algorithmen und Datenstrukturen

Algorithmen und Datenstrukturen 2. Dynamische Datenstrukturen

Datenstrukturen und Algorithmen. Vorlesung 14

Invarianzeigenschaft (für binären Suchbaum)

2.2.1 Algebraische Spezifikation (Fortsetzung)

Algorithmen und Programmieren 1 Funktionale Programmierung - Musterlösung zur Übungsklausur -

Programmieren in Haskell

Informatik II Bäume zum effizienten Information Retrieval

Alexander Nutz. 3. Dezember 2007

2.3 Spezifikation von Abstrakten Datentypen

Algorithmen und Datenstrukturen. Bäume. M. Herpers, Y. Jung, P. Klingebiel

Einführung in die Funktionale Programmierung mit Haskell

Programmieren in Haskell

ADS 1: Algorithmen und Datenstrukturen

Datenstrukturen Teil 3. Traversierung und AVL- Bäume. Traversierung. Traversierung. Traversierung

Praktische Informatik 3: Funktionale Programmierung Vorlesung 4 vom : Typvariablen und Polymorphie

WS 2011/2012. Robert Giegerich. October 30, 2013

WS 2012/2013. Robert Giegerich. January 22, 2014

Geheimnisprinzip: (information hiding principle, Parnas 1972)

Praktische Informatik 3: Funktionale Programmierung Vorlesung 3 vom : Algebraische Datentypen

Proinformatik Marco Block Dienstag, den 21. Juli 2009

Bäume. Martin Wirsing. Ziele. Implementierung von Knoten. Bäume (abstrakt) Standardimplementierungen für Bäume kennen lernen

Bäume. Prof. Dr. Christian Böhm. in Zusammenarbeit mit Gefei Zhang. WS 07/08

Fahrplan. Algebraische Datentypen. Inhalt. Der Allgemeine Fall: Algebraische Datentypen. Algebraische Datentypen: Nomenklatur

expr :: Expr expr = Mul (Add (Const 3) (Const 4)) (Div (Sub (Const 73) (Const 37)) (Const 6))

Programmieren in Haskell Das Haskell Typsystem

Programmieren in Haskell. Stefan Janssen. Strukturelle Rekursion. Universität Bielefeld AG Praktische Informatik. 10.

Einführung in die funktionale Programmierung

1 AVL-Bäume. 1.1 Aufgabentyp. 1.2 Überblick. 1.3 Grundidee

7. Dynamische Datenstrukturen Bäume. Informatik II für Verkehrsingenieure

Kapitel 12: Induktive

Wiederholung ADT Menge Ziel: Verwaltung (Finden, Einfügen, Entfernen) einer Menge von Elementen

12. Dynamische Datenstrukturen

10 Abstrakte Datentypen

Software Entwicklung 1

Physikalisch Technische Lehranstalt Wedel 31. Januar 2004 Prof. Dr. Uwe Schmidt

Übung zur Vorlesung Algorithmische Geometrie

Beispiellösungen zu den Übungen Datenstrukturen und Algorithmen SS 2008 Blatt 6

Praktische Informatik 3: Funktionale Programmierung Vorlesung 4 vom : Typvariablen und Polymorphie

Prof. Dr. Uwe Schmidt. 30. Januar 2017

5. Januar Universität Bielefeld AG Praktische Informatik. Programmieren in Haskell. Stefan Janssen. Abstrakte Datentypen.

Paradigmen der Programmierung

Binäre Suchbäume. Mengen, Funktionalität, Binäre Suchbäume, Heaps, Treaps

Grundlegende Datentypen

KIV-Dokumentation. Buchholz, Stefan und Winkler, Jörg 25. Februar 2005

4.1 Bäume, Datenstrukturen und Algorithmen. Zunächst führen wir Graphen ein. Die einfachste Vorstellung ist, dass ein Graph gegeben ist als

Algorithmen und Datenstrukturen 1

Tutoraufgabe 1 (Implementierung eines ADTs):

Binäre Suchbäume. Mengen, Funktionalität, Binäre Suchbäume, Heaps, Treaps

Copyright, Page 1 of 8 AVL-Baum

Grundlegende Datentypen

B-Bäume. Suchbäume. Suchen, Einfügen, Löschen. Thomas Röfer. Balancierte Bäume (AVL-Bäume) Universität Bremen. Bäume 2

Praktische Informatik 3: Funktionale Programmierung Vorlesung 6 vom : Funktionen Höherer Ordnung II und Effizienzaspekte

Funktionale Programmierung Grundlegende Datentypen

5 Bäume. 5.1 Suchbäume. ein geordneter binärer Wurzelbaum. geordnete Schlüsselwertmenge. heißt (schwach) sortiert, g.d.w. gilt:

Einführung in die Informatik 2 9. Übung

Binäre Suchbäume. Organisatorisches. VL-10: Binäre Suchbäume. (Datenstrukturen und Algorithmen, SS 2017) Gerhard Woeginger.

Programmierkurs II. Mengen ein weiteres Beispiel

Programmierung und Modellierung

2.7 Bucket-Sort Bucket-Sort ist ein nicht-vergleichsbasiertes Sortierverfahren. Hier können z.b. n Schlüssel aus

Balancierte Bäume. Minimale Knotenanzahl von AVL-Bäumen. AVL-Bäume. Definition für "balanciert":

Frage, Fragen und nochmals Fragen

Anwendungsbeispiel MinHeap

Das Interface-Konzept am Beispiel der Sprache Java

WS 2011/2012. Georg Sauthoff 1. October 26, 2011

WS 2011/2012. Robert Giegerich Dezember 2013

B6.1 Introduction. Algorithmen und Datenstrukturen. Algorithmen und Datenstrukturen. B6.1 Introduction. B6.3 Analyse. B6.4 Ordnungsbasierte Methoden

Typklassen und Überladung in Haskell

Transkript:

7 Abstrakte Datentypen II Nachdem wir im letzten Kapitel die grundlegenden Eigenschaften abstrakter Datentypen beschrieben haben und dabei eher einfache Beispiele betrachtet haben, soll es in diesem Kapitel um Beispiele gehen, die für das praktische Programmieren von großer Bedeutung sind: Mengen, Felder und... 7.1 Mengen als abstrakte Datentypen Stapel und Schlange sind gut und schön, aber auch sehr einfach. Ein etwas nützlicherer abstrakter Datentyp ist die Menge, also eine Ansammlung von Elementen, in der kein Element doppelt auftritt und die Ordnung der Elemente nicht relevant ist. Als Basis-Operationen auf Mengen betrachten wir: die leere Menge, das Einfügen eines Elements, das Löschen eines Elements, das Leerheits-Prädikat, das Enthaltenseins-Prädikat, und eine Operation, die die Elemente einer Menge als Liste aufzählt. Damit lassen sich dann weitere Operationen, wie Vereinigung, Durchschnitt und Mengendifferenz, Teilmengen-Prädikat usw. leicht definieren. Mengen können als ein Typ Set a definiert werden, dessen Elemente mindestens vergleichbar sein müssen (um doppelte Einträge festzustellen). Für effizientere Implementierungen von Mengen werden wir dann geordnete Elementtypen brauchen. Die Schnittstelle des abstrakten Datentyps könnte so aussehen: module Set (Set, -- abstract datatype empty, -- Set a isempty, -- Set a-> Bool insert, delete, contains -- Ord a => a-> Set a-> Bool

96 7. Abstrakte Datentypen II enumeration ) where -- Set a-> [a] 7.1.1 Mengen als Listen Die einfachste Implementierung von Mengen sind sicher die Listen. Doppelte Einträge werden beim Einfügen durch Aufruf der vordefinierten Funktion nub::eq a [a] [a] entfernt, die aus der Bibliothek List importiert werden muss. Die anderen Mengenoperationen können eins zu eins durch entsprechende Konstruktoren und vordefinierte Funktionen auf Listen implementiert werden. Die Operation(\\) :: (Eq a) => [a] -> [a] -> [a] implementiert Listendifferenz; l 1 \\ l 2 entfernt also jeweils ein Vorkommen jedes Elements von l 2 aus l 1. 1 import List (intersperse, nub, (\\)) newtype Set a = Set [a] empty :: Set a empty = Set [] isempty :: Set a Bool isempty (Set xs) = null xs insert :: Eq a Set a a Set a insert (Set xs) x = Set (nub (x: xs)) delete :: Eq a Set a a Set a delete (Set xs) x = Set (xs \\ [x]) contains :: Eq a Set a a Bool contains (Set xs) x = x elem xs enumeration :: Set a [a] enumeration (Set xs) = xs Diese Implementierung ist nicht effizient, denn insert, delete und contains haben linearen Aufwand. 7.1.2 Mengen als geordnete Bäume Eine etwas bessere Implementierung wären die geordneten Bäume, die wir in Abschnitt 6.2 entwickelt haben. 1 Statt des Schlüsselworts newtype können wir hier data lesen; genaueres hierzu steht in Abschnitt 7.3.1.

7.1. Mengen als abstrakte Datentypen 97 Dazu importieren wir OrderedTree. Weil dieses Modul eine Ordnung auf den Elementen braucht, müssen wir das auch für den Modul SetAsOrderedTree fordern module SetAsOrderedTree (Set, -- an abstract type empty, -- Set a isempty, -- Set a-> Bool insert, delete, contains, -- Ord a => Set a-> a-> Bool enumeration, -- Set a-> [a] ) where import qualified OrderedTree as T Wir implementieren den Modul qualifiziert, so dass seine exportierten Namen mit dem Modulnamen qualifiziert werden müssen, weil sonst Namenskonflikte entstehen würden. Wir benennen OrderedTree in T um, damit die Namen handlich bleiben. Wir definieren Set wieder als newtype und können danach die Operationen für geordnete Bäume eins zu eins übernehmen: newtype Ord a Set a = Set (T.Tree a) empty = Set T.empty isempty (Set t) = T.isEmpty t insert (Set t) x = Set (T.insert t x) delete (Set t) x = Set (T.delete t x) contains (Set t) x = T.contains t x enumeration (Set t) = T.enumeration t instance (Ord a,show a) Show (Set a) where show t = "{" ++ concat (intersperse "," elements) ++ "}" where elements :: [String] elements = [show x x enumeration t] Wir bilden eine Instanz von Show, damit Mengen zünftig als Zeichenketten dargestellt werden können. Geordnete Bäume führen dazu, dass die Operationen insert, delete und contains im Mittel den Aufwand O(log n) haben, nämlich dann, wenn alle Knoten im Baum annähernd gleich hohe Teilbäume haben. Werden die Elemente aber in einer ungünstigen Reihenfolge eingefügt, kann der Baum zu einer Liste degenerieren, in dem die linken bzw. rechten Teilbäume der Knoten jeweils leer sind. (Das passiert, wenn die Elemente geordnet bzw. umgekehrt

98 7. Abstrakte Datentypen II geordnet eingefügt werden.) Im schlechtesten Fall haben die Operationen dann doch wieder linearen Aufwand. Im nächsten Abschnitt werden wir das weiter verbessern. 7.1.3 AVL-Bäume Das Degenerieren von geordneten Bäumen zu Listen kann verhindert werden, wenn die Knoten des Baumes nötigenfalls balanciert werden. Wir entwickeln eine Implementierung von AVL-Bäumen, die nach Adelson-Velskii und Landis [AVL62] benannt sind. An der Schnittstelle ändert sich bis auf die Namen des Moduls und des Typs nichts: module AVLTree (AVLTree, empty, -- AVLTree a isempty, -- AVLTree a-> Bool insert, delete, contains, -- Ord a => a-> AVLTree a-> Bool enumeration -- AVLTree a-> [a] ) where Ein AVL-Baum ist ausgeglichen, wenn der Höhenunterschied zwischen den zwei Unterbäumen eines Knotens höchstens eins beträgt und diese Unterbäume selbst auch ausgeglichen sind. Für alle Knoten n =Node l a r in so einem Baum gilt also die Invariante balanced n height l height r 1 balanced l balanced r Weil die Höhe eines Baums an vielen Stellen gebraucht wird, wird sie in jedem Knoten abgespeichert. data AVLTree a = Null Node Int (AVLTree a) a (AVLTree a) deriving Eq Alle Operationen müssen Ausgeglichenheit als Invariante bewahren. Beim Einfügen und Löschen müssen dazu ggf. Unterbäume rotiert werden. Zunächst jedoch die weitgehend unveränderten Operationen: die leere Menge, Leerheits- und Enthaltenseins-Prädikat und die Funktion zur Aufzählung der Elemente. empty :: AVLTree a empty = Null isempty :: AVLTree a Bool isempty Null = True isempty _ = False contains :: Ord a AVLTree a a Bool contains Null e = False

7.1. Mengen als abstrakte Datentypen 99 contains (Node _ l a r) e = a == e e < a && contains l e e > a && contains r e enumeration :: Ord a AVLTree a [a] enumeration Null = [] enumeration (Node _ l a r) = enumeration l ++ [a] ++ enumeration r Für die restlichen Operationen brauchen wir einige Hilfsfunktionen. Die Funktion mknode legt einen Knoten an und setzt seine Höhe mit der Funktion ht, die die Höhe nicht rekursiv berechnet, sondern auf den in den Wurzelknoten der Unterbäume gespeicherten Wert zugreift. mknode :: AVLTree a a AVLTree a AVLTree a mknode l n r = Node h l n r where h = 1+ max (ht l) (ht r) ht :: AVLTree a Int ht Null = 0 ht (Node h _) = h 2 Abb. 7.1. ein nicht ausgeglichener Baum Abbildung 7.1 stellt eine Situation dar, in der ein Baum nicht ausgeglichen ist. Das kann durch Löschen oder Einfügen passieren. Dann muss die Ausgeglichenheit durch Rotieren der Unterbäume wiederhergestellt werden. Dafür gibt es zwei Basisoperationen, Linksrotation und Rechtsrotation, die in den Abbildungen 7.2 und 7.3 illustriert sind. rotl :: AVLTree a AVLTree a rotl (Node _ xt y (Node _ yt x zt)) = mknode (mknode xt y yt) x zt rotr :: AVLTree a AVLTree a rotr (Node _ (Node _ xt y yt) x zt) = mknode xt y (mknode yt x zt) Die Funktionen rotl und rotr müssen dazu benutzt werden, Ausgeglichenheit sicherzustellen. Folgende Fälle müssen unterschieden werden:

100 7. Abstrakte Datentypen II zt xt yt zt xt yt Abb. 7.2. Linksrotation zt xt xt yt yt zt Abb. 7.3. Rechtsrotation 1. Der Unterbaum rechts außen ist zu hoch. Dann hilft einfache Linksrotation, siehe Abbildung 7.4. 2. Der Unterbaum rechts innen ist zu hoch oder gleich hoch wie der rechts außen. Dann muss der rechte Unterbaum durch Rechtsrotation ausgeglichen werden, bevor der Baum selber durch Linksrotation ausgeglichen werden kann. siehe Abbildung 7.5. Die spiegelbildlichen Fälle 3 und 4, in denen der linke Baum zu hoch ist, können spiegelbildlich durch Rechtsrotation gelöst werden, wobei vorher der linke Unterbaum links rotiert werden muss, wenn dessen innerer Unterbaum zu hoch war. Wir brauchen noch zwei weitere Hilfsfunktionen, um die Ausgeglichenheit sicherzustellen: bias t berechnet die Balance eines Baumes t, und mkavl xt y zt konstruiert neuen AVL-Baum mit Knoten y (unter der Voraussetzung, dass der Höhenunterschied zwischen xt und zt höchstens zwei beträgt). Dabei müssen folgende Fälle unterschieden werden: Rechter Teilbaum zu hoch: Fall 1: bias< 0

7.1. Mengen als abstrakte Datentypen 101 2 Abb. 7.4. Fall 1. Unterbaum rechts außen zu hoch... 2 2 Abb. 7.5. Fall 2. Unterbaum rechts innen zu hoch Fall 2: bias 0 Linker Teilbaum zu hoch: Fall 1: bias> 0 Fall 2: bias 0 bias :: AVLTree a Int bias (Node _ lt _ rt) = ht lt - ht rt mkavl :: AVLTree a a AVLTree a AVLTree a mkavl xt y zt hx+1< hz && 0 bias zt = rotl (mknode xt y (rotr zt)) hx+1< hz = rotl (mknode xt y zt) hz+1< hx && bias xt 0 = rotr (mknode (rotl xt) y zt) hz+1< hx = rotr (mknode xt y zt) otherwise = mknode xt y zt where hx= ht xt; hz= ht zt Nun können wir Einfügen und Löschen implementieren, wobei ggf. rotiert wird. insert :: Ord a AVLTree a a AVLTree a

102 7. Abstrakte Datentypen II insert Null a = mknode Null a Null insert (Node n l a r) b b < a = mkavl (insert l b) a r b == a = Node n l a r b > a = mkavl l a (insert r b) delete :: Ord a AVLTree a a AVLTree a delete Null x = Null delete (Node h l y r) x x < y = mkavl (delete l x) y r x == y = join l r x > y = mkavl l y (delete r x) Die Verwendung mkavl garantiert die Invariante (Ausgeglichenheit). Die Hilfsfunktion join fügt zwei Bäume ordnungserhaltend zusammen, wie wir es schon bei geordneten Bäumen im vorigen Abschnitt gesehen haben. join :: AVLTree a AVLTree a AVLTree a join xt Null = xt join xt yt = mkavl xt u nu where (u, nu) = splittree yt splittree :: AVLTree a (a, AVLTree a) splittree (Node h Null a t) = (a, t) splittree (Node h lt a rt) = (u, mkavl nu a rt) where (u, nu) = splittree lt Beispiel 7.1 (Strukturelle Rekursion über AVL-Bäumen). Auch über AVL- Bäumen kann die primitive Rekursion als Kombinator extrahiert werden: foldavl :: (Int b a b b) b AVLTree a b foldavl f e Null = e foldavl f e (Node h l a r) = f h (foldavl f e l) a (foldavl f e r) Das Aufzählen der Menge kann dann als Inorder-Traversion (via fold) definiert werden. enum :: Ord a AVLTree a [a] enum = foldavl (\h t1 x t2 t1++ [x]++ t2) [] Das Enthaltensein- Prädikat kann ebenfalls mit fold definiert werden. elem :: Ord a a AVLTree a Bool elem e = foldavl (\h b1 x b2 e == x b1 b2) False

7.2. Abschließende Bemerkungen 103 7.1.4 Mengen als AVL-Bäume Nun können wir Mengen als AVL-Bäume realisieren. module SetAsAVLTree (Set, -- an abstract type empty, -- => Set a isempty, -- => Set a-> Bool insert, delete, contains, -- Ord a => Set a-> a-> Bool enumeration, -- => Set a-> [a] ) where import qualified AVLTree as A Auch hier wird das Modul AVLTree qualifiziert importiert und umbenannt, und Set als newtype definiert: newtype Ord a Set a = Set (A.AVLTree a) empty = Set A.empty isempty (Set t) = A.isEmpty t insert (Set t) x = Set (A.insert t x) delete (Set t) x = Set (A.delete t x) contains (Set t) x = A.contains t x enumeration (Set t) = A.enumeration t 7.2 Abschließende Bemerkungen Auch die meisten Module in der Haskell-Bibliothek definieren abstrakte Datentypen. Das gilt auch für vordefinierte Zahlentypen, denn die interne Darstellung der Zahlenwerte wird verborgen und wird allein über die auf ihnen definierten Operationen definiert: Ihre Konstruktoren sind einerseits die Zahlenliterale wie 42, 3.1415 und 12.4e-8, mit denen Zahlenwerte direkt hingeschrieben werden können, andererseits vordefinierte Konstanten wie maxint und pi. Selektoren gibt es nicht, weil die Datentypen atomar sind. (Bei Gleitpunktzahlen könnte man darüber streiten; wirklich kann man für Werte der Klasse Floating auf Komponenten wie die Mantisse zugreifen.) Inspektionsoperationen sind beispielsweise die Gleichheits- und Ordnungsrelationen.

104 7. Abstrakte Datentypen II 7.3 Haskells Eck 7.3.1 Klassen-Instantiierung Dieser Abschnitt gehört thematisch noch in das Kapitel 5. Dort haben wir gesehen, wie wir die Operationen vordefinierter Typklassen für einen algebraischen Datentyp mit... deriving... automatisch ableiten lassen können. Hier wollen wir nachtragen, unter welchen Bedingungen Klasseneigenschaften automatisch abgeleitet werden können, und wie man selbst einen Typ zur Instanz einer Klasse machen kann. Ableiten einer Klasseninstanz Für einen algebraischen Datentyp data Context T α 1 α k = K 1 Typ 1,1...Typ 1,k1. K n Typ n,1...typ n,kn deriving (C 1,..., C m ) kann eine Instanz für eine der vordefinierten Typklassen C {Eq,Ord,Enum,Bounded,Show,Read} unter folgenden Bedingungen automatisch abgeleitet werden: 1. Es gibt einen Kontext Context, so dass sich für alle Komponententypen ableiten lässt: Context CTyp i,j 2. Wenn C = Bounded, muss T ein Produkttyp oder ein Aufzählungstyp sein; d.h., n = 1 oder k j = 0 für 1 j n. 3. Wenn C = Enum, muss T ein Aufzählungstyp sein; d.h., k j = 0 für 1 j n. 4. Es darf keine explizite Instanzvereinbarung geben, die T zu einer Instanz von C macht. Analoges gilt für eine newtype-vereinbarung, die die allgemeine Form hat: newtype Context T α 1 α k = K Typ 1...Typ k deriving (C 1,..., C m )

7.3. Haskells Eck 105 Instantiierung einer Klasse Alle algebraischen Typen können zur Instanz einer Klasse gemacht werden, indem sie explizit als Instanz vereinbart werden. Eine Instanzvereinbarung hat die Form instance Context C Typ where function declaration 1. function declaration k Für die Instanz müssen wenigstens die für C geforderten Funktionen definiert werden; es können auch alle Funktionen der Klasse definiert werden. Damit werden die default-vereinbarung der Klasse im objektorientierten Sinne überschrieben. Eine Instanz hängt von einem Context ab, wenn der Typ Typ parametrisiert ist und die Instantiierung nur gemacht werden kann (soll), wenn die Parameter ihrerseits Instanzen von C oder von anderen Klassen sind. Beispielsweise kann der Typ Day aus Abschnitt?? zu einer Instanz der Klasse Bounded gemacht werden: instance Bounded Day where minbound = Day 1 maxbound = Day 31 Eine Instanz könnte für diesen Typ zwar auch hergeleitet werden, aber dann würden die Grenzen als Day minint und Day maxint angenommen. Der Typ Day könnte auch aufzählbar gemacht werden: instance Enum Day where toenum d d < 1 d > 31 = error "toenum Day : int out of range" otherwise = Day d fromenum (Day d) = d (Dies ist Fassung 2.7.02 von 8. Dezember 2009.)

106 7. Abstrakte Datentypen II