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