Fortgeschrittene Techniken der Funktionalen Programmierung Vorlesung vom 08.12.09:
Nebenl¨ aufigkeit in Haskell: Abstraktionen und Ausnahmen
Christoph L¨uth, Dennis Walter Universit¨at Bremen Wintersemester 2009/10
1
Fahrplan
ITeil I: Monaden und fortgeschrittene Typen ITeil II: Fortgeschrittene Datenstrukturen ITeil III: Nebenl¨aufigkeit
I Grundlagen
I Abstraktionen und Ausnahmebehandlung
I Software Transactional Memory
ITeil IV: The Future of Programming
2
Tagesmen¨ u
IAbstraktionen:Kan¨ale IFallbeispiel:
ITalk
IAusnahmebehandlung:
IErweiterbare Ausnahmen
IUnscharfe Ausnahmen
IAsynchrone Ausnahmen
3
Kan¨ ale
ITypsicheres Lesen/Schreiben inFIFO-Ordnung
IBlockiertwenn leer data Chan a . . .
newChan : : IO ( Chan a )
writeChan : : Chan a → a → IO ( ) readChan : : Chan a → IO a
I Bonus:Duplizierbar (“Broadcast”)
4
Kan¨ ale
IEin Kanal besteht aus Strom mit einem Lese- und Schreibende:
data Chan a = Chan (MVar ( Stream a ) ) (MVar ( Stream a ) )
IHierMVar, um Lesen/Schreiben zu synchronisieren IEin Strom istMVar (ChItem a):
Ientweder leer,
Ioder enth¨alt Werte aus Kopfaund Rest.
type Stream a = MVar ( ChItem a ) data ChItem a = ChItem a ( Stream a )
5
In einen Kanal schreiben
INeues Ende (hole) anlegen IWert in altes Ende schreiben
IZeiger auf neues Ende setzen
writeChan : : Chan a → a → IO ( ) writeChan ( Chan w r i t e ) v a l = do
new hole ←newEmptyMVar o l d h o l e ←takeMVar w r i t e
putMVar o l d h o l e ( ChItem v a l new hole ) putMVar w r i t e new hole
IKann nicht blockieren —writeimmer gef¨ullt.
IOriginal-Code benutztmodifyMVar— Ausnahmesicher!
6
Aus Kanal lesen
IAnfang auslesen, Anfangszeiger weitersetzen IKann blockieren(*)wenn Kanal leer
readChan : : Chan a → IO a readChan ( Chan read ) = do
r e a d e n d ←takeMVar read
( ChItem v a l new read end ) ←readMVar r e a d e n d −−* putMVar read new read end
r e t u r n v a l
IreadMVar :: MVar a→IO aliestMVar, schreibt Wert zur¨uck.
IreadMVarstatttakeMVar, um Duplikation zu erm¨oglichen
Neuen Kanal erzeugen
ILese-Ende = Schreib-Ende newChan : : IO ( Chan a ) newChan = do
h o l e ←newEmptyMVar read ←newMVar h o l e w r i t e ←newMVar h o l e r e t u r n ( Chan read w r i t e )
Weitere Kanalfunktionen
IZeichen wieder vorne einh¨angen:
unGetChan : : Chan a → a → IO ( )
IKanal duplizieren (Broadcast):
dupChan : : Chan a → IO ( Chan a )
IKanalinhalt als (unendliche) Liste:
getChanContents : : Chan a → IO [ a ]
IAuswertung terminiert nicht, sondern blockiert
9
Fallbeispiel: Talk
IZiel: ein Programm, um sich ¨uber das Internetz zu unterhalten (talk, IRC, etc.)
IVerteilte Architektur:
Client Client Client
Client Server
IHier: Implementierung des Servers
I Netzverbindungen durchSocket
10
Socketprogrammierung
ISocket erzeugen, an Namen binden, mitlisten Verbindungsbereitschaft anzeigen
IZustandsbasierte Verbindung:
IServerseite: mitacceptauf eingehende Verbindungen warten
IJede Verbindung erzeugt neuen Filedescriptor
⇒inh¨arent nebenl¨aufiges Problem!
IClientseite: MitconnectVerbindung aufnehmen.
IZustandslose Verbindung:sendTozum Senden,recvFromzum Empfangen.
IGHC-ModulNetwork
ILow-level Funktionen inNetwork.Socket
11
Das Modul Network
ISockets:
type Socket
data PortID = S e r v i c e S t r i n g −−z.B. ”ftp”
| PortNumber PortNumber
| UnixSocket S t r i n g −−Socket mit Namen type Hostname = S t r i n g
i n s t a n c e Num PortNumber IZustandsbasiert:
l i s t e n O n : : PortID→ IO Socket
accept : : Socket→ IO ( Handle , Hostname , PortNumber ) connectTo : : Hostname → PortID → IO Handle
IZustandslos:
sendTo : : HostName → PortID → S t r i n g → IO ( ) recvFrom : : HostName → PortID → IO S t r i n g
12
Serverarchitektur
IEin Kanal zur Nachrichtenverbreitung:
Ieine Nachricht, viele Empf¨anger (broadcast)
IRealisierung mittelsdupChan IZentraler Scheduler
IF¨ur jede ankommende Verbindung neuer Thread:
INachrichten vom Socket auf den Kanal schreiben
INachrichten vom Kanal in den Socket schreiben
IProblem: Wie aus SocketoderKanal lesen wenn beide blockieren?
IL¨osung: Zwei Threads IClient:telnet
13
Talk 0.1: Hauptprogramm
main =do a : ← getArgs
l e t p = f r o m I n t e g e r ( read a ) s ← l i s t e n O n ( PortNumber p ) ch←newChan
loop s ch
14
Talk 0.1: Hauptschleife
loop s ch = f o r e v e r $ do ( handle , wh , p )← accept s h S e t B u f f e r i n g handle No B uffering
putStrLn $ ”New c o n n e c t i o n from ”++ wh++
” on p o r t ”++ show p
ch2 ←dupChan ch
f o r k I O ( newUser handle ch2 )
15
Talk 0.1: Benutzerprozess
newUser : : Handle→ Chan S t r i n g → IO ( ) newUser s o c k e t msgch =
f o r k I O ( f o r e v e r read ) f o r e v e r w r i t e where read : : IO ( )
read = hGetLine s o c k e t =writeChan msgch w r i t e : : IO ( )
w r i t e = readChan msgch=hPutStrLn s o c k e t
16
Talk 0.1: Zusammenfassung
Nachteile:
INachrichten stauen sich im Kanal
IKeine Fehlerbehandlung
IBenutzer anonym
17
Ausnahmebehandlung in Haskell98
IHaskell 98: Fehler leben im IO-Monaden.
IFehler fangen:
catch : : IO a → ( I O E r r o r → IO a )→ IO a
I Variante:try :: IO a→IO (Either IOError a) IFehler erzeugen:
u s e r E r r o r : : S t r i n g→ I O E r r o r i o E r r o r : : I O E r r o r→ IO a
IOder durch andere Operationen im IO-Monaden.
18
Fehler analysieren
IFunktionen, die im Handler benutzt werden k¨onnen:
i s A l r e a d y E x i s t s E r r o r : : I O E r r o r → Bool i s D o e s N o t E x i s t E r r o r : : I O E r r o r → Bool i s A l r e a d y I n U s e E r r o r : : I O E r r o r → Bool i s F u l l E r r o r : : I O E r r o r → Bool isEOFError : : I O E r r o r → Bool i s I l l e g a l O p e r a t i o n : : I O E r r o r → Bool i s P e r m i s s i o n E r r o r : : I O E r r o r → Bool i s U s e r E r r o r : : I O E r r o r → Bool i o e G e t E r r o r S t r i n g : : I O E r r o r → S t r i n g ioeGetHandle : : I O E r r o r → Maybe Handle ioeGetFileName : : I O E r r o r → Maybe F i l e P a t h
19
Talk 0.2: Hauptschleife
loop s ch = f o r e v e r $ do ( handle , wh , p ) ← a c ce p t s h S e t B u f f e r i n g handle No B uf f er in g i n s t a l l H a n d l e r sigPIPE I g n o r e Nothing putStrLn $ ”New c o n n e c t i o n from ”++ wh++
” on p o r t ”++ show p ch2←dupChan ch
f o r k I O ( catch ( newUser handle wh ch2 ) (λ → hClose handle ) ) IFehlerbehandlung f¨urnewUser(kein guter Stil) ISIGPIPEignorieren
20
Talk 0.2: Benutzerprozess
Teil 1: Anmeldeprozedur newUser s wh msgch = do
hPutStrLn s ” H e l l o t h e r e . P l e a s e send your nickname . ” n i c k ←do nm←hGetLine s
r e t u r n ( f i l t e r ( not . i s C o n t r o l ) nm) hPutStrLn s $ ” Nice to meet you , ”++ n i c k++ ” ! ” writeChan msgch $ n i c k++ ”@”++ wh++ ” has j o i n e d . ” (Fortsetzung)
21
Talk 0.2: Benutzerprozess
Teil 2: Hauptschleife:
wp← f o r k I O w r i t e
catch ( read ( ( n i c k ++ ” : ” ) ++) ) $ λe→ do k i l l T h r e a d wp
writeChan msgch $
i f isEOFError e then n i c k++ ”@”++ wh++ ” has l e f t . ” e l s e n i c k++ ”@”++ wh++ ” l e f t h a s t i l y ( ”++
i o e G e t E r r o r S t r i n g e++ ” ) ” hClose s where
read : : ( S t r i n g→ S t r i n g )→ IO ( ) read f = f o r e v e r $
hGetLine s=writeChan msgch . f w r i t e : : IO ( )
w r i t e = f o r e v e r $ readChan msgch=hPutStrLn s
22
Talk 0.2: Zusammenfassung
Vorteile:
IRobust
IFehlerbehandlung f¨ur Benutzerprozess
IAnmeldeprozedur: Benutzer hat Namen
ISchnell verkaufen!
Probleme mit der Ausnahmebehandlung in Haskell98
IKeineAusnahmebehandlung f¨urrein funktionalenCode.
I error :: String→abrichtProgrammausf¨uhrungab;
I z.B.Fehlerbeiread :: Read a⇒String→a?
I readIO :: Read a⇒String→IO awirft Ausnahme
I Laufzeitfehler(pattern match, fehlendeKlassenmethoden, . . . ) IKeine Behandlung vonasynchronen Ausnahmenm¨oglich.
I Nebenl¨aufige Fehler, e.g. stack overflow, Speichermangel, Interrupts;
Probleme mit rein funktionalen Ausnahmen.
IWarum nicht einfachthrow :: Exception→a?
IWird die Ausnahme geworfen?
l e n g t h [ throw e x c e p t i o n ]
IAbh¨angig von Tiefe der Auswertung (wertetlengthArgument aus?) IWelche Ausnahme wird geworfen:
throw ex1 + throw ex2
IAbh¨angig von Reihenfolge der Auswertung der Argumente IAber: Auswertungsreihenfolge in Haskell98unspezifiziert!
25
Unscharfe Ausnahmen.
INormaleAusnahmen:Wert eines Ausdrucks=NormalerWert oder Ausnahme
data Maybe a = J u s t a | Nothing data E i t h e r a = L e f t S t r i n g | Right a
IUnscharfeAusnahmen:Wert eines Ausdrucks=NormalerWert oder Mengevon m¨oglichenAusnahmen
I Menge wird nicht konstruiert — semantisches Konstrukt.
26
Unscharfe Ausnahmen fangen.
IAusnahmen fangen ist monadisch:
IFunktionbogus :: a→(Exception→a)→ah¨atte alten Probleme IDeterminisierungtrennenvonAusnahmebehandlung:
I evaluate :: a→IO awertet Ausdruck aus, wirft ggf. m¨ogliche Ausnahme.
IAusnahme durch Auswertungsreihenfolge bestimmt.
Icatch :: IO a→(Exception→IO a)→IO awie vorher.
IUnscharfeAusnahmen k¨onnen¨uberallgeworfen, aber nur im IO-Monaden gefangen werden.
27
Asynchrone Ausnahmen
IModelliert durch
throwTo : : ThreadId → E x c e p t i o n→ IO ( )
IAusnahme wird inanderemThread geworfen.
IModelliertalle Situationenwie Interrupts etc.
28
Asynchrone Ausnahmen: Beispiel
IParallele Auswertungzweier IO-Statements:
IWerzuerstfertig istbeendetAuswertung.
parIO : : IO a→ IO a→ IO a parIO a1 a2 =
dom←newEmptyVar ;
c1 ←f o r k I O ( a1 =putMVar m) c2 ←f o r k I O ( a2 =putMVar m) r← takeMVar m
throwTo c1 K i l l throwTo c2 K i l l r e t u r n r
29
Asynchrone Ausnahmen: Beispiel
ITimeout-Operator:
IWenn kein Ergbnis nachnMikrosekunden,Nothing timeout : : I n t→ IO a→ IO ( Maybe a ) timeout n a = parIO ( r←a ; r e t u r n ( J u s t r ) )
( t h r e a d D e l a y n ; r e t u r n Nothing )
30
Unscharfe Ausnahmen: Benutzung
IZur Benutzung:import Control.Exception(nurghc) IUmErweiterbarkeitzu gew¨ahrleisten:
ITypklasseException, alle Ausnahmen sind Instanzen
IAchtung, erst seit ghc 6.10.
IAchtung:per defaultnormaleAusnahmen (Haskell98) definiert
I ¨Uberlagerung durchImportoder Disambiguierung
IAusnahmen fangen:
catch : : E x c e p t i o n e⇒ IO a→ ( e → IO a )→ IO a t r y : : E x c e p t i o n e⇒ IO a → IO ( E i t h e r e a )
31
Vorsicht bei Ausnahmen
IAusnahmen und Nebenl¨aufigkeit
IAusnahmen k¨onnen in anderen Thread geworfen werden!
ch←newChan
f o r k I O ( f o r e v e r $ do readChan ch=putStrLn ) catch (do l e t x= . . .
writeChan ch x ) (λe → . . . )
IAusnahme wird in reader-Thread geworfen!
I Abhilfe: Auswertung mitevaluateforcieren.
32
Ausnahmen: Achtung!
IFehlerabfrage ersetztkeineAusnahmebehandlung:
b← d o e s D i r e c t o r y E x i s t name
when ( not b ) $ c r e a t e D i r e c t o r y name
IZweite Aktionkann fehlschlagen!
33
Zusammenfassung
IKan¨ale: N¨utzlicheKommunikationsabstraktion IUnscharfeAusnahmen:
I K¨onnen inbeliebigemCode auftreten
I Werden imIO-Monaden gefangen IAsynchroneAusnahmen:
I Werden inanderemThread ausgel¨ost IAusnahmebehandlung:
I Essentiellf¨ur robuste Programmierung
I NurAusnahmen fangen, die man behandelt!
I Fehlerabfrage ersetztkeineAusnahmebehandlung
34