Funktionale Systemprogrammierung - informatik.uni

Werbung
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
Herunterladen