Hier die Zusammenfassung zum Thema Typklassen (Erster Teil von Kapitel 6 von Real World Haskell). Konkret folgende Unterkapitel:
- The need for typeclasses
- What are typeclasses?
- Declaring typeclass instances
- Important Built-In Typeclasses
- Show
- Read
- Serialization with Read and Show
- Numeric Types
- Equality, Ordering, and Comparisons
The need for typeclasses
- Beispiel: Gleichheitsprüfung
- für eigene Datentypen
- für Standardtypen wie
String
1 2 3 4 5 6 7 8 9 10 11 12 | data Color = Red | Green | Blue
colorEq :: Color - > Color - > Bool
colorEq Red Red = True
colorEq Green Green = True
colorEq Blue Blue = True
colorEq _ _ = False
stringEq :: [Char] - > [Char] - > Bool
stringEq [] [] = True
stringEq (x:xs) (y:ys) = x = = y && stringEq xs ys
stringEq _ _ = False
|
The need for typeclasses
1 2 | colorEq :: Color - > Color - > Bool
stringEq :: [Char] - > [Char] - > Bool
|
- Probleme mit diesem Ansatz:
- Unterschiedliche Funktionsnamen
- Ableitbare Funktionen wie
/=
müssen mehrfach implementiert werden
- Funktionen sind nicht generisch
- Aufrufer muss konkreten Typ kennen
What are typeclasses?
- Lösung: Typklassen
- Definiert eine Menge von Operationen
- Instanzen müssen Operationen spezifisch implementieren
- Implementierung wird je nach Typ ausgewählt
- Schlüsselwort
class
- Kein Objekttyp (wie bei OOP)
- Eher mit Schnittstellen in OOP vergleichbar
What are typeclasses?
Syntax zur Definition der Typklasse:
1 2 | class BasicEq a where
isEqual :: a - > a - > Bool
|
1 2 | ghci> : type isEqual
isEqual :: (BasicEq a) = > a - > a - > Bool
|
Syntax zur Implementierung von Instanzen (bisher nur Bool
):
1 2 3 4 | instance BasicEq Bool where
isEqual True True = True
isEqual False False = True
isEqual _ _ = False
|
1 2 3 4 | ghci> isEqual False False
True
ghci> isEqual False True
False
|
What are typeclasses?
Weitere Funktion für die Typklasse:
1 2 3 | class BasicEq2 a where
isEqual2 :: a - > a - > Bool
isNotEqual2 :: a - > a - > Bool
|
- Nützlich, aber umständlich zu implementieren
isNotEqual
kann man von isEqual
ableiten
- ... und umgekehrt
What are typeclasses?
1 2 3 4 5 6 | class BasicEq3 a where
isEqual3 :: a - > a - > Bool
isEqual3 x y = not (isNotEqual3 x y)
isNotEqual3 :: a - > a - > Bool
isNotEqual3 x y = not (isEqual3 x y)
|
- Standardimplementierung in der Typklasse
- Es muss nur eine Funktion implementiert werden
- Beide Funktionen können spezifisch implementiert werden
- Achtung: Beispiel führt zu endloser Rekursion, sofern keine der Funktionen von der Instanz implementiert wird!
- Das "echte"
Eq
verlangt, dass man eine Funktion implementiert
Declaring typeclass instances
Statt ...
1 2 3 4 | colorEq Red Red = True
colorEq Green Green = True
colorEq Blue Blue = True
colorEq _ _ = False
|
- Typ
Color
in die Typklasse BasicEq3
aufnehmen
- Als Instanz der Typklasse deklarieren
- Erforderliche Funktionen implementieren
- Nur
isEqual3
- Implementierung ist gleich
1 2 3 4 5 | instance BasicEq3 Color where
isEqual3 Red Red = True
isEqual3 Green Green = True
isEqual3 Blue Blue = True
isEqual3 _ _ = False
|
Important Built-In Typeclasses
Show
- Wandelt Werte in
String
s um
- Typischerweise kein pretty print, sondern maschinenlesbar
- Wichtigste Funktion:
1 | show :: (Show a) = > a - > String
|
1 2 3 4 5 6 | ghci> show [ 1 , 2 , 3 ]
"[1,2,3]"
ghci> show "Hello!"
"\"Hello!\""
ghci> putStrLn (show "Hello!" )
"Hello!"
|
Show
Instanz von Show
implementieren:
1 2 3 4 5 6 | data Color = Red | Green | Blue
instance Show Color where
show Red = "Red"
show Green = "Green"
show Blue = "Blue"
|
Read
- Gegenstück zu
Show
- Wichtigste Funktion:
1 | read :: (Read a) = > String - > a
|
1 2 3 4 5 | main = do
putStrLn "Please enter a Double:"
inpStr < - getLine
let inpDouble = (read inpStr)::Double
putStrLn ( "Twice " + + show inpDouble + + " is " + + show (inpDouble * 2 ))
|
Read
Bei Mehrdeutigkeiten sind Typannotationen nötig:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ghci> read "5"
<interactive>: 1 : 0 :
Ambiguous type variable `a' in the constraint:
`Read a ' arising from a use of `read' at <interactive>: 1 : 0 - 7
Probable fix: add a type signature that fixes these type variable(s)
ghci> : type (read "5" )
(read "5" ) :: (Read a) = > a
ghci> (read "5" )::Integer
5
ghci> (read "5" )::Double
5.0
ghci> (read "5.0" )::Double
5.0
ghci> (read "5.0" )::Integer
* * * Exception: Prelude.read: no parse
|
Read
Implementierung von Read
für Color
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | instance Read Color where
- - readsPrec is the main function for parsing input
readsPrec _ value =
- - We pass tryParse a list of pairs. Each pair has a string
- - and the desired return value. tryParse will try to match
- - the input to one of these strings.
tryParse [( "Red" , Red), ( "Green" , Green), ( "Blue" , Blue)]
where tryParse [] = [] - - If there is nothing left to try , fail
tryParse ((attempt, result):xs) =
- - Compare the start of the string to be parsed to the
- - text we are looking for .
if (take (length attempt) value) = = attempt
- - If we have a match, return the result and the
- - remaining input
then [(result, drop (length attempt) value)]
- - If we don't have a match, try the next pair
- - in the list of attempts.
else tryParse xs
|
Read
1 2 3 4 5 6 7 8 9 10 11 12 | ghci> (read "Red" )::Color
Red
ghci> (read "Green" )::Color
Green
ghci> (read "Blue" )::Color
Blue
ghci> (read "[Red]" )::[Color]
[Red]
ghci> (read "[Red,Red,Blue]" )::[Color]
[Red,Red,Blue]
ghci> (read "[Red, Red, Blue]" )::[Color]
* * * Exception: Prelude.read: no parse
|
- Einlesen von zusammengesetzten Datentypen
- Voranstehende Leerzeichen werden oft verworfen
- Populäre Alternative für Parser:
Parsec
Serialization with Read and Show
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ghci> let d1 = [Just 5 , Nothing, Nothing, Just 8 , Just 9 ]::[Maybe Int ]
ghci> putStrLn (show d1)
[Just 5 ,Nothing,Nothing,Just 8 ,Just 9 ]
ghci> writeFile "test" (show d1)
ghci> input < - readFile "test"
"[Just 5,Nothing,Nothing,Just 8,Just 9]"
ghci> let d2 = (read input )::[Maybe Int ]
ghci> print d1
[Just 5 ,Nothing,Nothing,Just 8 ,Just 9 ]
ghci> print d2
[Just 5 ,Nothing,Nothing,Just 8 ,Just 9 ]
ghci> d1 = = d2
True
|
- Implementierungen von
Show
und Read
müssen "zusammenpassen"
- Auch für komplexe, zusammengesetzte Datenstrukturen
- Sofern alle Komponenten
Show
und Read
implementieren
Numeric Types
1 2 3 4 5 6 7 8 | class Num a where
( + ) :: a - > a - > a
( * ) :: a - > a - > a
( - ) :: a - > a - > a
negate :: a - > a
abs :: a - > a
signum :: a - > a
fromInteger :: Integer - > a
|
- Basisklasse, die von anderen Typklassen erweitert wird
- Es gibt viele Instanzen von
Num
- Es ist möglich selbst Instanzen anzulegen
- Dadurch kann man die Operatoren/Funktionen von
Num
verwenden
Table 6.1. Selected Numeric Types
[...]
Equality, Ordering, and Comparisons
Source von Eq
(Ausschnitt):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | infix 4 = = , / =
- - | The 'Eq' class defines equality ( '==' ) and inequality ( '/=' ).
- - All the basic datatypes exported by the "Prelude" are instances of 'Eq' ,
- - and 'Eq' may be derived for any datatype whose constituents are also
- - instances of 'Eq' .
- -
- - Minimal complete definition: either '==' or '/=' .
- -
class Eq a where
( = = ), ( / = ) :: a - > a - > Bool
x / = y = not (x = = y)
x = = y = not (x / = y)
|
Equality, Ordering, and Comparisons
Definition von Ord
:
1 2 3 4 5 6 7 8 9 10 11 | data Ordering = LT | EQ | GT
- - Minimal complete definition: compare or (< = )
class Eq a = > Ord a where
compare :: a - > a - > Ordering
(<) :: a - > a - > Bool
(> = ) :: a - > a - > Bool
(>) :: a - > a - > Bool
(< = ) :: a - > a - > Bool
max :: a - > a - > a
min :: a - > a - > a
|
Ord
baut auf Eq
auf
- Nur eine Funktion muss implementiert werden
- Sortierung von Instanzen mittels
Data.List.sort