Was bisher geschah I Algebraische Datentypen I Pattern Matching I Funktionen höherer Ordnung I Rekursionsschemata map, fold über Zahlen, Listen, Bäumen, ... I strukturelle Induktion I Polymorphie I Typklassen I Lazy evaluation 99 Funktionen mit Zusatzinformation Funktion f :: Int -> Int erweitern um zusätzliche Information (String) f’ :: Int -> (Int, String) Verkettung zweier Funktionen f,g :: Int -> Int (f . g) x = f (g x) beide mit zusätzlicher Information f’, g’ :: Int -> (Int, String) Nacheinanderausführung let ( y, s ) = g’ x ( z, s’) = f’ y in (z, s ++ s’) umständlich, besser wäre Kombination (Nacheinanderausführung) mit Verknüpfung der Informationen 100 Transformation: Wert → Wert mit Zusatzinformation infos x = (x, "") Transformation: Funktion → Funktion mit Zusatzinformation lift f x = (f x, "") alternative Defintion für lift lift f = infos . f Es gilt also f’ = lift f , g’ = lift g lift lässt sich also durch infos ausdrücken 101 Nacheinanderausführung Definition: nach :: (a -> (b, String) ) -> (a, String) -> (b, String) nach f (y,s) = let (z,s’) = f y in (z, s ++ "." ++ s’) Verwendung: g’ ‘nach‘ ( f’ ‘nach‘ (infos 4))) oder (lift g) ‘nach‘ ( ( lift f) ‘nach‘ (infos 4)) mon-info.hs 102 Datentyp für Wert mit Zusatzinformation f, g :: a -> b data MZ a = MZ (a, String) f’, g’ :: a -> MZ b Nacheinanderausführung let MZ ( y, s ) = g’ x MZ ( z, s’) = f’ y in MZ (z, s ++ "." ++ s’) Kombination (Nacheinanderausführung) mit Verknüpfung der Informationen Definition: nach :: (a -> MZ b) -> MZ a -> MZ b nach f (MZ (y,s)) = let (MZ (z,s’)) = f y in MZ (z, s ++ "." ++ s’) mon-mz.hs 103 Maybe data Maybe a = Just a | Nothing f’ : a -> Maybe b g’ : b -> Maybe c Nacheinanderausführung case ( g’ x ) of Nothing -> Nothing Just y -> f’ y umständlich, besser wäre Kombination (Berechnung) mit automatischer Übergabe von Nothing 104 Fehlermitteilung data MayFail a = Error EC | Result a data EC = Fgross | Gklein f’ : a -> MayFail b g’ : b -> MayFail c Nacheinanderausführung case ( g’ x ) of Error c -> Error c Result y -> f’ y umständlich, besser wäre Kombination (Berechnung) mit automatischer Übergabe der Fehlermitteilung 105 Gemeinsames Muster f :: a -> b g :: b -> c Übergang zu erweitertem Ergebnistyp f’ :: a -> .. b g’ :: b -> .. c Nacheinanderausführung solcher Funktionen mit automatischer Übergabe nach :: (a -> .. b) -> .. a -> .. b 106 in den Beispielen I Zusatzinformation data MZ a = MZ ( a, String nach :: (Int -> MZ Int) -> nach f (MZ (y,s)) = let MZ in MZ (z, s I ) MZ Int -> MZ Int (z,s’) = f y ++ "." ++ s’) Maybe nach :: (Int -> Maybe Int) -> Maybe Int -> Maybe Int nach f y = case y of Nothing -> Nothing Just y -> f y I Fehlermitteilung nach :: (Int -> MayFail Int) -> MayFail Int -> MayFail Int nach f y = case y of Error c -> Error c Result y -> f y 107 Monaden Ziel: Verknüpfung von Berechnungen zu komplexeren Berechnungen class Monad m where return :: a -> m a ( >>= ) :: m a -> (a -> m b) -> m b mit den Operationen: return (Eingang) return :: a -> m a bind (sequentielle Verknüpfung) (>>=) :: m a -> (a -> m b) -> m b Monaden-Gesetze: I (return x) >>= f == f x I m >>= return == m I ( m >>= f) >>= g == m >>= (\ x -> f x >>= g) 108 Beispiel Zusatzinformation data MZ a = MZ (a, String) instance Monad MZ where -- return :: Int -> MZ Int return x = (x, "") -- (>>=) :: MZ Int -> (Int -> MZ Int) -> MZ Int (y,s) >>= f = let (z,s’) = f y in (z, s ++ "." ++ s’) 109 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 ) mit Hilfe der Maybe-Monade instance Monad Maybe where return = \ x -> Just x m >>= f = case m of Nothing -> Nothing Just x -> f x kürzer und übersichtlicher: evaluate e l >>= \ a -> evaluate e r >>= \ b -> return ( a + b ) 110 MayFail-Monade data MayFail a = Error EC | Result a data EC = Fgross | Gklein statt: case ( evaluate e l ) of Error c -> Error c Result a -> case ( evaluate e r ) of Error c -> Error c Result b -> Result ( a + b ) mit Hilfe der MayFail-Monade instance Monad MayFail where return = \ x -> Just x m >>= f = case m of Error c -> Error c Result b -> f b kürzer und übersichtlicher: evaluate e l >>= \ a -> evaluate e r >>= \ b -> return ( a + b ) 111 Listen-Monade instance Monad [a] 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) 112 Do-Notation für Monaden Beispiel: original eval e l >>= \ a -> eval e r >>= \ b -> return ( a + b ) do-Notation (implizit geklammert) do a <- eval e l b <- eval 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 113 Berechnungen mit Nebenwirkungen Nebenwirkungen sind mitunter erwünscht, z.B. I von-Neumann-Maschine: Änderungen im Hauptspeicher I Modellierung von Prozessen mit Zustandsübergängen Zustände haben Typ State Übergang von Val zu State -> (State, Val) 114 Zustands-Monade data State s a = ST ( s -> (a,s) ) instance Monad State where return :: a -> State s a return x = ST (\x -> (x,s)) (>>=) :: State s a -> (a -> State s b) -> State s b (ST m) >>= f = ST (\s -> let (x,s1) = m s ST f’ = f x in f’ s1) Verwendung: import Control.Monad.State tick :: State Integer () tick = do c <- get ; put $ c + 1 evalState ( do tick ; tick ; get ) 0 115 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 >>= ) 116 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’ 117 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 ... 118 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 119 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 120 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 121 Ein- und Ausgabe-Anweisungen Eingabe: x <- A mit Aktion A (erzeugt Variablenbindung) Beispiel (Kombination): echoline :: IO () echoline = do input <- getLine putStr input 122 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) 123 Hakell-Compiler ghc Programm hello.hs: module Main where main = putStrLn "Hello" mit ghc kompilieren ghc -o hello hello.hs und ausführen: ./hello 124 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 sumlist.hs ./sumlist oder interpretieren (wie bisher) mit ghci: :l Main main 125 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 126 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 127 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) 128 Beipiel Datei-Ein- und Ausgabe readFile :: FilePath -> IO String putStrLn :: String -> IO () Wegen der Monad-Instanz: do-Notation do cs <- readFile "foo.bar" putStrLn cs main :: IO () main = do putStr " Filename:" fname <- getLine cont <- readFile fname putStr cont 129