Funktionale Systemprogrammierung Haskell für Erwachsene Christoph Lüth 15. Juli 2002 Funktionale Systemprogrammierung 1 Inhalt • • • • • Monadische Ein/Ausgabe Nebenläufigkeit Ausnahmen und Fehlerbehandlung Sprachüberschreitende Funktionsaufrufe Effizienzaspekte Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Systemprogrammierung 2 Grundlage Simon Peyton Jones. Tackling the awkward squad: monadic input/output, concurrency, exceptions and foreign-language calls in haskell. In Tony Hoare, Manfred Broy, and Ralf Steinbrüggen, editors, Engineering theories of software construction, pages 47– 96. IOS Press, 2001. http://www.research.microsoft.com/ Users/simonpj/papers/marktoberdorf/ Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 4 Inhalt • • • • • • Ein/Ausgabe in funktionalen Sprachen Wo ist das Problem? Der Datentyp IO. Vordefinierte Funktionen für E/A. Beispiel: Nim Wie würde man E/A implementieren? Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe Ein- und Ausgabe in funktionalen Sprachen • Problem: Funktion“ readLine :: () -> String würde ” referentielle Transparenz zerstören. • Lösung: Abhängigkeit von der Umwelt am Typ IO erkennbar. IO als abstrakter Datentyp: ◦ IO t Funktion vom Typ t, die Ein/Ausgabe betreibt (Aktion) ◦ Aktionen können nur mit Aktionen komponiert werden einmal IO, immer IO“ ” Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 5 Funktionale Ein/Ausgabe • IO als abstrakter Datentyp: type IO t (>>=) :: IO a-> (a-> IO b)-> IO b return :: a-> IO a • Vordefinierte Funktionen (Prelude): ◦ Zeile von stdin lesen: getLine :: IO String ◦ String ausgeben: putStr :: String-> IO () ◦ String mit Zeilenvorschub ausgeben: putStrLn :: String-> IO () Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 6 Funktionale Ein/Ausgabe • Ein einfaches Beispiel: echo :: IO () echo = getLine >>= putStrLn >>= \_ -> echo • Immer noch einfach: ohce :: IO () ohce = getLine >>= putStrLn . reverse >> ohce • Vordefinierte Abkürzung: (>>) :: IO t-> IO u-> IO u f >> g = f >>= \_ -> g Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 7 Funktionale Ein/Ausgabe Syntaktischer Zucker für IO • Abkürzende Schreibweise: echo = echo = do s<- getLine getLine putStrLn s >>= \s-> putStrLn s ⇐⇒ >> echo echo ◦ Rechts sind >>=, >> implizit. • Es gilt die Abseitsregel. ◦ Einrückung der ersten Anweisung nach do bestimmt Abseits. ◦ Ansonsten sagt hugs: Last generator in do {...} must be an expression Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 8 Funktionale Ein/Ausgabe 9 Ein/Ausgabe in Haskell98 • • • • Ein/Ausgabe aus Dateien und stdin,out (Modul IO) Fehlerbehandlung Zufallszahlen (Modul Random) Zugriff auf das Dateisystem (Modul Directory) ◦ Leider nicht in hugs. • Kommandozeile, Umgebungsvariablen (Modul System) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 10 Ein/Ausgabe mit Dateien • Im Prelude vordefiniert: ◦ Dateien schreiben (überschreiben, anhängen): type FilePath = String writeFile :: FilePath -> String -> IO () appendFile :: FilePath -> String -> IO () ◦ Datei lesen (verzögert): readFile :: FilePath -> IO String • Mehr Operationen im Modul IO der Standardbücherei ◦ Buffered/Unbuffered, Seeking, &c. ◦ Operationen auf Handle Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe • Ein Beispiel: wc :: String-> IO () wc file = do c<- readFile file putStrLn (show (length (lines ++ " putStrLn (show (length (words ++ " putStrLn (show (length c)++ " 11 c)) lines ") c)) words, and ") characters. ") ◦ Nicht sehr effizient — Inhalt der Datei wird im Speicher gehalten. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe Noch ein Beispiel: Nim revisited • Implementation von Nim: ◦ Am Anfang Anzahl der Hölzchen auswürfeln. ◦ Eingabe des Spielers einlesen. ◦ Wenn nicht mehr zu gewinnen, aufgeben, ansonsten ziehen. ◦ Wenn ein Hölzchen über ist, hat Spieler verloren. • Zufallszahlen: Modul Random ◦ class Random a where randomRIO :: (a, a)-> IO a randomIO :: IO a ◦ Instanzen von Random: Basisdatentypen. ◦ Random enthält ferner Zufallsgeneratoren für Pseudozufallszahlen. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 12 Funktionale Ein/Ausgabe • Nim revisited: ◦ Importe und Hilfsfunktionen: import Random (randomRIO) import IO(hFlush,stdout) ◦ wins wins wins if liefert Just n, wenn Zug n gewinnt; ansonsten Nothing :: Int-> Maybe Int n = m == 0 then Nothing else Just m where m = (n- 1) ‘mod‘ 4 Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 13 Funktionale Ein/Ausgabe 14 ◦ Hauptfunktion: play :: Int-> IO () play n = do putStrLn ("Es sind "++ show n ++ " Hölzchen im Haufen.") if n== 1 then putStrLn "Ich habe gewonnen!" else do m<- getInput case wins (n-m) of Nothing -> putStrLn "Ich gebe auf." Just l -> do putStrLn ("Ich nehme " ++ show l) play (n-(m+l)) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe ◦ Noch zu implementieren: Benutzereingabe getInput’ :: IO Int getInput’ = do putStr "Wieviele nehmen Sie? " n <- do s<- getLine return (read s) if n<= (0::Int) || n>3 then do putStrLn "Ungültige Eingabe!" getInput’ else return n ◦ Nicht sehr befriedigend: Abbruch bei falscher Eingabe. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 15 Funktionale Ein/Ausgabe • Fehlerbehandlung: ◦ Fehler werden durch abstrakten Datentyp IOError repräsentiert ◦ Fehlerbehandlung durch Ausnahmen (ähnlich Java) ioError :: IOError -> IO a -- "throw" catch :: IO a-> (IOError-> IO a) -> IO a ◦ Nur in IO t können Fehler behandelt werden. ◦ Fangbare Benutzerfehler mit userError::String-> IOError. ◦ IOError kann analysiert werden— Auszug aus Modul IO: isIllegalOperation :: IOError -> Bool isPermissionError :: IOError -> Bool isUserError :: IOError -> Bool ioeGetErrorString :: IOError -> String ioeGetFileName :: IOError -> Maybe FilePath Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 16 Funktionale Ein/Ausgabe 17 • Robuste Eingabe: ◦ readIO :: Read a=> String-> IO a wirft im Fehlerfall Ausnahme, die gefangen werden kann. getInput :: IO Int getInput = do putStr "Wieviele nehmen Sie? " hFlush stdout n <- catch (do s<- getLine; readIO s) (\_ -> do putStrLn "Eingabefehler." getInput) if n<=(0::Int) || n>3 then do putStrLn "Ungültige Eingabe!"; getInput else return n Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe • Haupt- und Startfunktion: ◦ Begrüßung ◦ Anzahl Hölzchen auswürfeln ◦ starten. main :: IO () main = do putStrLn "\nWillkommen bei Nim!\n" n <- randomRIO(5,49) play n Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 18 Funktionale Ein/Ausgabe 19 Aktionen als Werte • Aktionen sind Werte wie alle anderen. • Dadurch Definition von Kontrollstrukturen möglich: ◦ Endlosschleife: forever :: IO a-> IO a forever a = a >> forever a ◦ Iteration (feste Anzahl) forN :: Int-> IO a-> IO () forN n a | n == 0 = return () | otherwise = a >> forN (n-1) a ◦ Besser: forN n = sequence_ . (take n). repeat Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe ◦ Iteration (variabel, wie for in Java) for :: (a, a-> Bool, a-> a)-> (a-> IO ())-> IO () for (start, cont, next) cmd = iter start where iter s = if cont s then cmd s >> iter (next s) else return () Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 20 Funktionale Ein/Ausgabe • Vordefinierte Kontrollstrukturen (Prelude) ◦ Listen bearbeiten: sequence :: [IO a]-> IO [a] sequence (c:cs) = do x <- c xs <- sequence cs return (x:xs) sequence_ :: [IO ()]-> IO () ◦ Map für Monaden: mapM :: (a-> IO b)-> [a]-> IO [b] mapM f = sequence . map f mapM_ :: (a-> IO ())-> [a]-> IO () mapM_ f = sequence_ . map f ◦ Andere (wie filterM) im Modul Monad. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 21 Funktionale Ein/Ausgabe Der Glasgow Haskell Compiler. • Es muss ein Hauptmodul mit einer Hauptfunktion geben: module Main where ... main :: IO () • Aufruf mit ghc [-o Executable] file.hs ◦ Viele, viele Optionen! • Pakete einbinden mit -package, z.B. ◦ lang für IOExts und Freunde ◦ concurrent für Nebenläufigkeit • Interaktive Version: ghci • Integriertes Make: ghc --make. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 22 Funktionale Ein/Ausgabe 23 Ein/Ausgabe mit dem ghc Das Posix-Modul bietet • Prozeßprimitive (exec, fork) • Prozeßumgebung und Systemdatenbasis • Operationen auf Dateien • Primitive I/O (Operationen auf fd) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 24 Referenzen Referenzen sind veränderliche Variablen: data IORef a newIORef :: a-> IO (IORef a) readIORef :: IORef a-> IO a writeIORef :: IORef a-> a-> IO () Zur Benutzung: import IOExts Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 25 Beispiel: fac revisited. fac :: Int-> IO Int fac n = do r<- newIORef 1 for (1, (<= n), (+1)) (multRef r) readIORef r where multRef :: IORef Int-> Int-> IO () multRef r m = do n<- readIORef r writeIORef r (n*m) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 26 IO für Profis • IO sorgt nur für die richtige Reihenfolge. • Manchmal ist das unnötig: ◦ Reihenfolge definiert (Aktion immer am Anfang, z.B. Initialisierung) ◦ Reihenfolge egal (von anderen Aktionen unabhängig, Debuggingausgaben) • Aus dem Modul IOExts: unsafePerformIO :: IO a-> a • Handle with extreme care! ◦ Zum Beispiel: inlining verhindern {-# NOINLINE ... #-} Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe Der Blick hinter die Kulissen. • IO a ist keine schwarze Magie. • Grundprinzip: ◦ Der Systemzustand wird durch das Programm gereicht. ◦ Darf dabei nie dupliziert werden. ◦ Auswertungsreihenfolge muß erhalten bleiben. • Implementation: ◦ Mit Systemzustand S: type IO’ a = (S-> (a, S)) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 27 Funktionale Ein/Ausgabe 28 • Komposition: ◦ Wir wissen: a-> s-> (b, s) ∼ = (a, s)-> (b, s) ◦ Damit Definition von (>>=): (>>=) :: IO’ a-> (a-> IO’ b)-> IO’ b :: s-> (a, s)-> (a-> (s-> (b, s))) -> (s-> (b, s)) :: s-> (a, s)-> ((a, s)-> (b, s)) -> (s-> (b, s)) ◦ Entspricht (>.>) :: (a-> b)-> (b-> c)-> (a-> c) ◦ Im Prinzip: (>>=) :: IO’ a-> (a-> IO’ b)-> IO’ b f >>= g = f >.> uncurry g Aber: Typsynonym nicht abstrakt. (>>=) gehört zu Klasse Monad. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe • Komposition: ◦ IO’ als abstrakter Datentyp, Systemzustand s als Parameter: data IO’ s a = IO’ (s-> (a, s)) ◦ Lokale Funktion: IO’ auspacken unwrap :: IO’ s a-> (s-> (a, s)) unwrap (IO’ f) = f ◦ Instanz der Typklasse Monad: instance Monad (IO’ s) where f >>= g = IO’ (unwrap f >.> uncurry (unwrap. g)) return a = IO’ (\s-> (a, s)) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 29 Funktionale Ein/Ausgabe 30 • Implementation von Ein/Ausgabe: ◦ Systemzustand: Eingabestrom und Ausgabestrom type IO1 = IO’ (String, String) ◦ Eingabe: ersten String in Eingabestrom getLine’ :: IO1 String getLine’ = IO’ f where f (i, o)= (hd, (if null tl then tl else tail tl, o)) where (hd, tl) = span (/= ’\n’) i ◦ Ausgabe: String an den Ausgabestrom hängen putStr’ :: String-> IO1 () putStr’ s = IO’ (\(i, o)-> ((), (i, o++ s))) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 31 ◦ Programm laufen lassen: run :: IO1 ()-> String-> String run prog i = o where IO’ p = prog ((), (_, o))= p (i, []) ◦ Beispielprogramm: countXs :: IO1 () countXs = cnt 0 where cnt x = do putStr’ ("Found "++ show x ++ " crosses.\n") s<- getLine’ if null s then return () else cnt (x+ length (filter (’x’ ==) (map toLower s))) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Funktionale Ein/Ausgabe 32 Zusammenfassung • Ein/Ausgabe in Haskell durch IO a: ◦ Berechungen vom IO a hängen von Umwelt ab. ◦ Komposition von IO a durch (>>=) :: IO a-> (a-> IO b)-> IO b return :: a-> IO a ◦ Fehlerbehandlung durch Ausnahmen (IOError, catch). • Verschiedene Funktionen der Standardbücherei: ◦ Prelude: getLine, putStr, putStrLn ◦ Module: IO, Random • Implementation von IO: ◦ Explizite Modellierung des Systemzustandes. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Grundlagen der nebenläufigen Programmierung in Haskell Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Grundlagen der nebenläufigen Programmierung in Haskell 34 Konzepte der Nebenläufigkeit • Thread (lightweight process) vs. Programmiersprache Prozess Betriebssystem (z.B. Java, Haskell) gemeinsamer Speicher getrennter Speicher mehrere pro Programm einer pro Programm • Multitasking: präemptiv: Kontextwechsel kann erzwungen werden kooperativ: Kontextwechsel nur freiwillig Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Grundlagen der nebenläufigen Programmierung in Haskell 35 Zur Erinnerung: Threads in Java • • • • • Erweiterung der Klassen Thread oder Runnable Gestartet wird Methode run() — durch eigene überladen Starten des Threads durch Aufruf der Methode start() Kontextwechsel mit yield() Je nach JVM kooperativ oder präemptiv. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Grundlagen der nebenläufigen Programmierung in Haskell Threads in Haskell: Concurrent Haskell • Sequentielles Haskell: Reduktion eines Ausdrucks • Nebenläufiges Haskell: Reduktion eines Ausdrucks an mehreren Stellen • ghc (und Hugs) implementieren Haskell-Threads • ghc: präemptiv, Hugs: kooperativ • Modul Concurrent enthält Basisfunktionen • Wenige Basisprimitive, darauf aufbauend Abstraktionen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 36 Concurrent Haskell 37 Concurrent Haskell • Jeder Thread hat einen Identifier — Typ ThreadId • Neuen Thread erzeugen: forkIO:: IO()-> IO ThreadId • Thread stoppen: killThread :: ThreadId -> IO () • Kontextwechsel: yield :: IO () • Eigener Thread: myThreadId :: IO ThreadId Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Concurrent Haskell • Warten: threadDelay :: Int -> IO () ◦ Argument in Mikrosekunden ◦ Auflösung ∼ = 50ms • Blockierung: ◦ Blockierende Systemaufrufe blockieren alle Threads (den gesamten Haskell-Prozeß) ◦ Aber: Haskell Standard-IO blockiert nur den aufrufenden Thread Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 38 Concurrent Haskell Concurrent Haskell — erste Schritte Ein einfaches Beispiel: import Concurrent write :: Char-> IO () write c = putChar c >> write c main :: IO () main = forkIO (write ’X’) >> write ’O’ Übersetzen: ghc -package concurrent simple.hs Mit Hugs keine besonderen Optionen nötig. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 39 Synchronisation mit MVars 40 Synchronisation mit MVars • MVar a veränderbare Variable: • Entweder leer oder gefüllt mit einem a • Verhalten beim Lesen und Schreiben Zustand vorher: leer Lesen blockiert Schreiben danach gefüllt Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 gefüllt danach leer ? Synchronisation mit MVars • Neue Variable erzeugen (leer oder gefüllt): newEmptyMVar :: IO (MVar a) newMVar :: a -> IO (MVar a) • Lesen: takeMVar :: MVar a -> IO a • Schreiben: putMVar :: MVar a -> a -> IO () • Nicht-blockierendes Lesen: tryTakeMVar :: MVar a -> IO (Maybe a) • Test (Achtung: Zustand kann sich ändern) isEmptyMVar :: MVar a -> IO Bool Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 41 Synchronisation mit MVars 42 Ein einfaches Beispiel ohne Synchronisation • Nebenläufige Eingabe von der Tastatur echo :: String-> IO () echo p = do putStrLn ("\nPlease enter line for "++p) line <- getLine randomRIO (1,100) >>= \n-> forN n (putStr (p++ line)) echo p main :: IO () main = forkIO (echo "1") >> echo "2" • Problem: gleichzeitige Eingabe Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Synchronisation mit MVars 42 Ein einfaches Beispiel ohne Synchronisation • Nebenläufige Eingabe von der Tastatur echo :: String-> IO () echo p = do putStrLn ("\nPlease enter line for "++p) line <- getLine randomRIO (1,100) >>= \n-> forN n (putStr (p++ line)) echo p main :: IO () main = forkIO (echo "1") >> echo "2" • Problem: gleichzeitige Eingabe • Lösung: MVar synchronisiert Eingabe Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Synchronisation mit MVars 43 Ein einfaches Beispiel mit Synchronisation • MVar voll ⇔ Eingabe möglich ◦ Also: initial voll • Inhalt der MVar irrelevant: MVar () echo :: MVar ()-> String-> IO () echo flag p = do takeMVar flag putStrLn ("\nPlease enter line "++p) line <- getLine putMVar flag () randomRIO (1,100) >>= \n-> forN n (putStr (p++ line)) yield -- noetig, da sonst keine Fairness. echo flag p Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Synchronisation mit MVars Das Standardbeispiel: Die speisenden Philosophen • Philosoph i: ◦ vor dem Essen i-tes und (i + 1) mod n-tes Stäbchen nehmen ◦ nach dem Essen wieder zurücklegen • Stäbchen modelliert als MVar () Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 44 Synchronisation mit MVars 45 • i-ter Philosoph: philo :: [MVar ()] -> Int-> IO () philo chopsticks i = let num_phil = length (chopsticks) in do putStrLn ("Phil #"++(show i)++" thinks...") takeMVar (chopsticks !! i) takeMVar (chopsticks !! ((i+1) ‘mod‘ num_phil)) putStrLn ("Phil #"++(show i)++" eats...") putMVar (chopsticks !! i) () putMVar (chopsticks !! ((i+1) ‘mod‘ num_phil)) () philo chopsticks i Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Synchronisation mit MVars 46 • Hauptfunktion: n Stäbchen erzeugen • Anzahl Philosophen in der Kommandozeile main :: IO () main = do num <- getArgs >>= \a-> return (read (head a)) chopsticks <- sequence (take num (repeat (newMVar ()))) mapM_ (forkIO . (philo chopsticks)) [0.. num-1] block • Hilfsfunktion block: blockiert aufrufenden Thread block :: IO () block = newEmptyMVar >>= takeMVar Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktionen 47 Abstraktionen Mit MVars mächtigere Synchronisationskonzepte implementieren: • Lese/Schreiber-Probleme: Buffer und buffernde Kanäle • Semaphoren (allgemein, quantitativ) • Monitore und Mutexe • Buffer, Kanäle, Semaphoren schon im Modul Concurrent Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktionen I: Puffer 48 Abstraktionen I: Puffer • • • • Leser-Schreibe-Problem: Kein Überschreiben. Eine MVar a für Daten Schreiber→Leser Eine MVar () für Synchronisation Leser→Schreiber Neuen Puffer erzeugen: data CVar a = CVar (MVar a) (MVar ()) newCVar :: IO (CVar a) newCVar = do datum <- newEmptyMVar ack <- newMVar () return (CVar datum ack) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktionen I: Puffer • Puffer schreiben: writeCVar :: CVar a -> a -> IO () writeCVar (CVar datum ack) val = do takeMVar ack putMVar datum val return () • Puffer lesen: readCVar :: CVar a -> IO a readCVar (CVar datum ack) = do val<- takeMVar datum putMVar ack () return val • Nachteil: immer nur einen Wert zur Zeit. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 49 Abstraktion II: Kanäle 50 Abstraktion II: Kanäle • Ein Kanal hat einen Strom mit einem Lese- und Schreibende: data Chan a = Chan (MVar (Stream a)) (MVar (Stream a)) ◦ Hier MVar, damit Lesen/Schreiben nicht unterbrochen wird • Ein Strom ist ◦ entweder leer, ◦ oder enthält Werte (Typ ChItem a) aus Kopf a und Rest. type Stream a = MVar (ChItem a) data ChItem a = ChItem a (Stream a) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktion II: Kanäle • Schnittstelle: newChan :: IO (Chan a) writeChan :: Chan a -> a -> IO () readChan :: Chan a -> IO a • Neuen Kanal erzeugen: newChan :: IO (Chan a) newChan = do hole <- newEmptyMVar read <- newMVar hole write <- newMVar hole return (Chan read write) ◦ NB: Leseende = Schreibende Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 51 Abstraktion II: Kanäle 52 In einen Kanal schreiben • Neues Ende (hole) anlegen • Wert in altes Ende schreiben • Zeiger auf neues Ende setzen writeChan (Chan _read write) val = do new_hole <- newEmptyMVar old_hole <- takeMVar write putMVar write new_hole putMVar old_hole (ChItem val new_hole) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktion II: Kanäle 53 Aus Kanal lesen • Anfang auslesen, Anfangszeiger weitersetzen • Kann blockieren (wenn Kanal leer) readChan (Chan read _write) = do read_end <- takeMVar read (ChItem val new_read_end) <- readMVar read_end putMVar read new_read_end return val ◦ readMVar :: MVar a-> IO a liest MVar, schreibt Wert zurück. ◦ readMVar statt takeMVar, um Duplikation zu ermöglichen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktion II: Kanäle 54 Weitere Kanalfunktionen • Zeichen wieder vorne einhängen: unGetChan :: Chan a -> a -> IO () • Kanal duplizieren (Multicast): dupChan :: Chan a -> IO (Chan a) • Kanalinhalt als (unendliche) Liste: getChanContents :: Chan a -> IO [a] ◦ Auswertung terminiert nicht, sondern blockiert Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktionen III: Semaphoren Abstraktionen III: Semaphoren • Abstrakter Datentyp QSem • Betreten des kritischen Abschnitts (P): waitQSem :: QSem -> IO () • Verlassen des kritischen Abschnitts (V): signalQSem :: QSem -> IO () • Semaphore: Zähler plus evtl. wartende Threads ◦ P erniedrigt Zähler, blockiert ggf. aufrufenden Thread ◦ V erhöht Zähler, gibt ggf. blockierte Threads frei • Implementierung von Semaphoren mit MVar: eigenes Scheduling Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 55 Abstraktionen III: Semaphoren Semaphoren: die P-Operation data QSem = QSem (MVar (Int, [MVar ()])) newQSem :: Int -> IO QSem waitQSem :: QSem -> IO () waitQSem (QSem sem) = do (avail,blocked) <- takeMVar sem if avail > 0 then putMVar sem (avail-1,[]) else do block <- newEmptyMVar putMVar sem (0, blocked++[block]) takeMVar block Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 56 Abstraktionen III: Semaphoren Semaphoren: die V-Operation signalQSem :: QSem -> IO () signalQSem (QSem sem) = do (avail,blocked) <- takeMVar sem case blocked of [] -> putMVar sem (avail+1,[]) (block:blocked’) -> do putMVar sem (0,blocked’) putMVar block () • Alternatives Scheduling: ◦ am Anfang hinzufügen, vom Anfang nehmen (einfacher, kann aushungern) ◦ zufällige Auswahl Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 57 Abstraktion IV: Monitore und Mutexe Abstraktion IV: Monitore und Mutexe • Monitore schützen Aktionen • Modellierung als Typklasse: class Synchronize v where synchronize :: v -> IO a -> IO a ◦ Parameter v: Datenstruktur über der synchronisiert wird • Beispiel: Semaphoren instance Synchronize QSem where synchronize s a = do waitQSem s r<- a signalQSem s return r Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 58 Abstraktion IV: Monitore und Mutexe Reentrante Mutexe • Problem: Semaphoren (u.a.) nicht reentrant, keine Rekursion möglich • Lösung: reentrantes Mutex (mutual exclusion) • Operationen: acquire und release • Mutex enthält: ◦ ThreadId des besitzenden Threads, Zähler für Rekursionstiefe ◦ Liste von blockierten Threads (Id, MVar) data Mutex = Mutex (MVar (Maybe (ThreadId,Int), [(ThreadId, MVar ())])) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 59 Abstraktion IV: Monitore und Mutexe 60 • Mutex akquirieren: acquire :: Mutex -> IO () ◦ Wenn Thread Mutex besitzt, dann Zähler erhöhen; ◦ sonst neue MVar () erzeugen, ThreadId und MVar in Liste einhängen, auf MVar warten; • Mutex freigeben: release :: Mutex -> IO () ◦ Prüfen, ob Thread Mutex besitzt (sonst Fehler) ◦ Wenn Zähler 1 und Liste leer, wird Mutex leer ◦ Wenn Zähler 1, dann ersten Thread aus Liste aufwecken ◦ Wenn Zähler größer 1, dann erniedrigen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktion IV: Monitore und Mutexe 61 • Mutex akquirieren: acquire :: Mutex -> IO () acquire (Mutex mvar) = do st <- takeMVar mvar current <- myThreadId case st of (Nothing,[]) -> putMVar mvar (Just (current,1),[]) (Just (holder,n),pnd) -> if current == holder then putMVar mvar (Just (holder,n+1),pnd) else do bsem <- newEmptyMVar putMVar mvar (Just (holder,n), (current,bsem):pnd) takeMVar bsem Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktion IV: Monitore und Mutexe 62 release :: Mutex -> IO () release (Mutex mvar) = do st <- takeMVar mvar current <- myThreadId case st of (Just (h,n),pnd) | current == h -> release’ mvar h n pnd _ -> do putMVar mvar st error "Illegal lock release" where release’ mvar _ 1 [] = putMVar mvar (Nothing,[]) release’ mvar _ 1 ((h’,sem):pnd’) = do putMVar mvar (Just (h’,1),pnd’) putMVar sem () release’ mvar h n pnd = putMVar mvar (Just (h,n-1), pnd) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Abstraktion IV: Monitore und Mutexe Reentrante Monitore Mit Mutex Implementierung reentranter Monitore: instance Synchronize Mutex where synchronize m a = do acquire m r<- a release m return r Rekursion möglich (aber teuer). Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 63 Abstraktion IV: Monitore und Mutexe Zusammenfassung Concurrent Haskell bietet • Threads auf Quellsprachenebene Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 64 Abstraktion IV: Monitore und Mutexe Zusammenfassung Concurrent Haskell bietet • Threads auf Quellsprachenebene • Synchronisierung mit MVars Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 64 Abstraktion IV: Monitore und Mutexe Zusammenfassung Concurrent Haskell bietet • Threads auf Quellsprachenebene • Synchronisierung mit MVars • Durch schlankes Design einfache Implementierung Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 64 Abstraktion IV: Monitore und Mutexe Zusammenfassung Concurrent Haskell bietet • Threads auf Quellsprachenebene • Synchronisierung mit MVars • Durch schlankes Design einfache Implementierung • Funktionales Paradigma erlaubt Abstraktionen: Channels, Semaphoren, Monitore, . . . Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 64 Abstraktion IV: Monitore und Mutexe 64 Zusammenfassung Concurrent Haskell bietet • Threads auf Quellsprachenebene • Synchronisierung mit MVars • Durch schlankes Design einfache Implementierung • Funktionales Paradigma erlaubt Abstraktionen: Channels, Semaphoren, Monitore, . . . • Haskell: the world’s finest imperative concurrent language. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk Längeres Beispiel: It’s good to talk • Ziel: ein Programm, um sich über das Internetz zu unterhalten (talk, IRC, etc.) • Dazu: ein Server mit netzweit bekannter Adresse, zu dem Verbindung aufgenommen wird, und der Nachrichten an alle angeschlossenen Clients weiterverteilt. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 65 Längeres Beispiel: It’s good to talk Socketprogrammierung • Serverseite: ◦ Socket erzeugen, mit bind an Namen binden, mit listen Verbindungsbereitschaft anzeigen ◦ Mit accept auf eingehende Verbindungen warten und annehmen. ◦ Jede Verbindung erzeugt neuen Filedescriptor ⇒ inhärent nebenläufiges Problem! • Clientseite: Socket erzeugen, mit connect Verbindung aufnehmen • GHC-Modul Socket Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 66 Längeres Beispiel: It’s good to talk 67 Module Socket where type Socket data PortID = Service String -- z.B. "ftp" | PortNumber PortNumber | UnixSocket String -- socket in file system type Hostname = String mkPortNumber :: Int-> PortNumber -- Serverside: listenOn :: PortID-> IO Socket accept :: Socket-> IO (Handle, Hostname, PortNumber) -- Clientside: connectTo :: Hostname -> PortID -> IO Handle socketPort :: Socket-> IO PortID Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk 68 Serverarchitektur • Ein Kanal zur Nachrichtenverbreitung: ◦ eine Nachricht, viele Empfänger (broadcast) ◦ Realisierung mittels dupChan • Zentraler Scheduler • Für jede ankommende Verbindung neuer Thread: ◦ Nachrichten vom Socket auf den Kanal schreiben ◦ Nachrichten vom Kanal in den Socket schreiben Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk • Problem: Wie aus Socket oder Kanal lesen wenn beide blockieren? Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 69 Längeres Beispiel: It’s good to talk • Problem: Wie aus Socket oder Kanal lesen wenn beide blockieren? • Nicht mit select (BSD) oder poll (SysV) blockieren gesamten Haskell-Prozeß Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 69 Längeres Beispiel: It’s good to talk • Problem: Wie aus Socket oder Kanal lesen wenn beide blockieren? • Nicht mit select (BSD) oder poll (SysV) blockieren gesamten Haskell-Prozeß • Lösung: Zwei Threads Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 69 Längeres Beispiel: It’s good to talk • Problem: Wie aus Socket oder Kanal lesen wenn beide blockieren? • Nicht mit select (BSD) oder poll (SysV) blockieren gesamten Haskell-Prozeß • Lösung: Zwei Threads • Clientarchitektur: telnet Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 69 Längeres Beispiel: It’s good to talk Talk 0.1: Hauptprogramm main :: IO () main = do port_num <- getArgs >>= return . read . head s <- listenOn (PortNumber (fromInteger port_num)) ch<- newChan loop s ch Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 70 Längeres Beispiel: It’s good to talk 71 Talk 0.1: Hauptschleife loop :: Socket -> Chan String -> IO () loop s ch = do (handle, wh, p) <- accept s hSetBuffering handle NoBuffering putStrLn ("New connection from "++ wh++ " on "++ show p) ch2 <- dupChan ch forkIO (newUser handle ch2) loop s ch Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk Talk 0.1: Benutzerprozeß newUser :: Handle-> Chan String -> IO () newUser socket msgch = forkIO read >> write where read :: IO () read = hGetLine socket >>= writeChan msgch >> read write :: IO () write = readChan msgch >>= hPutStrLn socket >> write Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 72 Längeres Beispiel: It’s good to talk 73 Talk 0.1: Zusammenfassung Nachteile: • Nachrichten stauen sich im Kanal • Serverprozess fällt um, wenn Benutzer Verbindung beendet • Keine Fehlerbehandlung • Benutzer anonym Übersetzen mit -package concurrent -package net Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk 74 Talk 0.2: Hauptprogramm main :: IO () main = do port_num <- getArgs >>= return . read . head :: IO Intege s <- listenOn (PortNumber (fromInteger port_num)) ch <- newChan loop s ch • Kanäle nach Bedarf erzeugen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk 75 Talk 0.2: Hauptschleife loop :: Socket -> Chan String -> IO () loop s ch = do (handle, wh, p) <- accept s hSetBuffering handle NoBuffering installHandler sigPIPE Ignore Nothing putStrLn ("New connection from "++ wh++ " on port "++ sho ch2 <- dupChan ch forkIO (catch (newUser handle wh ch2) (\_ -> hClose handle)) loop s ch2 • Fehlerbehandlung für newUser, SIGPIPE ignorieren Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk 76 Talk 0.2: Benutzerprozeß newUser :: Handle-> String-> Chan String -> IO () newUser socket wh msgch = do hPutStrLn socket "Hello there. Please send your nickname. nick <- do nm <- hGetLine socket return (filter (not . isControl) nm) hPutStrLn socket ("Nice to meet you!") writeChan msgch (nick ++ "@" ++ wh ++ " has joined.") Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk 77 Talk 0.2: Benutzerprozeß wp <- forkIO write catch (read ((nick ++ ": ")++)) (\e-> do killThread wp if isEOFError e then writeChan msgch (nick++ "@"++ wh++ " has else writeChan msgch (nick++ "@"++ wh++ " left hastily ("++ ioeGetErrorStri hClose socket) where read :: (String-> String)-> IO () read f = hGetLine socket >>= writeChan msgch. f >> read f write :: IO () write = readChan msgch >>= hPutStrLn socket >> write Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Längeres Beispiel: It’s good to talk Talk 0.2: Zusammenfassung Nachteile: • Kein Nachrichtenstau mehr • Fehlerbehandlung für Benutzerprozeß • Anmeldeprozedur: Benutzer hat Namen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 78 Längeres Beispiel: It’s good to talk Talk 0.2: Zusammenfassung Nachteile: • Kein Nachrichtenstau mehr • Fehlerbehandlung für Benutzerprozeß • Anmeldeprozedur: Benutzer hat Namen • Schnell verkaufen! Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 78 Sprachinteroperabilität Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Sprachinteroperabilität 80 Grundlagen: • Manuel ‘Chilli’ Chakravarty (ed): The Haskell98 Foreign Function Interface 1.0 (Release Candidate 4) • GHC User’s Guide, Kapitel 8 Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Probleme bei der Sprachinteroperbilität Probleme bei der Sprachinteroperbilität • Unterschiedliche Datentypen • Unterschiedliche Auswertungsparadigmen • Unterschiedliche Übergabeparameterdisziplin ◦ Call by reference, call by value, im Register, auf dem Stack, Reihenfolge. . . • Meist sehr ad-hoc gelöst Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 81 Haskell und das Standard FFI Haskell und das Standard FFI • Die gute Nachricht: es gibt einen Standard Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 82 Haskell und das Standard FFI 82 Haskell und das Standard FFI • Die gute Nachricht: es gibt einen Standard • Die schlechte Nachricht: der Standard ist brandneu ◦ Wird von Glasgae Haskell erst ab 5.03 in genau der Form unterstützt ◦ Zu aktuellem Compiler (5.02.3) kleinere syntaktische Abweichungen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Haskell und das Standard FFI 83 Bestandteile des FFI • Spracherweiterung zur Deklaration von ◦ importierten Fremdfunktionen ◦ exportierten Haskellfunktionen • Module zum einfachen Marshalling ◦ Marshalling: Konversion/Aufbereitung der Daten zwischen den Sprachen ◦ Betonung liegt auf Haskell-seitigem Marshalling • Unterstützt C, C++, JVM, Win32, .NET Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Haskell und das Standard FFI 84 Deklaration importierter Fremdfunktionen • Eine einfache C-Funktion mit foreign import ccall toLower :: Char -> Char • C-Funktion mit Seiteneffekten und anderem Namen foreign import "PutChar" putChar :: Char-> IO () • Sicherheit: safe vs. unsafe ◦ safe (Default): Funktion kann wieder Haskell aufrufen. ◦ unsafe: Funktion ruft nicht wieder Haskell auf. ◦ Beispiel: foreign import "PutChar" unsafe putChar :: Char-> IO () • Achtung: Der Typ wird nicht überprüft! Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Haskell und das Standard FFI Deklaration exportierter Haskellfunktionen • Exportierte Funktion: foreign export ccall "Foo" foo :: Int-> Int • Export von Instanzen: double :: Num a=> a-> a foreign export ccall "doubleI" double :: Int-> Int foreign export ccall "doubleF" double :: Float-> Float Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 85 Haskell und das Standard FFI 86 Fremdtypen (foreign types) • Externe Entitäten auf Fremdtypen beschränkt: • Basisfremdtypen sind: ◦ Char, Int, Double, Float, Bool; ◦ Int8, . . . , Word64, Ptr a, StablePrt a, aus Modul Foreign • Fremdtypen sind: a1 -> a2 -> . . . -> r ◦ ai sind Basisfremdtypen, oder Typsynonyme, oder newtype’s davon; ◦ r dito, oder (), oder IO a (mit a dito) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Marshalling und Speicherverwaltung 87 Marshalling • Int und Word • Module Foreign und CForeign • In der Package lang ◦ Beim Übersetzen -package lang Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Marshalling und Speicherverwaltung Speicherverwaltung I: Ptr und Storable • Typ Ptr a abstrakte Adresse auf Objekt vom Typ a ◦ Instanz von Eq, Ord, Show • Ausgezeichneter Zeiger ins ’Nichts’ nullPtr :: Ptr a • Pointerarithmetik, Alignment, Casting plusPtr :: Ptr a -> Int-> Ptr a minusPtr :: Ptr a -> Int-> Ptr a alignPtr :: Ptr a -> Int-> Ptr a castPtr :: Ptr a -> Ptr b Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 88 Marshalling und Speicherverwaltung • Klasse Storable erlaubt Zugriff auf Inhalt der Ptr ◦ Instanzen: Basisdatentypen, Int und Word, Zeiger • Größe und Alignment: sizeOf :: Storable a=> a -> Int alignment :: Storable a=> a-> Int • Peek und Poke: peek :: Storable a=> Ptr a-> IO a poke :: Storable a=> Ptr a-> a-> IO () ◦ Alignment beachten! ◦ Weitere Hilfsfunktionen für indizierte Zugriffe in Felder peekElemOff :: Storable a=> Ptr a-> Int-> IO a pokeElemOff :: Storable a=> Ptr a-> Int-> a-> IO () Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 89 Marshalling und Speicherverwaltung Memory Management I: ForeignPtr • Das Problem: ◦ In imperative Sprache explizite Finalisierung (Resourcefreigabe) ◦ Aber in Haskell: implizite Garbage Collection Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 90 Marshalling und Speicherverwaltung Memory Management I: ForeignPtr • Das Problem: ◦ In imperative Sprache explizite Finalisierung (Resourcefreigabe) ◦ Aber in Haskell: implizite Garbage Collection • Lösung: ForeignPtr a mit Finalizer ◦ Wird aufgerufen, bevor Objekt abgeräumt wird. data ForeignPtr a newForeignPtr :: Ptr a-> IO ()-> IO (ForeignPtr a) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 90 Marshalling und Speicherverwaltung Memory Management II: StablePtr • Noch einProblem: ◦ Imperative Sprachen erwarten unveränderliche Referenzen ◦ in Haskell sind Adressen flüchtig (garbage collection) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 91 Marshalling und Speicherverwaltung Memory Management II: StablePtr • Noch einProblem: ◦ Imperative Sprachen erwarten unveränderliche Referenzen ◦ in Haskell sind Adressen flüchtig (garbage collection) • Die Lösung: StablePtr a ◦ Stabile Adresse garantiert. data StablePtr a newStablePtr :: a-> IO (StablePtr a) deRefStablePtr :: StablePtr a-> IO a freeStablePtr :: StablePtr a-> IO () Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 91 Zwei kleine Beispiele 92 From Heaven to Hell Haskell importiert C: module Main where {-# OPTIONS -#include "hello.h" #-} foreign import "hello" unsafe helloWorld :: IO () main = do putStrLn "Here you go: " helloWorld Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Zwei kleine Beispiele • Name der C-Quelldatei unerheblich. • Pragma optional. • Übersetzen: ghc -c -fglasgow-exts Heaven2Hell.hs gcc -c hello.c ghc -o hello Heave2Hell.o hello.o Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 93 Zwei kleine Beispiele 94 From Hell to Heaven Haskell-Modul mit exportierter Funktion: module Hell2Heaven where foreign export ccall "fac" fac :: Int-> Int fac :: Int-> Int fac n | n <= 0 = 1 | otherwise = n* fac (n-1) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Zwei kleine Beispiele • Compilation mit ghc erzeugt: Hell2Heaven_stub.c -> Hell2Heaven_stub.o Hell2Heaven_stub.h • Einbinden von fac in callHs.c: ◦ Hell2Heaven stub.h enthält Signatur (Prototypen) ◦ Haskell-Laufzeitsystem initialisieren/beenden ◦ Alle Haskell-Funktionen in einem Modul zusammengefassen Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 95 Zwei kleine Beispiele • Kompilieren: ghc -c -fglasgow-exts Hell2Heaven.lhs gcc -I /usr/local/lib/ghc-5.02.2/include \ -c callHs.c ghc -no-hs-main -o callHs callHs.o \ Hell2Heaven.o Hell2Heaven_stub.o ◦ Pfad auf ghc-includes systemspezifisch ◦ Linken mit ghc, aber main aus callHs.o Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 96 Conclusions 97 Generell zu beachten • Typen werden nicht überprüft • Beliebige Typkonversionen möglich • Fremdfunktionen können Haskell-Heap korrumpieren Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Conclusions 98 Auf dem FFI aufsetzende Tools • GreenCard ◦ Generiert Marshalling aus Direktiven ◦ Kein Export • C->Haskell ◦ Generiert Marshalling aus Haskell mit Direktiven und C Headerdateien • H/Direct ◦ Generiert Marshalling aus IDL ◦ Import und Export; unterstützt für Java, C, COM+ ◦ Groß und potentiell out-of-date Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung Effizienzaspekte • Beste Lösung: bessere Algorithmen. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 100 Effizienzaspekte in funktionaler Programmierung Effizienzaspekte • Beste Lösung: bessere Algorithmen. • Zweitbeste Lösung: Büchereien nutzen. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 100 Effizienzaspekte in funktionaler Programmierung Effizienzaspekte • Beste Lösung: bessere Algorithmen. • Zweitbeste Lösung: Büchereien nutzen. • Effizenzverbesserungen durch ◦ Endrekursion und Striktheit: Speicherlecks vermeiden ◦ The eternal conflict: speed vs. space Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 100 Effizienzaspekte in funktionaler Programmierung Termgraphendarstellung • Ausdrücke werden intern durch Termgraphen dargestellt. • Argument wird nie mehr als einmal ausgewertet: ◦ trace :: String-> a-> a druckt String bei Auswertung. import IOExts (trace) f :: Int-> Int-> Int f x y = x+ x test1 = f (trace "Eins\n" (3+2)) (trace "Zwei\n" (3+2)) • Sharing von Teilausdrücken ◦ Explizit mit where ◦ Implizit (ghc) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 101 Effizienzaspekte in funktionaler Programmierung 102 Endrekursion (tail recursion) • Eine Funktion ist endrekursiv, wenn kein rekursiver Aufruf in einem geschachtelten Ausdruck steht. ◦ Endrekursion entspricht goto oder while in imperativen Sprachen. ◦ Endrekursive Funktionen werden in Schleifen übersetzt. ◦ Nicht-endrekursive Funktionen verbrauchen Platz auf dem Stack. • Bsp: ◦ fac nicht endrekursiv: fac :: Int-> Int fac n = if n == 0 then 1 else n * fac (n-1) ◦ fac endrekursiv: Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung 103 fac0 n acc = if n == 0 then acc else fac0 (n-1) (n*acc) fac’ n = fac0 n 1 fac’ :: Int-> Int Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung 104 • Überführung in endrekursive Form: ◦ Zwischenergebnisse in zusätzlichen Parameter akkumulieren. • Aufgesammelte Zwischenergebnisse brauchen immer noch Platz: Auswertung erzwingen. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung 105 Striktheit • Funktion f ist strikt in einem Argument x, wenn ein undefinierter Wert für das Argument die Funktion undefiniert werden läßt. ◦ Bsp: (+) strikt in beiden Argumenten ◦ (&&) strikt im ersten, nicht-strikt im zweiten: False && (1/0 == 1/0) False • Strikte Argumente erlauben Optimierung. ◦ Zum Beispiel Auswertung vor Aufruf bei Endrekursion. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung 106 • Auswertung erzwingen: seq :: a-> b-> b wertet erstes Argument aus • Fakultät in konstantem Platzaufwand: fac’’ :: Int-> Int fac’’ n = fac0 n 1 where fac0 n acc = seq acc (if n == 0 then acc else fac0 (n-1) (n*acc)) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung foldr vs. foldl • foldr ist nicht endrekursiv. • foldl ist endrekursiv: foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs • foldl’ :: (a-> b-> a)-> a-> [b]-> a ist endrekursiv und strikt. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 107 Effizienzaspekte in funktionaler Programmierung 108 • foldl endrekursiv, aber traversiert immer die ganze Liste. • foldl’ konstanter Platzaufwand, aber traversiert immer die ganze Liste. • Wann welches fold? ◦ Strikte Funktionen mit foldl’ falten. ◦ Wenn nicht die ganze Liste benötigt wird, foldr: all :: (a-> Bool)-> [a]-> Bool all p = foldr ((&&) . p) True Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung Überladene Funktionen sind langsam. • Typklassen sind elegant aber langsam. ◦ Implementierung von Typklassen: dictionaries von Klassenfunktionen. ◦ Überladung muß zur Laufzeit aufgelöst werden. • Bei kritischen Funktionen durch Angabe der Signatur Spezialisierung erzwingen. • NB: Zahlen (numerische Literale) sind in Haskell überladen! ◦ Bsp: facs hat den Typ Num a=> a-> a facs n = if n == 0 then 1 else n* facs (n-1) Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 109 Effizienzaspekte in funktionaler Programmierung Listen sind keine Felder • Wenn Felder benötigt werden, Felder verwenden! • Listen: ◦ Beliebig lang ◦ Zugriff auf n-tes Element in linearer Zeit. • Felder: ◦ Feste Länge ◦ Zugriff auf n-tes Element in konstanter Zeit. ◦ Abstrakt: Abbildung Index auf Daten Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 110 Effizienzaspekte in funktionaler Programmierung 111 • Modul Array aus der Standardbücherei data Ix a=> Array a b -- abstract array :: (Ix a) => (a,a) -> [(a,b)] -> Array a b listArray :: (Ix a) => (a,a) -> [b] -> Array a b (!) :: (Ix a) => Array a b -> a -> b (//) :: (Ix a) => Array a b -> [(a,b)] -> Array a b • Als Indexbereich geeignete Typen (Klasse Ix): Int, Integer, Char, Char, Tupel davon, Aufzählungstypen. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 Effizienzaspekte in funktionaler Programmierung Listen sind keine FiniteMaps! • GHC kennt FiniteMap. ◦ Effizient, weil durch balancierte Bäume implementiert. ◦ In der Package textttdata (-package data) data Ord key => FiniteMap key elt • Alles fängt mit emptyFM an. • Hinzufügen mit addToFM, addToFM C. ◦ Verhalten bei Überschreiben kann spezifiziert werden. • Auslesen mit lookupToFM, lookupWithDefaultFM. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 112 Effizienzaspekte in funktionaler Programmierung type vs. newtype vs. data • Typsynonyme type Text = String ◦ Typen Text und String nicht unterschiedbar. • Algebraische Datentypen data Text = Text String ◦ Anderer Typ, aber ineffizient. • newtype newtype Text = Text String ◦ Semantisch wie data, aber der Konstruktor wird wegoptimiert. ◦ Nur bei einem einstelligen Konstruktor. Christoph Lüth: Funktionale Systemprogrammierung, 15. Juli 2002 113