Softwerkskammer

 

Hier die Zusammenfassung zum Thema Typklassen (Erster Teil von Kapitel 6 von Real World Haskell):

Chapter 6. Using Typeclasses (I)

  • 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
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

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
      • Abstrakt
    • Instanzen müssen Operationen spezifisch implementieren
    • Implementierung wird je nach Typ ausgewählt
  • Schlüsselwort class
    • Kein Objekttyp (wie bei OOP)
      • Sondern Klasse von Typen
    • Eher mit Schnittstellen in OOP vergleichbar

What are typeclasses?

Syntax zur Definition der Typklasse:

class BasicEq a where
    isEqual :: a -> a -> Bool
ghci> :type isEqual
isEqual :: (BasicEq a) => a -> a -> Bool

Syntax zur Implementierung von Instanzen (bisher nur Bool):

instance BasicEq Bool where
    isEqual True  True  = True
    isEqual False False = True
    isEqual _     _     = False
ghci> isEqual False False
True
ghci> isEqual False True
False

What are typeclasses?

Weitere Funktion für die Typklasse:

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?

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 ...

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
      • Schlüsselwort instance
    • Erforderliche Funktionen implementieren
      • Nur isEqual3
      • Implementierung ist gleich
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 Strings um
  • Typischerweise kein pretty print, sondern maschinenlesbar
  • Wichtigste Funktion:
show :: (Show a) => a -> String
  • Beispiele:
ghci> show [1, 2, 3]
"[1,2,3]"
ghci> show "Hello!"
"\"Hello!\""
ghci> putStrLn (show "Hello!")
"Hello!"

Show

Instanz von Show implementieren:

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:
read :: (Read a) => String -> a
  • Beispiel:
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:

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:

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

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

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

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
    • Division fehlt z.B.
  • 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):

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:

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