Funktionale Programmierung 14.6.2005 1 Das Funktionale Quiz Warum haben wir den Typ () erst letzte Woche gebraucht? Bis jetzt hatten wir keine Seiteneffekte, d.h. wir haben Ausdrücke immer angegeben, um ihren Wert zu erfahren. 2-3 Das Funktionale Quiz Warum ist es ein Unterschied, ob eine Liste ⊥ als Element enthält oder aus ⊥ aufgebaut ist? Funktion die Liste konsumiert betrachtet Elemente möglicherweise gar nicht. 4-5 Das Funktionale Quiz Haskell-Implementierungen bieten eine Möglichkeit, I/O auch innerhalb von Ausdrücken auszuführen: putStrLn (unsafePerfomIO getLine) Welchen Typ hat unsafePerfomIO? unsafePerformIO :: IO a -> a Führt also I/O-Aktion aus. Gefährlich, nicht in den Übungen verwenden! 6-7 Das Funktionale Quiz IO.isEOF verändert Datei/Filedeskriptor nicht, warum hat es trotzdem den Typ IO Bool und nicht nur Bool? Das Ergebnis von IO.isEOF hängt von anderen I/O-Aktionen ab, daher muss feststehen, vor/nach welchen I/O-Aktionen IO.isEOF ausgewertet wird. 8-9 Das Funktionale Quiz Warum haben wir copyInputToOutput nicht so definiert: copyInputToOutput = while (not isEOF) (putStrLn getLine) isEOF liefert IO Bool, das kann not nicht negieren getLine liefert IO String, das kann putStrLn nicht ausgeben 10-11 Das Funktionale Quiz Worin besteht das Problem in folgendem Programm: goUntilEmpty :: IO () goUntilEmpty = do line <- getLine while (return (line /= [])) (do putStrlen line line <- getLine return () <- sieht zwar aus wie die Zuweisung in C/C++/Java, ist aber ein Bindungskonstrukt, das einen neuen Scope einführt! Im Beispiel ist also die Variable line im Test und in putStrLn ist also immer die erste eingelesene Zeile. 12-13 Themen heute • Mehr zu I/O • Monaden • Roboter-Steuerung 14 Mehr I/O I/O mit Dateien • type FilePath = String • readFile :: FilePath -> IO String liest Datei ein • writeFile :: FilePath -> String -> IO () schreibt Datei • appendFile :: FilePath -> String -> IO () hängt an Datei an Fehler(-Behandlung) • ioError :: IOError -> IO a Erzeugt I/O-Fehler • catch :: IO a -> (IOError -> IO a) -> IO a Fängt I/O-Fehler im ersten Argument mittels zweitem Argument ab 15 Erklärung des do-Konstrukts Der Typ IO a besitzt mehere Operationen: return, putStr, getLine do kann Werte vom Typ IO a aneinanderreihen Wie macht do das? 16 Der Operator >>= do verwendet den Operator >>= ("then" oder "bind") (>>=) :: IO a -> (a -> IO b) -> IO b • >>= reiht 2 I/O-Aktionen aneinander • >>= übergibt den Wert Ergebnis der ersten Aktion an Funktion, die zweite Aktion berechnet • >>= gibt zweite Aktion zurück 17 >>= statt do addOneInt :: IO () addOneInt = do line <- getLine putStrLn (show (1 + read line)) Mit<<=: addOneInt = getLine >>= \line -> putStrLn (show (1 + read line)) 18 übersetzung von do nach >>= do ist nur syntaktischer Zucker do {e} do {e;stmts} do {p <- e; stmts} = e = e >> do {stmts} = let ok p = do {stmts} ok _ = fail "No match" in e >>= ok do {let decls; stmts} = let decls in do {stmts} wobei gilt m >> k = m >>= \_ -> k 19 Monaden Datentyp der Berechnung kapselt Berechung: Auswertung die Seiteneffekt beinhalten kann Sequenzierung mit >>= Ermöglicht es, Seiteneffekte in funktionale Programme einzubetten 20 Monad Monaden sind in Haskell als Konstruktorklasse definiert: class Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a m >> k = m >>= \_ -> k fail s = error s • fail entspricht Berechnung, die fehlgeschlagen ist. Für IO: *Main> fail "foo" *** Exception: user error (foo) *Main> 21 Geforderte Eigenschaften der Operationen • return x sollte nur den Wert x liefern, ohne Seiteneffekte • >>= ist assoziativ • fail s sollte einer fehlgeschlagenen Berechnung entsprechen 22 Formale Definition: Monaden-Gesetze Ersten beiden Punkte: (return x) >>= f m >>= return (m >>= f) >>= g == f x == m == m >>= (\x -> f x >>= g) (M1) (M2) (M3) Keine formale Möglichkeit, fail zu beschreiben. 23 Monadengesetze (M1) und (M2) besagen, dass return die Identität für >>= ist. (M3) besagt, dass >>= assoziativ ist. Aber: Es gibt keine Möglichkeit, (M1) - (M3) automatisch zu überprüfen. 24 Identitätsmonade newtype Id a = Id a instance Monad Id where (Id x) >>= f = f x return x = Id x Repräsentiert triviale Berechung: • Es werden keine Seiteneffekte ausgeführt • Werte werden sofort zurückgegeben 25 Listenmonade instance Monad [] where xs >>= f = concat (map f xs) return x = [x] fail s = [] Interpretation: nicht-deterministische Berechnung • Liste repräsentiert alle möglichen Ergebnisse • >>= wendet Funktion auf alle Ergebnisse an • return erzeugt einzelnes Ergebnis - also kein Nicht-Determinismus • fail liefert gar kein Ergebnis - also einen Fehler 26 Beispiel Nicht-Determinismus flipBool = [True,False] res = do x1 <- flipBool x2 <- flipBool x3 <- flipBool return ((x1 && x2) || (x1 && x3)) => res = [True,True,True,False, False,False,False,False] flipBool liefert True und False 27 Maybe-Monade instance Monad Maybe where (Just x) >>= k = k x Nothing >>= k = Nothing return = Just fail s = Nothing Interpretation: Berechnung, die fehlschlagen kann 28 Beispiel Maybe-Monade teile :: Int -> Int -> Maybe Int teile x 0 = Nothing teile x y = Just (div x y) addiere :: Int -> Int -> Maybe Int addiere x y = Just (x+y) r = do a <- addiere 1 (-1) b <- teile 10 a return (addiere a b) test = r == Nothing 29 Zustandsmonade Brechnungen, die Zustand transformieren, d.h. verändern oder auslesen data StateT s a = StateT (s -> (a,s)) instance Monad (StateT s) where return v = StateT (\s -> (v,s)) (StateT st) >>= f = StateT (\store -> let (v,s1) = st store (StateT st') = f v in st' s1) fetchStateT :: StateT s s fetchStateT = StateT (\s -> (s,s)) storeStateT :: s -> StateT s () storeStateT v = StateT (\_ -> ((), v)) 30 Beispiel Zustandsmonade runStateT :: StateT s a -> s -> (a,s) runStateT (StateT st) init = st init isTwFv :: StateT Integer Bool isTwFv = do x <- fetchStateT storeStateT (x * x) y <- fetchStateT return (y == 25) test = runStateT isTwFv 5 == (True,25) 31 Standardfunktionen für Monaden Im Modul Monad • Liften liftM :: Monad m => (a -> b) -> m a -> m b liftM f a = do a' <- a return (f a') • Analog: liftM2... liftM5 • Plätten join join x :: (Monad m) => m (m a) -> m a = x >>= id 32 Imperative Robotersteuerung • Domain-Specific-Language • Imperativ: Kommandos steuern Roboter Also Seiteneffekte => Roboter-Monade Robot a Implementierung in Robot.hs, aber nicht zum Lesen gedacht Roboter laufen lassen: runRobot :: Robot () -> IO () 33 Roboter-Kommandos Roboter hat eine momentane Richtung, einen Stift und kann Münzen legen und sammeln move :: Robot () 1 Schritt vor turnRight:: Robot () 90º rechts drehen turnLeft:: Robot () 90º links drehen penDown :: Robot () Stift runter penUp :: Robot () Stift hoch 34 Beispiel: Winkel zeichnen do penDown move turnRight move 35 Weitere Kommandos In eine Richung drehen: data Direction North | South | East | West turnTo :: Direction -> Robot () Stiftfarbe wechseln data Color = Black | Blue | Green | Cyan | Red | Magenta | Yellow | White deriving (Eq, Ord, Bounded, Enum, Ix, Show, Read) setPenColor :: Color -> Robot () 36 Reaktion auf Umwelt Roboter lebt in Gitter-Welt, begrenzt durch Wände • blocked :: Robot Bool blockiert? • blockedRight,blockedLeft, blockedBack :: Robot Bool rechts/links/hinten blockiert? Kontrollkonstrukte: • cond :: Robot Bool -> Robot a -> Robot a -> Robot a Bedingte Anweisung (wie if) • cond1 :: Robot Bool -> Robot () -> Robot () if ohne Alternative 37 Beispiel Bei Blockade nach rechts drehen evade :: Robot () evade = cond blocked (do turnRight evade) (do move evade) 38 Geliftete Operatoren isnt :: Robot Bool -> Robot Bool isnt = liftM not (>*),(<*) :: Robot Int -> Robot Int -> Robot Bool (>*) = liftM2 (>) (<*) = liftM2 (<) 39 Liften von && und || liftM2 macht Funktion strikt: liftM2 :: (Monad m) => (a -> b -> c) -> (m a -> m b -> m c) liftM2 f a do a' <b' <return b = a b (f a' b') Unerwünscht für binäre logische Operatoren 40 Liften von && und || Deswegen "zu Fuß" definieren: (||*) :: Robot Bool -> Robot Bool -> Robot Bool b1 ||* b2 = do p <- b1 if p then return True else b2 (&&*) :: Robot Bool -> Robot Bool -> Robot Bool b1 &&* b2 = do p <- b1 if p then b2 else return False 41 while für Roboter while :: Robot Bool -> Robot () -> Robot () moveToWall :: Robot () moveToWall = while (isnt blocked) move 42 Münzen und Position onCoin :: Robot Bool Münze unter Roboter? pickCoin :: Robot () Münze aufnehmen dropCoin :: Robot () Münze ablegen getPosition :: Robot (Int,Int) liefert Position 43