Reaktive Programmierung
Vorlesung 2 vom 16.04.15: Monaden und Nebenlaeufigkeit in Haskell
Christoph Lüth & Martin Ring Universität Bremen Sommersemester 2015
16:11:07 2015-05-26 1 [32]
Fahrplan
I Teil I: Grundlegende Konzepte
IWas ist Reaktive Programmierung?
INebenläufigkeit und Monaden in Haskell
IFunktional-Reaktive Programmierung
IEinführung in Scala
IDie Scala Collections
IScalaTest und ScalaCheck I Teil II: Nebenläufigkeit
I Teil III: Fortgeschrittene Konzepte
2 [32]
Speisekarte
I Das Geheimnis der Monade
I Concurrent Haskell
3 [32]
Zustandsübergangsmonaden
I Aktionen (IO a) sind keine schwarze Magie.
I Grundprinzip: Systemzustand Σ wird explizit behandelt.
f::a→IO b ∼= f :: (a,Σ)→(b,Σ) FolgendeInvariantenmüssen gelten:
ISystemzustand darfnie dupliziertodervergessenwerden.
IAuswertungsreihenfolge muss erhalten bleiben.
I KompositionmussInvariantenerhalten Zustandsübergangsmonaden
4 [32]
Komposition von Zustandsübergängen
I Im Prinzip Vorwärtskomposition:
(=) :: ST s a→ (a→ST s b)→ST s b
(=) :: ( s→ (a , s ))→ (a→ s→ (b , s ))→ ( s→ (b , s )) (=) :: ( s→ (a , s ))→ ((a , s )→ (b , s ))→ ( s→ (b , s ))
I Damitf =g =uncurry g◦f I Aber:STkannkein Typsynonymsein
I Nötig:abstrakter DatentypumInvariantenzu erhalten
5 [32]
ST als Abstrakter Datentyp
I Datentyp verkapseln:
newtypeST s a = ST ( s→ (a , s ))
I Hilfsfunktion (Selektor)
unwrap :: ST s a→ ( s→ (a , s )) unwrap (ST f ) = f
I Damit ergibt sich
f= g = ST ( uncurry (unwrap . g)◦unwrap f ) return a = ST (λs→ (a , s ))
6 [32]
Aktionen
I Aktionen: Zustandstransformationen auf der Welt I TypRealWorld#repräsentiert Außenwelt
I Typ hat genau einen Wertrealworld #, der nur für initialen Aufruf erzeugt wird.
I Aktionen:typeIO a = ST RealWorld# a I Optimierungen:
I ST s adurchin-place-updateimplementieren.
I IO-Aktionen durcheinfachen Aufrufersetzen.
I Compiler darf keine Redexe duplizieren!
I TypIOstelltlediglichReihenfolge sicher.
7 [32]
Was ist eigentlich eine Monade?
I STmodelliertimperative Konzepte.
I Beobachtung:Andere Konzepte könnenähnlich modelliertwerden:
IAusnahmen:f :: a-> Maybe bmit Komposition (=) :: Maybe a→(a→Maybe b)→Maybe b Just a = f = f a
Nothing= f = Nothing
8 [32]
Monads: The Inside Story
classMonad mwhere
(=) :: m a → (a →m b) →m b return :: a →m a
() :: m a→m b →m b f a i l :: String →m a pq = p=λ_→q
f a i l s = er ro r s
FolgendeGleichungenmüssen (sollten)gelten:
return a=k = k a m=return = m
m=(λx→k x=h) = (m=k)=h
9 [32]
Beispiel: Speicher und Referenzen
I Signatur:
typeMem a instanceMem Monad I Referenzen sind abstrakt:
typeRef
newRef :: Mem Ref I Speicher liest/schreibtString:
readRef :: Ref →Mem String writeRef :: Ref → String→Mem ()
10 [32]
Implementation der Referenzen
Speicher: Liste von Strings, Referenzen: Index in Liste.
typeMem = ST [ String ] −−Zustand type Ref = Int
newRef = ST (λs→ ( length s , s++ [ "" ] ) ) readRef r = ST (λs→ ( s ! ! r , s )) writeRef r v = ST (λs→ ( ( ) ,
take r s ++ [ v ] ++ drop ( r+1) s )) run :: Mem a→ a
run (ST f )= f s t ( f [ ] )
11 [32]
IORef — Referenzen
I Datentyp der Standardbücherei (GHC) importData . IORef
dataIORef a
newIORef :: a → IO (IORef a) readIORef :: IORef a → IO a writeIORef :: IORef a → a →IO () modifyIORef :: IORef a → (a→ a) → IO ()
atomicModifyIORef :: IORef a→ (a→ (a , b)) →IO b I Implementation: “echte” Referenzen.
12 [32]
Beispiel: Referenzen
fac :: Int→IO Int
fac x =doacc← newIORef 1 loop acc xwhere
loop acc 0 = readIORef acc loop acc n =do t ← readIORef acc
writeIORef acc ( t∗ n) loop acc (n−1)
13 [32]
Die Identitätsmonade
I Die allereinfachste Monade:
type Id a = a
instanceMonad Id where return a = a b= f = f b
14 [32]
Die Listenmonade
I Listen sind Monaden:
instance Monad [ ]where m= f = concatMap f m
return x = [ x ] f a i l s = [ ]
I Intuition:f :: a→[b]Liste der möglichen Resultate
I Reihenfolge der Möglichkeiten relevant?
15 [32]
Fehlermonaden
I Erste Nährung:Maybe
I Maybekennt nurNothing, daher strukturierte Fehler:
data Either a b = Left a | Right b type Error a = Either String a instanceMonad ( Either String ) where
( Right a)= f = f a ( Left l ) = f = Left l return b = Right b I Nachteil: Fester Fehlertyp I Lösung: Typklassen
16 [32]
Exkurs: Was genau ist eigentliche eine Monade?
I Monade: Konstrukt ausKategorientheorie I Monade∼= (verallgemeinerter) Monoid I Monade: gegeben durchalgebraische Theorien
I Operationen endlicher (beschränkter) Aritität
I Gleichungen
I Beispiele:Maybe,List,Set,State, . . . I Monaden in Haskell:computational monads
I Strukturierte Notation fürBerechnungsparadigmen
I Beispiel: Rechner mit Fehler, Nichtdeterminismus, Zustand, . . .
17 [32]
Konzepte der Nebenläufigkeit
I Thread (lightweight process) vs. Prozess Programmiersprache/Betriebssystem Betriebssystem (z.B. Java, Haskell, Linux)
gemeinsamerSpeicher getrennterSpeicher
Erzeugungbillig Erzeugungteuer
mehrereproProgramm einerproProgramm
I Multitasking:
Ipräemptiv:Kontextwechsel wirderzwungen
Ikooperativ:Kontextwechsel nurfreiwillig
18 [32]
Zur Erinnerung: Threads in Java
I Erweiterung der KlassenThreadoderRunnable I Gestartet wird Methoderun()— durch eigene überladen I Starten des Threads durch Aufruf der Methodestart() I Kontextwechsel mityield()
I Je nach JVM kooperativoderpräemptiv.
I Synchronisation mitsynchronize
19 [32]
Threads in Haskell: Concurrent Haskell
I SequentiellesHaskell: Reduktion eines Ausdrucks
IAuswertung
I NebenläufigesHaskell: Reduktion eines Ausdrucks anmehreren Stellen I ghcimplementiert Haskell-Threads
I ModulControl.Concurrententhält Basisfunktionen I Wenige Basisprimitive, darauf aufbauend Abstraktionen
20 [32]
Wesentliche Typen und Funktionen
I Jeder Thread hat einen Identifier: abstrakter TypThreadId I Neuen Thread erzeugen:forkIO :: IO()→IO ThreadId I Thread stoppen: killThread :: ThreadId→IO () I Kontextwechsel: yield :: IO ()
I Eigener Thread:myThreadId :: IO ThreadId I Warten:threadDelay :: Int →IO ()
21 [32]
Rahmenbedingungen
I Zeitscheiben:
ITick: Default 20ms
IContextswitchpro Tick bei Heapallokation
IÄnderungen perKommandozeilenoptionen:+RTS -V<time> -C<time>
I Blockierung:
ISystemaufrufe blockierenalle Threads
IMit threaded library (-threaded) nicht alle
IAber: Haskell Standard-IO blockiertnur den aufrufenden Thread
22 [32]
Concurrent Haskell — erste Schritte
I Ein einfaches Beispiel:
write :: Char→ IO () write c = putChar cwrite c main :: IO ()
main = forkIO ( write ’X’ )write ’O’
I Ausgabeghc: (X∗|O∗)∗
23 [32]
Synchronisation mit MVars
I Basissynchronisationmechanismusin Concurrent Haskell
IAlles andereabgeleitet
I MVar averänderbareVariable (vgl.IORef a) I Entwederleerodergefülltmit Wert vom Typa I Verhalten beim Lesen und Schreiben
Zustand vorher: leer gefüllt
Lesen blockiert(bis gefüllt) danach leer Schreiben danach gefüllt blockiert(bis leer)
INB.Aufweckenblockierter ProzesseeinzelninFIFO
24 [32]
Basisfunktionen MVars
I Neue Variable erzeugen (leer oder gefüllt):
newEmptyMVar :: IO (MVar a) newMVar :: a → IO (MVar a)
I Lesen:
takeMVar :: MVar a→ IO a
I Schreiben:
putMVar :: MVar a →a → IO ()
25 [32]
Abgeleitete Funktionen MVars
I Nicht-blockierendes Lesen/Schreiben:
tryTakeMVar :: MVar a → IO (Maybe a) tryPutMVar :: MVar a→a→IO Bool
I Änderung der MVar:
swapMVar :: MVar a→ a →IO a
withMVar :: MVar a→ (a →IO b) → IO b modifyMVar :: MVar a→ (a →IO (a , b)) →IO b
IAchtung:race conditions
26 [32]
Ein einfaches Beispiel ohne Synchronisation
I Nebenläufige Eingabe von der Tastatur echo :: String→IO ()
echo p = forever (do
putStrLn ( "∗∗∗␣Please␣enter␣ l i n e ␣fo r␣"++p) l i n e ← getLine
n←randomRIO (1 ,100)
replicateM_ n ( putStr (p++ " : "++ l i n e ++"␣" ))) main :: IO ()
main = forkIO (echo "2")echo "1"
I Problem: gleichzeitige Eingabe I Lösung:MVarsynchronisiert Eingabe
27 [32]
Ein einfaches Beispiel mit Synchronisation
I MVarvoll⇔Eingabe möglich
IAlso: initial voll
I Inhalt der MVar irrelevant:MVar () echo :: MVar ()→ String→IO () echo f l a g p = forever (do
takeMVar f l a g
putStrLn ( "∗∗∗␣Please␣enter␣ l i n e ␣"++ p) l i n e ← getLine
n ←randomRIO (1 ,100)
replicateM_ n ( putStr (p++ " : "++ l i n e ++"␣" )) putMVar f l a g ())
main :: IO ()
main =dof l a g ←newMVar ()
forkIO (echo f l a g "3")forkIO (echo f l a g "2") echo f l a g "1"
28 [32]
Das Standardbeispiel
I Speisende Philosopen
I Philosophi:
I vor dem Esseni-tes und (i+ 1) modn-tes Stäbchen nehmen
I nach dem Essen wieder zurücklegen
I Stäbchen modelliert alsMVar ()
29 [32]
Speisende Philosophen
philo :: [MVar ( ) ] → Int→IO () philo chopsticks i = forever (do
let num_phil = length ( chopsticks )
−−Thinking:
putStrLn ( " Phil␣#"++ show i ++"␣thinks. . ." ) randomRIO (10 , 200)= threadDelay
−−Get ready to eat:
takeMVar ( chopsticks ! ! i )
takeMVar ( chopsticks ! ! (( i+1) ‘mod‘ num_phil))
−−Eat:
putStrLn ( " Phil␣#"++ show i ++"␣eats. . ." ) randomRIO (10 , 200)= threadDelay
−−Done eating:
putMVar ( chopsticks ! ! i ) ()
putMVar ( chopsticks ! ! (( i+1) ‘mod‘ num_phil)) ())
30 [32]
Speisende Philosophen
I Hauptfunktion:nStäbchen erzeugen I Anzahl Philosophen in der Kommandozeile
main =do a :_← getArgs letnum= read a
chopsticks ← replicateM num (newMVar ()) mapM_ ( forkIO◦( philo chopsticks )) [ 0 . . num−1]
block
I Hilfsfunktionblock: blockiert aufrufenden Thread block :: IO ()
block = newEmptyMVar= takeMVar
I NB: Hauptthread terminiert — Programm terminiert!
31 [32]
Zusammenfassung
I Monaden und andere Kuriositäten
IZustandsmonade - Referenzen
IFehlermonaden I Concurrent Haskellbietet
IThreadsauf Quellsprachenebene
ISynchronisierung mitMVars
IDurchschlankes Designeinfache Implementierung I Funktionales Paradigma erlaubtAbstraktionen
IBeispiel:Semaphoren
I Nächste Woche: Funktional-Reaktive Programmierung.
32 [32]