Was bisher geschah I Algebraische Datentypen I Pattern Matching I Rekursionsschemata map, fold über Zahlen, Listen, Bäumen, ... I strukturelle Induktion I Polymorphie 61 Eingeschänkte Polymorphie reverse [1,2,3,4] = [4,3,2,1] reverse "foobar" = "raboof" reverse :: [a] -> [a] reverse ist polymorph sort [5,1,4,3] = [1,3,4,5] sort "foobar" = "abfoor" sort :: [a] -> [a] -- ?? sort [sin,cos,log] = ?? sort ist eingeschränkt polymorph 62 Motivation sort enthält: let ( low, high ) = partition ( < ) xs in ... Für alle Typen a, die für die es eine Vergleichs-Funktion compare gibt, hat sort den Typ [a] -> [a]. sort :: Ord a => [a] -> [a] Ord ist eine Typklasse, definiert durch class Ord a where compare :: a -> a -> Ordering data Ordering = LT | EQ | GT 63 Instanzen Typen können Instanzen von Typklassen sein. (OO: Klassen implementieren Interfaces) Für vordefinierte Typen sind auch die meisten sinnvollen Instanzen vordefiniert instance Ord Int ; instance Ord Char ; ... weitere Instanzen kann man selbst deklarieren: data Student = Student { vorname :: String , nachname :: String , matrikel :: Int } instance Ord Student where compare s t = compare (matrikel s) (matrikel t) 64 Typen und Typklassen In Haskell sind diese drei Dinge unabhängig 1. Deklaration einer Typklasse (= Deklaration von abstrakten Methoden) class C where { m :: ... } 2. Deklaration eines Typs (= Sammlung von Konstruktoren und konkreten Methoden) data T = ... 3. Instanz-Deklaration (= Implementierung der abstrakten Methoden) instance C T where { m = ... } In Java sind 2 und 3 nur gemeinsam möglich class T implements C { ... } 65 Wörterbücher Haskell-Typklassen/Constraints. . . class C a where m :: a -> a -> Int f :: C a => a -> Int f x = m x x + 5 . . . sind Abkürzungen für Wörterbücher: data C a = C { m :: a -> a -> Foo } f :: C a -> a -> Int f dict x = ( m dict ) x x + 5 Für jedes Constraint setzt der Compiler ein Wörterbuch ein. 66 instance C Bar where m x y = ... dict_C_Bar :: C Bar dict_C_Bar = C { m = \ x y -> ... } An der aufrufenden Stelle ist das Wörterbuch statisch bekannt (hängt nur vom Typ ab). b :: Bar ... f b ... wird übersetzt zu ... f dict_C_bar b ... 67 Vergleich Polymorphie I Haskell-Typklassen: statische Polymorphie, Wörterbuch ist zusätzliches Argument der Funktion I OO-Programmierung: dynamische Polymorphie, Wörterbuch ist im Argument-Objekt enthalten. (OO-Wörterbuch = Methodentabelle der Klasse) 68 Typen mit Gleichheit class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool Beispiele: I (’a’ == ’b’) = False I (True /= False) = True I ("ab" /= "ac") = True I ([1,2] == [1,2,3]) = False I (\ x -> 2 * x) == (\ x -> x + x) = ? 69 Typen mit totaler Ordnung Instanzen der Typklasse Eq mit data Ordering = LT | EQ | GT class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool min :: a -> a -> a max :: a -> a -> a Beispiele: I (’a’ < ’b’) = True I (False < True) = True I ("ab" < "ac") = True (lexikographisch) I ([1,2] > [1,2,3]) = False 70 Klassen-Hierarchien Typklassen können in Beziehung stehen. Ord ist „abgeleitet“ von Eq: class Eq a where (==) :: a -> a -> Bool class Eq a => Ord a where (<) :: a -> a -> Bool Ord ist Typklasse mit Typconstraint (Eq) also muß man erst die Eq-Instanz deklarieren, dann die Ord-Instanz. Jedes Ord-Wörterbuch hat ein Eq-Wörterbuch. 71 Instanzen data Bool = False | True instance Ord False == True == _ == Bool where False = True True = True _ = False instance Eq Bool where False < True = True _ < _ = False x <= y = ( x < y ) || ( x == y ) x > y = y < x x >= y = y <= x 72 Numerische Typen class (Eq a, Show a) => Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a Beispiele: I signum (-3) = -1 I signum (-3.3) = -1.0 Instanzen Int, Integer, Float 73 Numerische Typen mit Division class Num a => Integral a where div :: a -> a -> a mod :: a -> a -> a Instanzen Int, Integer class Num a => Fractional a where (/) :: a -> a -> a recip :: a -> a -> a Instanz Float Beispiele: I 3 / 2 = 0.6 I 3 ‘div‘ 2 = 1 74 Typen mit Operation zum (zeilenweisen) Anzeigen class Show a where show :: a -> String Beispiele: I show 123 = "123" I show True = "True" I show [1,2] = "[1,2]" I show (1,’a’,True) = "show (1,’a’,True)" Instanzen Bool, Char, Int, Integer, Float, Listen und Tupel von Instanzen 75 Typklasse Show Die Interpreter Ghci/Hugs geben bei Eingabe exp (normalerweise) show exp aus. Man sollte (u. a. deswegen) für jeden selbst deklarierten Datentyp eine Show-Instanz schreiben. . . . oder schreiben lassen: deriving Show 76 Typen mit Operation zum Lesen class Read a where read :: String -> a Beispiele: I ( read "3" :: Int ) = 3 I ( read "3" :: Float ) = 3.0 I ( read "False" :: Bool ) = False I ( read "’a’" :: Char ) = ’a’ I ( read "[1,2,3]" :: [Int] ) = [1,2,3] Instanzen Bool, Char, Int, Integer, Float, Listen und Tupel von Instanzen 77 Generische Instanzen class Eq a where (==) :: a -> a -> Bool Vergleichen von Listen (elementweise) wenn a in Eq, dann [a] in Eq: instance Eq a => Eq [a] where [] == [] = True (x : xs) == (y : ys) = (x == y) && ( xs == ys ) _ == _ = False 78 Abgeleitete Instanzen Deklaration eigener Typen als Instanzen von Standardklassen durch automatische Erzeugung der benötigten Methoden: Beispiele: data Bool = False | True deriving (Eq, Ord, Show, Read) data Shape = Circle Float | Rect Float Float deriving (Eq, Ord, Show, Read) Beispiel: (Circle 3 < Rect 1 2) == True data (Eq a) => Maybe a = Nothing | Just a deriving (Eq, Ord, Show, Read) Beispiel: (Just ’a’ == Just ’b’) == False 79 Interaktive Programme I Berechnungen in Haskell sind nebenwirkungsfrei. (Diese Eigenschaft soweit wie möglich beibehalten) I Ein- und Ausgabe sind Nebenwirkungen. (Nebenwirkungen zulassen, wo sie unvermeidbar sind) I Bei Ein- und Ausgabeaktionen ist die Ausführungsreihenfolge wichtig. Idee: I Isolation der Programmteile mit Nebenwirkungen (Operation return ) I sequentielle Verknüpfung von Ausdrücken mit Nebenwirkungen (Operation bind >>= ) 80 Hakell-Compiler ghc Programm hello.hs: module Main where main = putStrLn "Hello" mit ghc kompilieren ghc -o hello Main.hs und ausführen: ./hello 81 Noch ein Beispiel module Main where sumList :: [Int] -> Int sumList [] = 0 sumList (x:xs) = x + sumList xs main = print ( sumList [1 .. 10] ) kompilieren mit ghc und aufrufen: ghc -o sumlist Main.hs ./sumlist oder interpretieren (wie bisher) mit ghci: :l Main main 82 Abstraktes Modell für IO I IO a = Aktion mit Resultat :: a und Nebenwirkung I ein ausführbares Haskell-Programm enthält module Main where main :: IO () main = ... diese Aktion wird (berechnet und) ausgeführt. oft zusätzlich notwendig: import System.IO main = do hSetBuffering stdout NoBuffering ... 83 Konkretes Modell für IO: Zustand Typ für Aktionen, Änderung des Weltzustandes data World = ... data IO = World -> World data IO a = IO { World -> (a, World) } das Welt-Objekt bezeichnet die Welt außerhalb des Programmes Problem: f :: World -> ( World, World ) f w = ( deleteFile "foo" w, putStr "bar" w ) mögliche Lösungen: I Haskell: Typ World ist privat, öffentlich ist nur IO I Clean: Typ World ist öffentlich, aber unique 84 Ein- und Ausgabe putStrLn "Hello" hat den Typ IO () Zustandsänderung ohne Resultat putStrLn hat den Typ String -> IO () Funktion, Funktionswert ist eine Aktion (die zu einer Zustandsänderung führen kann) putStr, putStrLn nur für Strings putStr :: String -> IO () print für beliebige Typen (mit show) print x = putStrLn (show x) getLine hat den Typ IO String Zustandsänderung mit Resultat :: String 85 Ein- und Ausgabe Für IO-Aktion ist die Reihenfolge wichtig. muss in Haskell angegeben werden do-Notation do A B C mit Anweisungen A,B,C 86 Ein- und Ausgabe-Anweisungen Eingabe: x <- A mit Aktion A (erzeugt Variablenbindung) Beispiel (Kombination): echoline :: IO () echoline = do input <- getLine putStr input 87 Häufige IO-Aktionen abfrage :: String -> IO String abfrage s = do putStrLn s getLine mit getLine :: IO String getLine = do x <- getChar if x == ’\n’ then return [] else do xs <- getLine return xs Verwendung: main :: IO () main = do name <- abfrage "Name:" putStrLn ("Hello " ++ name) 88 Ein- und Ausgaben von Werten getNumber :: IO Int getNumber = do putStr "Zahl eingeben: " readLn z.B. in main :: IO () main = do n1 <- getNumber n2 <- getNumber putStr "Summe: " print (n1 + n2) analog: getNumber :: IO Int 89 Rückgabewerte get2 :: IO (Int, Int) get2 = do n1 <- getNumber n2 <- getNumber return (n1, n2) return :: a -> IO a main = do x <- get2 print x 90 Rekursion getNums :: IO [Int] getNums = do n <- readLn if n == 0 then return [] else do ns <- getNums return ([n] -- ns) main :: IO () main = do putStrLn "Zahlen eingeben (0 für Ende)" nums <- getNums putStr "Summe = " ++ show (sum nums) 91 IO data IO -- abstract readFile :: FilePath -> IO String putStrLn :: String -> IO () Alle „Funktionen“, deren Resultat von der Außenwelt (Systemzustand) abhängt, haben Resultattyp IO ... Am Typ einer Funktion erkennt man ihre möglichen (schädlichen) Wirkungen bzw. deren garantierte Abwesenheit. Wegen der Monad-Instanz: benutze do-Notation do cs <- readFile "foo.bar" putStrLn cs 92 Monaden class Monad m where return :: a -> m a ( >>= ) :: m a -> (a -> m b) -> m b mit den Operationen: return return :: a -> m a Eingang bind (>>=) :: m a -> (a -> m b) -> m b sequentielle Verknüpfung Monaden-Gesetze: I (return x) >>= f == f x I m >>= return == m I ( m >>= f) >>= g == m >>= (\ x -> f x >>= g) 93 IO-Monade class Monad m where return :: a -> m a ( >>= ) :: m a -> (a -> m b) -> m b für m = IO: return :: a -> IO a ( >>= ) :: IO a -> (a -> IO b) -> IO b instance Monad IO where return v = \ w -> (v, w) f >>= g = \ w -> case f w of (v, w’) -> g v w’ 94 Do-Notation für Monaden Beispiel: original evaluate e l >>= \ a -> evaluate e r >>= \ b -> return ( a + b ) do-Notation (implizit geklammert) do a <- evaluate e l b <- evaluate e r return ( a + b ) Übersetzung: I do f übersetzt zu f I do f; ... übersetzt zu f >>= \ _ -> do I do x <- f; ... übersetzt zu f >>= \ x -> do 95 Beipiel Datei-Ein- und Ausgabe main :: IO () main = do putStr " Filename:" fname <- getLine cont <- readFile fname putStr cont 96 Maybe-Monade data Maybe a = Just a | Nothing statt: case ( evaluate e l ) of Nothing -> Nothing Just a -> case ( evaluate e r ) of Nothing -> Nothing Just b -> Just ( a + b ) kürzer und übersichtlicher: evaluate e l >>= \ a -> evaluate e r >>= \ b -> return ( a + b ) mit Hilfe der Maybe-Monade instance Monad Maybe where return = \ x -> Just x m >>= f = case m of Nothing -> Nothing Just x -> f x 97 Listen-Monade instance Monad [] where return = \ x -> [x] m >>= f = concat ( map f m ) Beispiel: Kreuzprodukt von xs :: [a] mit ys :: [b] cross xs ys = concat ( map ( \ x -> concat ( map ( \ y -> [ (x,y) ] ) ys ) ) xs ) besser: cross xs ys = xs >>= \ x -> ys >>= \ y -> return (x,y) noch einfacher lesbar: cross xs ys = do x <- xs y <- ys return (x,y) 98