Funktionale Programmierung Das Funktionale Quiz Warum haben wir den Typ () erst letzte Woche gebraucht? 14.6.2005 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. Bis jetzt hatten wir keine Seiteneffekte, d.h. wir haben Ausdrücke immer angegeben, um ihren Wert zu erfahren. 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! 1-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. 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 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 () Themen heute • Mehr zu I/O • Monaden • Roboter-Steuerung <- 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. 8-14 Mehr I/O Erklärung des do-Konstrukts I/O mit Dateien • type FilePath = String • readFile :: FilePath -> IO String liest Datei ein • writeFile :: FilePath -> String -> IO () schreibt Datei Der Typ IO a besitzt mehere Operationen: return, putStr, getLine • appendFile :: FilePath -> String -> IO () hängt an Datei an do kann Werte vom Typ IO a aneinanderreihen Wie macht do das? 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 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 >>= statt do addOneInt :: IO () addOneInt = do line <- getLine putStrLn (show (1 + read line)) Mit<<=: addOneInt = getLine >>= \line -> putStrLn (show (1 + read line)) 15-18 übersetzung von do nach >>= Monaden 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} Datentyp der Berechnung kapselt Berechung: Auswertung die Seiteneffekt beinhalten kann Sequenzierung mit >>= Ermöglicht es, Seiteneffekte in funktionale Programme einzubetten wobei gilt m >> k = m >>= \_ -> k Monad Geforderte Eigenschaften der Operationen 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 • return x sollte nur den Wert x liefern, ohne Seiteneffekte • >>= ist assoziativ • fail s sollte einer fehlgeschlagenen Berechnung entsprechen • fail entspricht Berechnung, die fehlgeschlagen ist. Für IO: *Main> fail "foo" *** Exception: user error (foo) *Main> 19-22 Formale Definition: Monaden-Gesetze Monadengesetze Ersten beiden Punkte: (return x) >>= f m >>= return (m >>= f) >>= g (M1) und (M2) besagen, dass return die Identität für >>= ist. == f x == m == m >>= (\x -> f x >>= g) Keine formale Möglichkeit, fail zu beschreiben. 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 (M1) (M2) (M3) (M3) besagt, dass >>= assoziativ ist. Aber: Es gibt keine Möglichkeit, (M1) - (M3) automatisch zu überprüfen. 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 23-26 Beispiel Nicht-Determinismus Maybe-Monade 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] instance Monad Maybe where (Just x) >>= k = k x Nothing >>= k = Nothing return = Just fail s = Nothing Interpretation: Berechnung, die fehlschlagen kann flipBool liefert True und False Beispiel Maybe-Monade Zustandsmonade Brechnungen, die Zustand transformieren, d.h. verändern oder auslesen 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 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)) 27-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) Imperative Robotersteuerung • Domain-Specific-Language 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 Roboter-Kommandos Roboter hat eine momentane Richtung, einen Stift und kann Münzen legen und sammeln 1 Schritt vor • Imperativ: Kommandos steuern Roboter move :: Robot () Also Seiteneffekte => Roboter-Monade Robot a turnRight:: Robot () 90º rechts drehen Implementierung in Robot.hs, aber nicht zum Lesen gedacht turnLeft:: Robot () 90º links drehen Roboter laufen lassen: runRobot :: Robot () -> IO () penDown :: Robot () Stift runter penUp :: Robot () Stift hoch 31-34 Beispiel: Winkel zeichnen Weitere Kommandos In eine Richung drehen: data Direction North | South | East | West turnTo :: Direction -> Robot () do penDown move turnRight move Stiftfarbe wechseln data Color = Black | Blue | Green | Cyan | Red | Magenta | Yellow | White deriving (Eq, Ord, Bounded, Enum, Ix, Show, Read) setPenColor :: Color -> Robot () Reaktion auf Umwelt Beispiel 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) Bei Blockade nach rechts drehen evade :: Robot () evade = cond blocked (do turnRight evade) (do move evade) • cond1 :: Robot Bool -> Robot () -> Robot () if ohne Alternative 35-38 Geliftete Operatoren Liften von && und || liftM2 macht Funktion strikt: isnt :: Robot Bool -> Robot Bool isnt = liftM not (>*),(<*) :: Robot Int -> Robot Int -> Robot Bool (>*) = liftM2 (>) (<*) = liftM2 (<) 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 Liften von && und || while für Roboter 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 while :: Robot Bool -> Robot () -> Robot () moveToWall :: Robot () moveToWall = while (isnt blocked) move 39-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