10 · Typklassen und Overloading Typklassen · 10.3 Type Constraints in Intanziierungen � Gleichheit von Listen des Typs [α] ist offensichtlich nur dann wohldefiniert, wenn auch α Gleichheitstests zulässt. � Dies kann durch einen Type Constraint auf dem Klassenparameter α ausgedrückt werden: 1 � instance Eq α => Eq [α] where 2 3 4 5 [] == [] = True (x:xs) == (y:ys) = x == y _ == _ = False && xs == ys 6 7 � xs /= ys = not (xs == ys) Die (==)-Operatoren entstammen verschiedenen Instanziierungen. (was ist damit gemeint?) Damit sind jetzt automatisch alle Listen über vergleichbaren Typen vergleichbar (des rechtfertigt die Syntax =>). Frage Wie könnten Paare des Typs (α,β) für den Gleichheitstest zugelassen werden? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 314 10 · Typklassen und Overloading Typklassen · 10.3 Antwort Die Instanziierung von Eq (α, β) kommt aus der Prelude, und könnte etwa so aussehen: 1 instance (Eq α, Eq β) => Eq (α, β) where 2 (x, y) == (v, w) = x == v && y == w 3 4 p 5 � /= q = not (p == q) Vergleiche die letzte Zeile mit der Instanziierung von Eq [α]. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 315 10 · Typklassen und Overloading Typklassen · 10.3 class Defaults � � Oft sind default-Definitionen für die Funktionen einer Typklasse sinnvoll (s. Eq und (/=)). Eine default-Definition wird innerhalb des class-Konstruktes vorgenommen. Jede Instanz dieser Typklasse “erbt” diese Definition, wenn sie nicht explizit überschrieben wird. Beispiel Allgemein besteht eine offensichtliche Beziehung zwischen Gleichheit und Ungleichheit. 1 2 3 4 5 6 7 � � class Eq a where — Minimal complete definition: (==) | (/=) (==) :: a -> a -> Bool x == y = not (x /= y) (/=) :: a -> a -> Bool x /= y = not (x == y) Bei der Instanziierung muss jetzt nur noch (mindestens!) eine der Definitionen überschrieben werden. Defaults erlauben es konsistentes Verhalten der Funktionen einer Klasse nahezulegen (nicht: erzwingen). Stefan Klinger · DBIS Informatik 2 · Sommer 2016 316 10 · Typklassen und Overloading Typklassen · 10.3 Beispiel Typen mit Ordnung können die Funktionen der Klasse Ord (data Ordering = EQ | LT | GT) überladen: 1 2 3 4 class Ord α where compare :: α -> α -> Ordering (<), (<=), (>=), (>) :: α -> α -> Bool max, min :: α -> α -> α 5 6 7 8 9 — Minimal complete definition: (<=) | compare compare x y | x == y = EQ | x <= y = LT | otherwise = GT 10 11 12 13 14 x x x x <= < >= > y y y y = = = = compare compare compare compare x x x x y y y y /= == /= == GT LT LT GT = = = = x y x y 15 16 17 18 19 max x y | | min x y | | x >= y otherwise x <= y otherwise Frage Steht hier die “ganze Wahrheit”? Oder haben wir etwas vergessen? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 317 10 · Typklassen und Overloading Typklassen · 10.3 Antwort compare verwendet ==, benötigt also einen Eq α Kontext: 1 2 3 4 class Eq α => Ord α where compare :: α -> α -> Ordering (<), (<=), (>=), (>) :: α -> α -> Bool max, min :: α -> α -> α 5 — Minimal complete definition: (<=) or compare compare x y | x == y = EQ | x <= y = LT | otherwise = GT 6 7 8 9 — Hierfür brauchen wir Eq α, ... 10 x x x x 11 12 13 14 <= < >= > y y y y = = = = compare compare compare compare x x x x y y y y /= == /= == GT LT LT GT = = = = x y x y — ... hierfür nicht. Warum? 15 max x y | | min x y | | 16 17 18 19 � � x >= y otherwise x <= y otherwise Auch Klassendefinitionen können einen Type Constraint aufweisen Man darf einen Typ nur dann zu Ord instanziieren, wenn er auch vergleichbar ist! Stefan Klinger · DBIS Informatik 2 · Sommer 2016 318 10 · Typklassen und Overloading Typklassen · 10.3 Zusammenfassung der Syntax 38 Ein Type Constraint kann mehrere Klassen oder Typvariablen beinhalten: (==) :: Eq α ⇒ α → α → Bool λx. x + 1 > 3 :: (Num α, Ord α) ⇒ α → Bool λx y z. (x + 1, y > z) :: (Num α, Ord β) ⇒ α → β → β → (α, Bool) � Formal hat ein Type Constraint also die Form � TypeConstraint OneConstraint � → | → OneConstraint => ( OneConstraint (, OneConstraint)∗ ) => ClassName TypeVariable Klassendefinition und Instanziierung können Type Constraints enthalten: class TypeConstraint? C α where Deklarationen und Default-Definitionen instance TypeConstraint? C α where Definitionen 38 Genauer: https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-750004.3 Stefan Klinger · DBIS Informatik 2 · Sommer 2016 319 10 · Typklassen und Overloading 10.4 Instanziierung algebraischer Datentypen Beispiel 1 � 2 3 4 5 6 7 8 9 10 Erinnerung: Typ Weekday. data Weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun � 1 Instanziierung algebraischer Datentypen · 10.4 Wie macht man Weekday zu einem aufzählbaren Datentyp (also zu einer Instanz von Enum)? Die Prelude definiert39 Defaults für alle Funktionen in Enum bis auf toEnum und fromEnum: class Enum a where toEnum :: Int -> a fromEnum :: a -> Int . . . succ :: a -> a succ = toEnum . (+1) . fromEnum . . . enumFromTo :: a -> a -> [a] enumFromTo x y = map toEnum [ fromEnum x .. fromEnum y ] . . . 39 http://hackage.haskell.org/package/base-4.8.0.0/docs/Prelude.html#t:Enum Stefan Klinger · DBIS Informatik 2 · Sommer 2016 320 10 · Typklassen und Overloading Instanziierung algebraischer Datentypen · 10.4 Verbleibt also die Definition von toEnum und fromEnum mit der offensichtlich gewünschten Eigenschaft fromEnum ◦ toEnum ≡ id. Hinweis fromEnum und toEnum etablieren einen Isomorphismus von Weekday und [0..6]. 1 2 3 4 5 6 7 8 9 10 11 12 Jetzt geht zum Beispiel: 1 2 13 *Main> [Wed .. Sat] — Leerzeichen wichtig! [Wed,Thu,Fri,Sat] 14 15 instance Enum Weekday where toEnum 0 = Mon toEnum 1 = Tue toEnum 2 = Wed toEnum 3 = Thu toEnum 4 = Fri toEnum 5 = Sat toEnum 6 = Sun fromEnum Mon = 0 fromEnum Tue = 1 fromEnum Wed = 2 fromEnum Thu = 3 fromEnum Fri = 4 fromEnum Sat = 5 fromEnum Sun = 6 Problem Die Definition derartiger Datentypen als Instanz von Enum (aber auch Eq, Ord, Show, Read40 ) ist kanonisch, aufwändig, und fehleranfällig. 40 Einige dieser Typklassen tauchen später noch auf. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 321 10 · Typklassen und Overloading Instanziierung algebraischer Datentypen · 10.4 Beispiel Wir machen BinTree zur Instanz von Eq, und definieren dazu den Gleichheitstest auf Werten von BinTree: 1 2 3 4 instance Eq a => Eq (BinTree a) where Empty == Empty = True Node l1 x1 r1 == Node l2 x2 r2 = x1 == x2 && l1 == l2 && r1 == r2 _ == _ = False Beobachtungen (reguläres Muster): � Die Struktur der Definition von (==) folgt der rekursiven Struktur des Datentyps BinTree. � Nur jeweils durch gleiche Konstruktoren erzeugte Werte können potentiell gleich sein. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 322 10 · Typklassen und Overloading Instanziierung algebraischer Datentypen · 10.4 deriving � Das soeben beobachtete reguläre Muster der Typklassen-Operationen ermöglicht es dem Haskell-Compiler, instance-Deklarationen automatisch abzuleiten. Syntax Die deriving-Klausel... data T α1 α2 ... αn = ... | ... .. . deriving (C1 ,...,Cm ) ...macht den Typkonstruktor T zur Instanz der Typklassen C1 , ...Cm . � Diese Magie ist leider nicht für alle Typklassen Ci verfügbar. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 323 10 · Typklassen und Overloading Instanziierung algebraischer Datentypen · 10.4 Die Definitionen der Typklassen-Operationen können automatisch erzeugt werden, für folgende in der Prelude definierten Klassen: Eq Ableitbar für alle Datentypen mit vergleichbaren Komponenten. � Gleichheit wird über die Identität von Konstruktoren und (rekursiv) der Gleichheit der Komponenten des Datentyps entschieden. Ord Ableitbar für alle Datentypen mit anordenbaren Komponenten. � Reihenfolge der Konstruktoren in data-Deklaration entscheidend, Ordnung wird lexikographisch (rekursiv) definiert. Enum Datentyp muss ein reiner Summentyp sein (s. vorn), also data T = K0 | ... | Kn−1 mit fromEnum Ki = i. Show Textuelle Repräsentation der Konstruktoren, s. später. ... Einige andere die wir hier nicht besprechen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 324 10 · Typklassen und Overloading Beispiel 1 2 3 Instanziierung algebraischer Datentypen · 10.4 BinTree (nochmal) data BinTree a = Empty | Node (BinTree a) a (BinTree a) deriving (Eq, Ord) führt automatisch zu: 1 2 3 4 instance Eq a => Eq (BinTree a) where Empty == Empty = True Node l1 x1 r1 == Node l2 x2 r2 = x1 == x2 && l1 == l2 && r1 == r2 _ == _ = False 5 6 7 8 9 10 11 12 instance Ord a => Ord (BinTree a) where Empty <= Empty = True Empty <= Node _ _ _ = True Node _ _ _ <= Empty = False Node l1 x1 r1 <= Node l2 x2 r2 = l1 < l2 || l1 == l2 && x1 < x2 || x1 == x2 && r1 <= r2 Hinweis zu den == Operatoren: Der Type Constraint Ord α impliziert bereits Eq α. Das kommt aus der Klassendefinition von Ord, cf. Seite 318 Stefan Klinger · DBIS Informatik 2 · Sommer 2016 325 10 · Typklassen und Overloading Instanziierung algebraischer Datentypen · 10.4 Beispiel Nicht immer ist deriving die Antwort! Erinnerung – Frac repräsentiert rationale Zahlen. 1 2 data Frac = Integer :/ Integer deriving Eq Hier führt deriving nicht zum Ziel ... 1 2 > 2 :/ 5 == 4 :/ 10 False Gemeint ist vielmehr: 1 2 instance Eq Frac where (x1 :/ y1) == (x2 :/ y2) = x1 * y2 == x2 * y1 3 4 5 > 2 :/ 5 == 4 :/ 10 True Stefan Klinger · DBIS Informatik 2 · Sommer 2016 326 10 · Typklassen und Overloading Beispiel 1 2 Instanziierung algebraischer Datentypen · 10.4 Weekday (nochmal) data Weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun deriving (Eq, Ord, Enum, Show) Damit haben wir automagisch: 1 2 3 4 5 6 7 8 > Mon < Tue True > Mon == Sat False > [Mon,Wed .. Sun] [Mon,Wed,Fri,Sun] > fromEnum Sat 5 � Frage Was ergibt der Aufruf map toEnum [2,3]? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 327 10 · Typklassen und Overloading Anmerkung: Mehrdeutige Typen 10.5 � Anmerkung: Mehrdeutige Typen · 10.5 Die Funktion show :: Show α ⇒ α → String wird vom GHCi verwendet, um das Ergebnis einer Berechnung auszugeben (cf. Seite 334). 1 2 3 4 5 6 *Main> ['1', '2', '3'] "123" *Main> [1,2,3] [1,2,3] *Main> 123 123 1 2 3 4 5 6 *Main> show ['1', '2', '3'] "\"123\"" *Main> show [1,2,3] "[1,2,3]" *Main> show 123 "123" � Offensichtlich muss show für verschiedene Typen unterschiedlich implementiert sein. ⇒ Je nach Typ wird die passende Implementation von show ausgewählt. � Welche Implementation soll für toEnum 2 ausgewählt werden? 1 2 *Main> :t toEnum 2 toEnum 2 :: Enum a => a Stefan Klinger · DBIS Vollkommen unklar! α kann mit jedem beliebigen Typen aus der Klasse Enum instanziiert werden. Informatik 2 · Sommer 2016 328 10 · Typklassen und Overloading Anmerkung: Mehrdeutige Typen · 10.5 Fehlermeldung 1 2 3 4 5 6 7 8 9 10 bis GHCi 7.6.3, sonst cf. Seite 332 Prelude> toEnum 2 <interactive>:2:1: No instance for (Enum a0) arising from a use of ‘toEnum’ The type variable ‘a0’ is ambiguous Possible fix: add a type signature that fixes these type variable(s) Note: there are several potential instances: instance Enum Weekday -- Defined at <interactive>:3:35 instance Enum Double -- Defined in ‘GHC.Float’ instance Enum Float -- Defined in ‘GHC.Float’ ...plus 8 others � � Der GHCi sagt uns dass er nicht weiss welches Enum wir meinen. Wir können aber für einen Ausdruck explizit einen Typ angeben: 1 2 3 4 5 6 7 1 2 3 4 *Main> :t 4 4 :: Num a => a *Main> 4 4 Stefan Klinger · DBIS 8 *Main> GT *Main> '\STX' *Main> Wed *Main> 2 toEnum 2 :: Ordering toEnum 2 :: Char — cf. ascii(7) toEnum 2 :: Weekday toEnum 2 :: Int Frage Was passiert hier? Warum kein Fehler? Informatik 2 · Sommer 2016 329 10 · Typklassen und Overloading Anmerkung: Mehrdeutige Typen · 10.5 Type Defaulting Antwort Offensichtlich sucht sich der GHCi einen “passenden Typ” aus. � Auf Dauer wäre es anstrengend, auch bei einfachen Fällen (z.B. Zahlen) immer einen Typ mit angeben zu müssen. 1 � � Dieses Vorgehen heisst Type Defaulting und wird auch für andere Fälle verwendet: 2 3 4 5 6 *Main> [] — [ ] :: ∀α. [α] [] *Main> [] :: String "" *Main> 2.3 — 2.3 :: Fractional α ⇒ α 2.3 Die Defaulting-Regeln sind im Haskell-Standard41 und im GHCi Manual42 beschrieben. 41 https://www.haskell.org/onlinereport/haskell2010/haskellch4.html#x10-790004.3.4 42 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/interactive-evaluation.html#extended-default-rules Stefan Klinger · DBIS Informatik 2 · Sommer 2016 330 10 · Typklassen und Overloading � 1 2 3 4 5 6 7 Anmerkung: Mehrdeutige Typen · 10.5 Man kann den ghci mit der Option -Wall starten43 , dann wird ein Defaulting-Vorgang angezeigt. Prelude> 5 <interactive>:3:1: Warning: Defaulting the following constraint(s) to type ‘Integer’ (Show a0) arising from a use of ‘print’ at <interactive>:3:1 (Num a0) arising from a use of ‘it’ at <interactive>:3:1 In a stmt of an interactive GHCi command: print it 5 8 9 10 11 12 13 14 Prelude> [] <interactive>:4:1: Warning: Defaulting the following constraint(s) to type ‘()’ (Show t0) arising from a use of ‘print’ In a stmt of an interactive GHCi command: print it [] Der Unit-Type () enthält genau einen Wert, () :: (), ebenfalls Unit genannt. Er wird oft verwendet wenn man einen Typ braucht der weiter kaum Eigenschaften hat. 43 -W legt fest welche Warnungen gezeigt werden sollen. Hier: all = Alle. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 331 10 · Typklassen und Overloading Anmerkung: Mehrdeutige Typen · 10.5 Erweiterte Defaulting-Regeln GHCi ab Version 7.8 Die Defaulting-Regeln wurden im Laufe der Zeit erweitert44 . � Aktuelle Versionen des GHCi verwenden oft () als Default. 1 2 3 GHCi, version 7.8.4: http://www.haskell.org/ghc/ Prelude> :set -Wall Prelude> toEnum 3 :? for help 4 5 6 7 8 � � <interactive>:3:1: Warning: Defaulting the following constraint(s) to type ‘()’ ... *** Exception: Prelude.Enum.().toEnum: bad argument Hier wird also toEnum 3 :: Enum α ⇒ zu toEnum 3 :: () konkretisiert. Die Fehlermeldung wird dann von der konkreten Funktion toEnum 3 :: () ausgegeben: • () ist zwar Instanz der Enum-Klasse, • enthält aber nur einen Wert! (Ausprobieren: toEnum 0) 44 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/interactive-evaluation.html#extended-default-rules Stefan Klinger · DBIS Informatik 2 · Sommer 2016 332 10 · Typklassen und Overloading � Anmerkung: Mehrdeutige Typen · 10.5 Obacht � Defaulting ist eine Krücke die die Arbeit mit dem interaktiven Interpreter vereinfachen soll. � Dadurch wird die Sprache Haskell nicht klarer, oder einfacher, vielmehr fallen magische Effekte vom Himmel. � Verlassen Sie sich beim Programmieren nicht auf Defaulting, sondern geben Sie die Typen Ihrer Funktionen an! Stefan Klinger · DBIS Informatik 2 · Sommer 2016 333 10 · Typklassen und Overloading 10.6 � Die Typklasse Show · 10.6 Die Typklasse Show Die Überführung ihrer Werte in eine externe Repräsentation (vom Typ String) ist eine der Kernaufgaben jeder Programmiersprache, z.B. für • Ein-/Ausgabe von Werten, interaktive Programme, • Speichern/Laden/Übertragung von Daten. � In Haskell wird die benötige Funktionalität von Instanzen der Typklasse Show bereitgestellt45 : 1 2 3 4 class Show α where show :: α -> String — Minimal complete definition: show ... 45 Typklasse Read leistet die Umkehrabbildung, das sog. parsing. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 334 10 · Typklassen und Overloading Beispiel 1 2 3 4 5 6 7 8 9 10 Die Typklasse Show · 10.6 Ausgabe von Werten des Typs Frac als Brüche der Form x . y showFrac :: Frac -> String showFrac (x :/ y) = replicate (div (l-lx) 2) ' ' ++ sx ++ "\n" ++ replicate l '-' ++ "\n" ++ replicate (div (l-ly) 2) ' ' ++ sy where sx = show x sy = show y lx = length sx ly = length sy l = max lx ly 11 12 13 instance Show Frac where show = showFrac Haskell-Interpreter GHCi benutzt show :: Show α ⇒ α → String, um in interaktiven Sessions Werte darzustellen: 1 2 3 4 *Main> 421 :/ 6546516 421 ------6546516 Stefan Klinger · DBIS Informatik 2 · Sommer 2016 335 10 · Typklassen und Overloading Beispiel 1 2 3 4 5 6 7 8 9 10 11 Die Typklasse Show · 10.6 “Visualisierung” von Werten des Typs BinTree α . showTree :: Show a => BinTree a -> String showTree = concat . ppTree where ppTree :: Show a => BinTree a -> [String] ppTree Empty = ["@\n"] ppTree (Node l x r) = [show x ++ "\n"] ++ ("|--" ++ ls) : map ("| ++ ("‘--" ++ rs) : map (" where ls:lss = ppTree l rs:rss = ppTree r " ++) lss " ++) rss 12 13 14 1 2 3 4 5 6 7 8 9 10 instance Show a => Show (BinTree a) where show = showTree *Main> Node (leaf 1) 2 (Node (leaf 3) 4 Empty) 2 |--1 | |--@ | ‘--@ ‘--4 |--3 | |--@ | ‘--@ ‘--@ Stefan Klinger · DBIS Informatik 2 · Sommer 2016 336