Funktionale Programmierung in der Praxis Stefan Wehr ([email protected]) factis research GmbH, Freiburg im Breisgau 3. Juni 2013 Universität Bonn Funktionale Programmierung in der 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 in der Praxis 2 / 79 Warum funktional? Warum Haskell? I I I I I I I I I Hohe Modularität, klar abgegrenzte Komponenten Gute Testbarkeit Ausdrucksstarkes Typsystem: Wenn das Programm kompiliert funktioniert es auch! 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 in der 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 in der Praxis 4 / 79 Teil 1: Kommerzielle Softwareentwicklung mit Haskell Funktionale Programmierung in der 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 in der Praxis 6 / 79 Architektur von Checkpad Funktionale Programmierung in der Praxis 7 / 79 Import I Vielzahl verschiedener Krankenhaussysteme I I I I I I I I DICOM (Radiologie) HL7 (Stammdaten, Befunde) SAP (Stammdaten, Befunde) Individuallösungen ... Hauptsächlich Datenimport, aber auch Export 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 in der Praxis 8 / 79 Dokumentengenerierung I I iPad zeigt generische “Dokumente” an Generierung dieser Dokumente aus den Importdaten: I I I I Aufbereitung Aggregierung Interpretation 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der Praxis 19 / 79 Teil 2: Parallelität und Nebenläufigkeit mit Haskell Funktionale Programmierung in der 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 Funktionale Programmierung in der 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 in der 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 in der 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 in der 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 in der Praxis 25 / 79 Deterministischer Parallelismus in Haskell I I I Die Par Monade Explizite Datenflussinformationen durch Verwendung spezieller “Boxen” Semi-automatische Parallelisierung Funktionale Programmierung in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der Praxis 36 / 79 Speedup mit dynamischer Partitionierung Funktionale Programmierung in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der 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 in der Praxis 51 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 52 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 53 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 54 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 55 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 56 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 57 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 58 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 59 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 60 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 61 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 62 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 63 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 64 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 65 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 66 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 67 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der Praxis 68 / 79 Beispiel: Ausführung von STM Funktionale Programmierung in der 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 in der 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 in der 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 in der 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 in der 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 Funktionale Programmierung in der Praxis = serverSettings 44444 HostAny settings doubleApp x $= =$= processLine) $$ 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 in der Praxis 75 / 79 Benchmark-Ergebnisse I I 5000 parallele Clients 10 Anfragen pro Client Funktionale Programmierung in der Praxis 76 / 79 Resourcen: Parallelität & Nebenläufigkeit I I Folien und Material zum Vortrag: http://factisresearch.com/parallel2013/ 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/ Funktionale Programmierung in der 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 http://book.realworldhaskell.org/ Blog Funktionale Programmierung: http://funktionale-programmierung.de/ Funktionale Programmierung in der 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 in der Praxis 79 / 79