Funktionale Programmierung bringt`s! Ein Ausflug mit Haskell in die

Werbung
Funktionale Programmierung bringt’s!
Ein Ausflug mit Haskell in die Praxis
Stefan Wehr ([email protected])
factis research GmbH, Freiburg im Breisgau
26. Juni 2013
FH Wedel
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
1 / 79
Wer bin ich? Was machen wir?
I
I
I
I
Haskell Benutzer seit 2003
Zunächst vor allem im akademischen Bereich
Seit 2010: Anwendung von Haskell in der Industrie
factis research GmbH, Freiburg im Breisgau
I
I
I
I
I
Softwareprodukte für den Medizin- und Pflegebereich
Serverseitige Software fast ausschließlich in Haskell geschrieben
Große Erfahrung mit komplexen mobilen Anwendungen
Projekte und Schulungen im Bereich funktionale
Programmierung und mobile Anwendungen
Vier feste, etwas sechs freie Mitarbeiter
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
2 / 79
Warum funktional? Warum Haskell?
I
I
I
I
I
I
I
I
I
I
Kontrollierter Umgang mit Seiteneffekten
Ausdrucksstarkes Typsystem: Wenn das Programm kompiliert
funktioniert es auch!
Hohe Modularität, klar abgegrenzte Komponenten
Gute Testbarkeit
Kurzer, prägnanter und lesbarer Code
Einfaches Abstrahieren
Hoher Wiederverwendungsgrad
“World’s finest imperative programming language”
Reichhaltige Palette an Ansätzen zur parallelen und
nebenläufigen Programmierung
“Schlaue Leute”
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
3 / 79
Was erwartet Sie heute?
I
I
I
Einblick in ein (größtenteils) in Haskell geschriebenes,
kommerzielles Softwareprodukt
Erfahrung von mehr als drei Jahren kommerzieller
Softwareentwicklung mit Haskell
Übersicht über paralleles und nebenläufiges Programmieren in
Haskell
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
4 / 79
Teil 1: Kommerzielle Softwareentwicklung mit
Haskell
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
5 / 79
Kommerzielle Softwareentwicklung mit Haskell
I
Checkpad MED: elektronische Patientenakte auf dem iPad
I
I
I
I
I
I
Bringt alle Patientendaten zusammen
Unterstützt Krankenhausärzte bei Arbeitsabläufen
Unabhängig vom KIS (Krankenhausinformationssystem)
Demo
Entwicklung seit 2010
Heute: erster zahlender Kunde, Pilotbetrieb in mehreren
Krankenhäusern
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
6 / 79
Architektur von Checkpad
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
7 / 79
Import
I
Vielzahl verschiedener Krankenhaussysteme
I
I
I
I
I
I
I
DICOM (Radiologie)
HL7 (Stammdaten, Befunde)
SAP (Stammdaten, Befunde)
Individuallösungen
...
Java-Schnittstelle zu vielen System vorhanden
Implementiert in Scala
I
I
I
funktional-objekt-orientierte Sprache
JVM-basiert
nahtlose Integration von Java Bibliotheken
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
8 / 79
Dokumentengenerierung
I
I
iPad zeigt generische “Dokumente” an
Generierung dieser Dokumente aus den Importdaten:
I
I
I
I
I
Aufbereitung
Aggregierung
Interpretation
Export von Workflowdaten
Funktioniert wie ein Buildsystem:
I
I
I
Eingabe: Importdaten oder Links auf bereits erzeugte
Dokumente
Ausgabe: Dokumente
Automatische Neugenerierung der Ausgabe bei Änderung der
Eingaben
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
9 / 79
Synchronisation und iPad-Client
I
Ziele:
I
I
I
I
Synchronisationsserver:
I
I
I
offline-Funktionalität
neue Dokumente werden so schnell wie möglich “gepusht”
Sicherheit (Übertragungskanal, Speicher auf dem iPad)
Kennt Synchronisationszustands der Clients
Priorisiert zu schickende Dokumente
Client:
I
I
I
I
Offline-fähig
Speichert empfange Dokumente verschlüsselt
Enthält keine Krankenhaus-spezifische Logik
Skriptbar über Javascript
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
10 / 79
Konkrete Vorteile von Haskell: Generisches Programmieren
I
I
safecopy: Bibliothek zum Serialisieren/Deserialisieren von
Haskell-Datentypen
Automatisches Erzeugen der benötigten Funktionen mittels
Template Haskell
data Contact = Contact Name Address
$(deriveSafeCopy 1 ’base ’’Contact)
I
Migration durch Haskell-Funktion
data Contact0 = Contact0 Name Address
data Contact = Contact Name Address (Maybe Phone)
$(deriveSafeCopy 1 ’base ’’Contact0)
$(deriveSafeCopy 2 ’extension ’’Contact)
instance Migrate Contact where
type MigrateFrom Contact = Contact0
migrate (Contact0 n a) = Contact n a Nothing
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
11 / 79
Konkrete Vorteile von Haskell: Continuations
I
Dokumentengenerierung erfolgt durch Generatoren
I
I
Spezifikation wie aus Importdaten Dokumente erzeugt werden
Keine Logik zum Umgang mit Änderungen an den
Ausgangsdaten
labDocGen = mkGen "labDocGen" 1 $ \(patId, labId) ->
do Just pat <- getImportData patId
Just lab <- getImportData labId
return (LabDoc { title = patName pat ++ " Labor "
++ labDate lab
, sections = [kvs (labParams lab)] })
I
getImportData kümmert sich automatisch um
Neuausführung bei Datenänderungen
I
I
Merkt sich den “Rest” der Berechnung
Führt diesen “Rest” bei Datenänderungen erneut aus
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
12 / 79
Konkrete Vorteile von Haskell: Nebenläufigkeit
I
I
I
I
Leichtgewichtige Threads
STM
Relevant vor allem für Synchronisationsserver
Später mehr dazu
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
13 / 79
Probleme mit Haskell: Lazyness und Speicherlecks
I
I
Unausgewertete Datenstrukturen können zu Speicherlecks
führen
Kleines Rätsel: Ist das mit folgendem Code der Fall?
I
Annahme: übergebene Map bleibt lange im Speicher
import qualified Data.ByteString as BS
md5 :: BS.ByteString -> MD5
md5 bs = MD5 (md5’ bs)
where
md5’ :: BS.ByteString -> BS.ByteString
md5’ = ...
storeMD5 :: Key -> BS.ByteString -> Map.Map Key MD5
-> Map.Map Key MD5
storeMD5 key bs = Map.insert key (md5 bs)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
14 / 79
Nein, kein Speicherleck!
import qualified Data.Map.Strict as Map
data MD5 = MD5 { unMD5 :: !BS.ByteString }
storeMD5 :: Key -> BS.ByteString -> Map.Map Key MD5
-> Map.Map Key MD5
storeMD5 key bs = Map.insert key (md5 bs)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
15 / 79
Doch, Speicherleck!
import qualified Data.Map.Lazy as Map
data MD5 = MD5 { unMD5 :: !BS.ByteString }
storeMD5 :: Key -> BS.ByteString -> Map.Map Key MD5
-> Map.Map Key MD5
storeMD5 key bs = Map.insert key (md5 bs)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
16 / 79
Doch, Speicherleck!
import qualified Data.Map.Strict as Map
data MD5 = MD5 { unMD5 :: BS.ByteString }
storeMD5 :: Key -> BS.ByteString -> Map.Map Key MD5
-> Map.Map Key MD5
storeMD5 key bs = Map.insert key (md5 bs)
I
I
Nur strikte Datenstrukturen dürfen längere Zeit im Speicher
bleiben
Erfordert Disziplin
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
17 / 79
Probleme mit Haskell: Laufzeitmonitoring
I
Nützliche Informationen während ein Programm läuft
I
I
I
I
I
I
Was macht das Programm gerade?
Welche Threads laufen?
Wie ist der Speicherverbrauch?
Was wird gerade alloziert?
Viele solcher Informationen gar nicht oder nur nach Ende des
Programms verfügbar
GHC-Profiling suboptimal
I
I
I
nur offline
funktioniert nicht mit mehreren Prozessorkernen
relativ hoher Performanceverlust
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
18 / 79
Probleme mit Haskell: Einstiegshürde
I
I
I
I
Wenige Programmierer können Haskell
Schritt vom Gelegenheitsprogrammierer zum professionellen
Haskell-Programmierer relativ groß
Andere Sprache (wie z.B. Scala) bieten einen schrittweisen
Einstieg in die funktionale Programmierung
Aber:
I
I
Haskell-Programmierer sind typischerweise sehr gut
Hohe Produktivität mit Haskell
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
19 / 79
Teil 2: Parallelität und Nebenläufigkeit mit
Haskell
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
20 / 79
Although I’m a C++ guy, functional programming will
probably be a better solution to this [parallel
programming] in the long run.
Jerry Higgins, Microsoft, 2013
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
21 / 79
Nebenläufigkeit
I
Nebenläufigkeit ist ein eigenständiges Ziel
I
I
I
Gleichzeitige Kommunikation mit mehreren externen Quellen
Reaktion auf Events der Außenwelt
Notwendigerweise nicht-deterministisch
Schwierigkeiten
I
I
I
I
Deadlocks
Race conditions
Nicht-deterministisches Verhalten
Testabdeckung
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
22 / 79
Vorteile von Nebenläufigkeit
I
I
Manche Probleme werden durch Nebenläufigkeit auch
einfacher
Beispiel: Netzwerkserver mit einem Thread pro Client
I
I
Sequentieller Ablauf pro Client
Alternative: Gleichzeitige Interaktion mit allen Clients
I
Komplizierte Zustandsmaschine
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
23 / 79
Parallelität
I
I
I
Parallelität ist kein Ziel per se
Ziel: Programm soll schneller laufen
Parallelität ist ein möglicher Weg dieses Ziel zu erreichen
I
I
I
Benutzung mehrerer CPU Kerne
Benutzung von GPU Kernen
Kann determinisisch sein
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
24 / 79
Schwierigkeiten bei der Parallelisierung
I
I
I
Welche Teile des Programms sind langsam?
Welche Teile des Programms können parallelisiert werden?
Datenabhängigkeiten?
Granularität
I
I
Zu fein: Overhead wird zu groß
Zu grob: nicht genug Parallelität möglich
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
25 / 79
Deterministischer Parallelismus in Haskell
I
I
I
Die Par Monade
Explizite Datenflussinformationen durch Verwendung spezieller
“Boxen”
Semi-automatische Parallelisierung
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
26 / 79
Beispiel: Sudoku
I
I
I
Parallelisierung durch gleichzeitiges Lösen mehrere Rätsel
Keine Parallelisierung des Algorithmus zum Lösen eines
Rätsels
Gegeben:
I
I
I
Funktion zum Lösen eines Rätsels: solve ::
Maybe Grid
data Maybe a = Just a | Nothing
String ->
Gesucht: Funktion zum Lösen mehrerer Rätsel:
computeSolutions :: [String] -> [Maybe Grid]
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
27 / 79
Sequentielles Sudoku-Lösen
-- Datei: SudokuSeq.hs
computeSolutionsSeq :: [String] -> [Maybe Grid]
computeSolutionsSeq puzzles =
map solve puzzles
I
I
I
Kompilieren: ghc --make -O2 -threaded -rtsopts
SudokuSeq.hs
Ausführen: ./SudokuSeq +RTS -s -RTS
sudoku17.1000.txt
Laufzeit: Total time 1.52s ( 1.53s elapsed)
I
I
CPU-Zeit: 1,52 Sekunden
Wall-Zeit: 1,53 Sekunden
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
28 / 79
Grundlagen der Par Monade
I
I
Installation: cabal install monad-par
Par a
I
I
fork ::
I
I
Typ einer Aktion in der Par-Monad mit Ergebnistyp a
Führt eine Aktion parallel aus
runPar ::
I
I
I
Par () -> Par ()
Par a -> a
Bringt Aktionen in der Par-Monade zurück in die Welt der
normale Haskell-Berechnungen.
Ohne Seiteneffekte (sieht man am Typ!)
Deterministisch (sieht man am Typ!)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
29 / 79
Datenfluss in der Par Monade
I
I
I
Parallele Aktionen kommunizieren über Boxen
IVar a: Box für Werte vom Typ a
Boxen beschreiben den Datenfluss zwischen Aktionen
I
I
I
I
new :: Par (IVar a)
get :: IVar a -> Par a
put :: NFData a => IVar a -> a -> Par ()
Wichtig: Jede Box darf nur einmal beschrieben werden, sonst
Aktion nicht mehr deterministisch
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
30 / 79
Sudoku: statische Paritionierung mit Par
-- Datei: SudokuPar1.hs
computeSolutionsPar1 :: [String] -> [Maybe Grid]
computeSolutionsPar1 puzzles =
let n = length puzzles
(as, bs) = splitAt (n ‘div‘ 2) puzzles
in runPar (f as bs)
where
f :: [String] -> [String] -> Par [Maybe Grid]
f as bs = do b1 <- new
b2 <- new
fork (put b1 (map solve as))
fork (put b2 (map solve bs))
res1 <- get b1
res2 <- get b2
return (res1 ++ res2)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
31 / 79
Performance mit statischer Partitionierung
I
Laufzeit auf einem Kern:
I
I
I
Mit 2 Kernen: Laufzeitoption -N<K> mit K Anzahl der Kerne
I
I
I
./SudokuPar1 +RTS -s -RTS sudoku17.1000.txt
1,52s CPU-Zeit, 1,54s Wall-Zeit
./SudokuPar1 +RTS -s -N2 -RTS sudoku17.1000.txt
1,57s CPU-Zeit, 0,97s Wall-Zeit
Speedup: 1, 53/0, 97 = 1, 55
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
32 / 79
Debugging mit Threadscope
I
I
I
Kompilieren: ghc --make -O2 -threaded -rtsopts
-eventlog -o SudokuPar1 e SudokuPar1.hs
Ausführen: ./SudokuPar1 e +RTS -s -N2 -ls -RTS
sudoku17.1000.txt
Eventlog in Threadscope öffnen
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
33 / 79
Probleme mit statischer Partitionierung
I
Verschiedene Sudokurätsel brauchen unterschiedliche viel Zeit
zum Lösen
I
I
Statische Partitionierung teilt Liste der Problem einfach in der
Mitte
Statische Partitionierung legt sich auf zwei parallele
Berechnungen fest
I
Laufzeit mit vier Kernen schlechter als mit zwei Kernen
I
I
./SudokuPar1 +RTS -s -N4 -RTS sudoku17.1000.txt
1,80s CPU-Zeit, 1,03s Wall-Zeit
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
34 / 79
Sudoku: dynamische Paritionierung
I
I
I
Skaliert auf beliebig viele Kerne
Kommt mit unterschiedlichen Größen der Teilprobleme zurecht
Idee:
I
I
Teile Liste der Sudokurätsel in viele kleine Einheiten
Laufzeitsystem kümmert sich um Aufteilung der Einheiten auf
Prozessorkerne
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
35 / 79
Dynamische Partitionierung mit Par
I
Rückblick auf sequenzielle Variante:
computeSolutionsSeq puzzles = map solve puzzles
I
Parallele Variante von map
map
::
(a -> b) -> [a] ->
[b]
parMap :: NFData b => (a -> b) -> [a] -> Par [b]
I
Parallele Variante des Sudoku-Solvers
computeSolutionsPar2 :: [String] -> [Maybe Grid]
computeSolutionsPar2 puzzles =
runPar (parMap solve puzzles)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
36 / 79
Speedup mit dynamischer Partitionierung
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
37 / 79
Implementierung von parMap
I
Idee:
I
I
I
parMap f xs wertet f auf jedes Element aus xs in einer
neuen, parallelen Berechung aus
Ergebnis einer solchen Berechnung landet in einer Box
Am Schluss werden alle Ergebnisse aus den Boxen
eingesammelt
parMap :: NFData b => (a -> b) -> [a] -> Par [b]
parMap f xs =
do bs <- mapM g xs
mapM get bs
where
g x =
do b <- new
fork (put b (f x))
return b
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
38 / 79
Was ist Datenparallelismus?
I
I
I
Gleichzeitiges Anwenden derselben Operation auf
unterschiedliche Daten
SIMD-Instruktionen
Flacher Datenparallelismus:
I
I
I
I
Paralleles Anwenden sequentieller Operationen
Weitverbreitet (MPI, HPF, . . . )
Begrenzte Anwendungsmöglichkeiten
Geschachtelter Datenparallelismus:
I
I
Paralleles Anwenden paralleler Operationen
Sehr viele Anwendungsmöglichkeiten
I
I
I
I
I
I
Divide-And-Conquer Algorithmen
Graphenalgorithmen
Machine Learning
...
Modularer als flacher Datenparallelismus
Forschungsthema, Implementierung sehr schwierig
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
39 / 79
Accelerate: flacher Datenparallelismus auf der GPU
I
I
I
GPUs sind parallele Mehrkernprozessoren
Spezialisiert auf parallele Grafikoperationen (flacher
Datenparallelismus)
GPGPU: General Purpose Computation on Graphics
Processing Unit
I
I
I
OpenCL, CUDA
Sehr aufwändig
Idee: Erzeuge aus Haskell Code ein GPU Programm
I
I
I
I
Accelerate ist eine EDSL (Embedded Domain Specific
Language)
Accelerate ist eine (kleine) Teilmenge von Haskell
Syntaxbaum der EDSL wird zur Laufzeit reifiziert und mittels
CUDA auf die GPU gebracht
Performance: etwas langsamer als natives CUDA, aber
akzeptabel
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
40 / 79
Accelerate, ein Beispiel
import qualified Data.Array.Accelerate as A
dotp ::
->
->
dotp xs
let
A.Vector Float
A.Vector Float
A.Acc (A.Scalar Float)
ys =
xs’ = A.use xs
ys’ = A.use ys
in A.fold (+) 0 (A.zipWith (*) xs’ ys’)
I
I
A.use transferiert die Daten auf die GPU
A.fold und A.zipWith werden auf der GPU ausgeführt
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
41 / 79
DPH: geschachtelter Datenparallelismus
I
I
I
I
I
Paralleles Anwenden paralleler Operationen
Viel flexibler und modularer als flacher Datenparallelismus
DPH: Data Parallel Haskell
Zentraler Begriff: parallele Arrays, z.B. [: Double :]
Operationen über parallele Arrays werden automatisch
parallelisiert
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
42 / 79
DPH, ein Beispiel
I
I
I
I
Multiplikation dünnbesetzter Matrizen mit einem Vektor
[:Double:]: dichtbesetzer Vektor
[:(Int, Double):]: dünnbesetzter Vektor
Dünnbesetzte Matrix: [:[:(Int,Double):]:]
svMul :: [:(Int,Double):] -> [:Double:] -> Double
svMul sv v = sumP [: (v !: i) * f | (i,f) <- sv :]
smMul :: [:[:(Int,Double):]:] -> [:Double:] -> Double
smMul sm v = sumP [: svMul row v | row <- sm :]
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
43 / 79
Nebenläufigkeit mit Haskell
I
Threads sind extrem billig
I
I
I
I
I
forkIO :: IO () -> IO ThreadId
Green Threads
Mehrere Million Threads auf diesem Laptop keine Problem
Gute Interaktion mit blockierenden Systemcalls und nativen
Threads
Synchronisation mit STM
I
I
Relativ einfach (im Gegensatz zu Locks)
Modular (im Gegensatz zu Locks)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
44 / 79
STM (Software Transactional Memory)
I
Beispiel: Überweisung von einem Bankkonto
I
I
I
I
Probleme mit Threads
I
I
I
Deadlocks, Race conditions
Unmodular
Idee von STM:
I
I
I
transfer acc1 acc2 amount: überweise Betrag amount von
Konto acc1 auf acc2
transfer soll thread-safe sein
Im Beispiel: Konten sind nicht persistenz
Markiere Codeblöcke als “atomar”
Atomare Blöcke werden entweder ganz oder gar nicht
ausgeführt (wie Datenbanktransaktionen)
Vorteile von Haskell:
I
I
Immutability
Lazy Auswertung
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
45 / 79
Kontoüberweisungen mit STM
transfer :: Account -> Account -> Int -> IO ()
transfer acc1 acc2 amount =
atomically (do deposit acc2 amount
withdraw acc1 amount)
I
atomically ::
I
I
I
I
STM a -> IO a
Führt die übergebene Aktion atomar aus
Auszuführende Aktion wird auch Transaktion genannt
STM a: Typ einer Transaktion mit Ergebnis vom Typ a
STM ist eine Monade
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
46 / 79
Transaktionsvariablen
I
STM-Aktionen kommunizieren über Transaktionsvariablen
I
I
I
I
TVar a: Transaktionsvariablen die Wert vom Typ a enthält
Lesen: readTVar :: TVar a -> STM a
Schreiben: writeTVar :: TVar a -> a -> STM ()
Außerhalb von atomically können Transaktionsvariablen
weder gelesen noch geschrieben werden.
type Account = TVar Int
deposit :: Account -> Int -> STM ()
deposit acc amount =
do bal <- readTVar acc
writeTVar acc (bal + amount)
withdraw :: Account -> Int -> STM ()
withdraw acc amount = deposit acc (- amount)
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
47 / 79
Blockieren mit STM
I
I
Warten auf eine bestimmte Bedingung ist essential für
nebenläufige Programme
Beispiel: limitedWithdraw soll blockieren falls nicht genug
Geld zum Abheben da ist
limitedWithdraw :: Account -> Int -> STM ()
limitedWithdraw acc amount =
do bal <- readTVar acc
if amount > 0 && amount > bal
then retry
else writeTVar acc (bal - amount)
I
retry ::
I
I
STM a
Bricht die aktuelle Transaktion ab
Versucht zu einem späteren Zeitpunkt die Transaktion
nochmals auszuführen
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
48 / 79
Kombinieren von Transaktionen
I
Beispiel: Abheben mittels limitedWithdraw von mehreren
Konto solange bis ein Konto genug Geld hat
limitedWithdrawMany :: [Account] -> Int -> STM ()
limitedWithdrawMany [] _ = retry
limitedWithdrawMany (acc:rest) amount =
limitedWithdraw acc amount ‘orElse‘
limitedWithdrawMany rest amount
I
orElse ::
I
I
I
I
STM a -> STM a -> STM a
orElse t1 t2 führt zuerst t1 aus
Falls t1 erfolgreich, so auch orElse t1 t2
Falls t1 abbricht (durch Aufruf von retry) wird t2 ausgeführt
Falls t2 abbricht bricht auch die gesamte Transaktion ab, d.h.
sie wird zu einem späteren Zeitpunkt nochmals ausgeführt
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
49 / 79
Zusammenfassung der STM API
atomically :: STM a -> IO a
retry :: STM a
orElse :: STM a -> STM a -> STM a
newTVar
:: a -> STM (TVar a)
readTVar :: TVar a -> STM a
writeTVar :: TVar a -> a -> STM ()
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
50 / 79
Ausführungsmodell für STM
I
I
I
I
I
atomically t wird optimistisch ausgeführt, ohne Locks
writeTVar v x schreibt in ein Log, nicht in den Speicher
readTVar v liest erst aus dem Log und nur bei Misserfolg aus
dem Speicher
Falls readTVar v den Wert nicht im Log findet wird der im
Speicher gelesene Wert auch im Log vermerkt
Am Ende einer Transaktion erfolgt die Validierung
I
I
I
I
Muss atomar erfolgen (Locks)
Validierung erfolgreich falls die Werte aller Leseoperation im
Log zum Hauptspeicherinhalt passen
Bei erfolgreicher Validierung: aktualisiere Hauptspeicher
retry benutzt Log um Herauszufinden wann eine
Neuausführung sich lohnt
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
51 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
52 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
53 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
54 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
55 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
56 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
57 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
58 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
59 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
60 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
61 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
62 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
63 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
64 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
65 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
66 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
67 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
68 / 79
Beispiel: Ausführung von STM
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
69 / 79
Typsystem verhindert STM-Katastrophen!
I
I
IO-Aktionen sind nicht beliebig wiederholbar
Typsystem verhindert Ausführung von IO-Aktionen innerhalb
einer STM-Transaktion
launchMissiles :: IO ()
launchMissiles = -- ...
bad xv yv =
atomically (do x <- readTVar xv
y <- readTVar yv
when (x > y) launchMissiles)
$ ghc Bad.hs
Bad.hs:12:25:
Couldn’t match type ‘IO’ with ‘STM’
Expected type: STM ()
Actual type: IO ()
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
70 / 79
Threads in Haskell
I
Threads sind extrem billig
I
I
I
I
I
I
forkIO : IO () -> IO ThreadId
Green Threads
Platzbedarf < 100 Bytes + Stack
Mehrere Million Threads auf diesem Laptop keine Problem
Runtimesystem implementiert blockierende Aufrufe intern
asynchron (z.B. mit epoll unter Linux)
Interoperabilität mit nativem Code
I
I
Runtimesystem führt nativen Code in gesonderten OS-Threads
aus
forkOS :: IO () -> IO ThreadId
I
I
erstellt einen Haskell-Thread, dessen nativen Aufrufe immer
im selben OS-Thread laufen
Wichtig für bestimmt Bibliotheken und “thread-local State”
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
71 / 79
Vergleich mit node.js
I
I
I
node.js: Javascript Framework für serverseitige
Netzwerkanwendungen
Bekannt für gute Performance
Asynchrone Schnittstelle zum Lesen von Sockets
I
I
“inversion of control”
Unbequem zum Programmieren
I
I
I
Logik für mehrere Clients vermischt
Zustand des Kontrollflusses muss explizit in globalen Variablen
abgelegt werden
Ausnutzung mehrerer Kerne nur durch mehrere Prozesse
möglich
I
Benutzung von in-memory Datenstrukturen über mehrere
Kerne hinweg schwierig
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
72 / 79
Benchmark Haskell vs. node.js
I
I
I
I
Einfacher Server zum Verdoppeln von Zahlen
Client schickt eine Zahl, Server schickt das Doppelte der Zahl
zurück
Client kann mehrere Zahlen schicken bevor der Server eine
Antwort schickt
Beispiel:
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
73 / 79
Haskell Server mit Conduits
I
Conduits
I
Lösung für das Streaming Problem
I
I
I
Konstanter Speicher
Prompte Freigabe von Resourcen
Prinzip: Unix-Pipes
main =
do let settings
runTCPServer
where
doubleApp x =
appSource
(CB.lines
appSink x
= serverSettings 44444 HostAny
settings doubleApp
x $=
=$= processLine) $$
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
74 / 79
Die processLine Funktion
I
I
await: Wartet auf neue Daten, blockiert falls keine da.
sendLine: Erzeugt eine Ausgabe
processLine :: Monad m
=> Conduit BS.ByteString m BS.ByteString
processLine =
do mBs <- await -- blockiert bis Daten da
case mBs of
Nothing -> return () -- EOF
Just bs | bs == "end" -> sendLine "Thank you"
| otherwise ->
let l = case parseInt bs of
Just i -> show (2 * i)
Nothing -> "not an int"
in do sendLine l
processLine
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
75 / 79
Benchmark-Ergebnisse
I
I
5000 parallele Clients
10 Anfragen pro Client
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
76 / 79
Resourcen: Parallelität & Nebenläufigkeit
I
Parallel and Concurrent Programming in Haskell, Simon
Marlow
I
I
I
Beautiful concurrency, Simon Peyton Jones
I
I
I
http:
//community.haskell.org/~simonmar/par-tutorial.pdf
Buch erscheint in Kürze bei O’Reilly
appeared in “Beautiful code”, Herausgeber Greg Wilson,
O’Reilly 2007
http:
//research.microsoft.com/pubs/74063/beautiful.pdf
Deterministic Parallel Programming with Haskell, Duncan
Coutts und Andres Löh
I
I
Computing in Science & Engineering, Volume 14, Issue 6, Dez.
2012
http://www.well-typed.com/blog/aux/files/
deterministic-parallel-programming.pdf
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
77 / 79
Resourcen: Haskell und funktionale Programmierung
allgemein
I
I
School of Haskell: https://www.fpcomplete.com/
Real World Haskell, Bryan O’Sullivan, Don Stewart und John
Goerzen, O’Reilly 2008
I
I
I
http://book.realworldhaskell.org/
Blog Funktionale Programmierung:
http://funktionale-programmierung.de/
Folien und Material zum Vortrag:
http://factisresearch.com/wedel2013/
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
78 / 79
Fazit & Zusammenfassung
I
I
Mit Haskell wird kommerzielle Software entwickelt!
Haskell punktet auch in der Industrie:
I
I
I
I
I
Haskell bietet viel bzgl. Nebenläufigkeit und Parallelität
I
I
I
I
I
I
I
Hohe Produktivität
Korrektheit
Sicherheit
Hohe Wiederverwendbarkeit
Deterministischer Parallelismus
Datenparallelismus
GPU-Programmierung
STM
Leichtgewichtige Threads
Haskell ist “World’s finest imperative programming language”
Haskell ist performant
Funktionale Programmierung bringt’s! Ein Ausflug mit Haskell in die Praxis
79 / 79
Herunterladen