Save 37% off PRO during our Black Friday Sale! »

Funktionale Programmierung in der Praxis

Funktionale Programmierung in der Praxis

Ein Vortrag im Rahmen der Vorlesung "Funktionale Programmierung" an der Universität Rostock.

16ad3ff954790c142086a2dfde2aea48?s=128

Jens Grassel

January 31, 2020
Tweet

Transcript

  1. Funktionale Programmierung in der Praxis Andr´ e Sch¨ utz, Jens

    Grassel . . Wegtam GmbH
  2. Wie kamen wir zu funktionaler Programmierung?

  3. Werdegang Am Anfang war die Suchmaschine... • so um Entwicklung

    einer eigenen Suchmaschine • in Java (ohne EE und ohne Spring ;-)) • Gr¨ undung einer eigenen Firma daf¨ ur • Gewinn eines Innovationspreises • neues Projekt (Forschung und Entwicklung) ⇒ Daten-Integrations-Plattform • Grenzen von Java erreicht ⇒ “Geht es nicht auch besser?”
  4. Die Qual der Wahl... Es gibt eine Vielzahl von Programmiersprachen,

    aber . Wir wollten funktional programmieren (Affinit¨ at zu Lisp). . Wir hatten ein existierendes ¨ Okosystem (Integration). Verschiedene M¨ oglichkeiten • Lisp-Dialekte (Common Lisp, Scheme, Clojure, SBCL) • ML-Dialekte (SML, OCaml, Haskell) • Sonstige (Erlang, Scala) Am Ende blieb die Wahl zwischen Clojure und Scala.
  5. Warum Scala? Wir entschieden uns f¨ ur Scala... • Wir

    wollten in der JVM bleiben. • Wir wollten eine statisch typisierte Programmiersprache. • zum Entscheidungszeitpunkt schon etwas “erwachsen” • Interoperabilit¨ at mit existierendem Java-Code • Verf¨ ugbarkeit von Bibliotheken, Treibern(!) und Toolkits
  6. Unser Weg mit funktionaler Programmierung

  7. M¨ uhsam ern¨ ahrt sich das Eichh¨ ornchen... Nur Mut,

    auch Zwerge haben klein angefangen! • erstmal noch “Java in Scala” mit funktionalem “Feenstaub” drauf • immutable Variablen • Pattern Matching • Monaden gemieden
  8. Weitere Schritte: Funktoren Was man so tut, wenn man keine

    Ahnung hat... val a: List[Int] = ??? val b = for (i <- 0 until a.length) yield a(i) + 1 Es gibt Funktoren, einfach gesprochen: “map” val b = a.map(_ + 1) Und es gibt noch mehr... • contravariante Funktoren (contramap ⇒ “prepend”) • invariante Funktoren (imap ⇒ map + contramap)
  9. Weitere Schritte: Monoide ( / ) Was ist ein Monoid?

    Eine Halbgruppe mit einem neutralen Element. Wozu braucht man das? ⇒ Zum Zusammenfassen von Mengen zum Beispiel. Aber, das geht doch auch so. @ 1 + 2 res0: Int = 3 Stimmt, doch was ist damit? @ Option(1) + Option(2) type mismatch Compilation Failed
  10. Weitere Schritte: Monoide ( / ) Es geht nat¨ urlich,

    wenn man unbedingt will: @ Option(1).map(a => Option(2).map(b => a +b)) res0: Option[Option[Int]] = Some(Some(3)) @ Option(1).map(a => Option(2).map(b => a +b)) .flatten res1: Option[Int] = Some(3) Mit den entsprechenden Instanzen verf¨ ugbarer Monoide geht sowas: @ Option(1) |+| Option(2) res0: Option[Int] = Some(3) Denkt das Szenario weiter in Richtung ADTs, die kombiniert werden k¨ onnen!
  11. Weitere Schritte: Monaden ( / ) Was ist ein Monad

    eigentlich? Ein Monoid in der Kategorie der Endofunktoren. Aber das ist nicht wichtig! Was sind die praktischen Anwendungsm¨ oglichkeiten? • Sequenzierung (“Hurra, wir k¨ onnen Batch-Skripte schreiben!”) • “Lifting” von Werten in einen Monad (via pure) • Kapselung von Effekten • starke Garantien hinsichtlich des Verhaltens (“Monad Laws”)
  12. Weitere Schritte: Monaden ( / ) Durch Nutzung von Monaden

    beschreiben wir das Programm als abstrakten Syntaxbaum (AST) ⇒ “Free Monads” • “Programm” wird erst bei Bedarf ausgef¨ uhrt • M¨ oglichkeiten zur Optimierung (Interpreter) • konsistente Fehlerbehandlung durch MonadError Der gr¨ oßte Nachteil ⇒ Monads do not compose! • Monad-Transformer • Konstruktor, der einen Monad bekommt und wieder einen zur¨ uckliefert • Nachteile: unhandlich, “teuer”
  13. Das Streben nach “pure” FP... Warum “pure”? • keine Seiteneffekte

    • lazy evaluation • Ausgabe von Funktionen nur abh¨ angig von ihrer Eingabe Das alles f¨ uhrt zu referentieller Transparenz und besserem Verst¨ andnis des Programmverhaltens (via “informal reasoning”).
  14. You have reached the next level! Mit wachsender Erfahrung werden

    auch “fortgeschrittene” Techniken allt¨ aglich angewendet. • Folds statt Pattern Matching • IO-Monad (cats-effect) • Rekursionsschemata • Refined Types • Optics
  15. Welche Probleme l¨ ost dies f¨ ur uns?

  16. Kein “low level” optimierter Code mehr! ( / ) case

    class Street(number: Int, name: String) case class Address(city: String, street: Street) case class Company(name: String, address: Address) case class Employee(name: String, company: Company) val employee = ??? Jetzt wollen wir den ersten Buchstaben des Straßennamens der Firma großschreiben!
  17. Kein “low level” optimierter Code mehr! ( / ) So

    in etwa s¨ ahe das ohne Optics aus... employee.copy( company = employee.company.copy( address = employee.company.address.copy( street = employee.company.address.street.copy( name = employee.company.address.street.name .capitalize ) ) ) )
  18. Kein “low level” optimierter Code mehr! ( / ) Und

    nun mit Optics... employee .lens(_.company.address.street.name) .composeOptional(headOption) .modify(_.toUpper) Aber das ist noch nicht alles! • Traversieren von Strukturen • Rekursion (bzw. Rekursionsschemata) • kein “Bits schieben” mehr
  19. Kein “low level” optimierter Code mehr! ( / ) Aber

    was sind eigentlich “Optics”? • pure funktionale Abstraktionen zur Manipulation von immutable Objekten • Basis ist Iso (Ja, kurz f¨ ur Isomorphismus. ;-)) • darauf aufbauend: • Lens (in Daten “reinzoomen”) • Prism “Teile” von Daten selektieren • des weiteren: Getter, Optional, Traversal, Setter, Fold • in Haskell gibt es Lens https://github.com/ekmett/lens
  20. Keine unkontrollierten Seiteneffekte mehr! ( / ) Die ¨ ublichen

    Trivialbeispiele, aber ihr versteht worum es geht. for { _ <- Future { println("Hi there!") } _ <- Future { println("Hi there!") } } yield () Was macht der Code? val printF = Future { println("Hi there!") } for { _ <- printF _ <- printF } yield () Und was macht dieser?
  21. Keine unkontrollierten Seiteneffekte mehr! ( / ) Es geht auch

    anders, sprich: “IO gibt es nicht nur bei Haskell.” val printF = IO(println("Hi there!")) val effect = for { _ <- printF _ <- printF } yield () Und dieser Code macht tats¨ achlich das, was da steht! :-) @ effect.unsafeRunSync Hi, there! Hi, there!
  22. Wir m¨ ussen weniger Tests schreiben! Nutzen des Typsystems •

    Vermeidung von “stringified programming” • Compiler verhindert Fehler, die wir sonst per Tests finden m¨ ußten! • per Refined Types noch ’ne Schippe drauf • Ergo, der Compiler verhindert noch mehr Fehler! :-) • Reduzierung des Implementierungsraums durch Typisierung und Polymorphismus • D.h. es wird einfacher Funktionen zu implementieren. def f(a: String): String = ??? // versus def f(a: IPv4): Hostname = ???
  23. Wir k¨ onnen bessere Tests schreiben! Es werden nicht nur

    weniger Tests, sondern auch bessere! • leichteres Testen durch Modularisierung und Isolation (ASTs, IO) • robusteres Testen durch “Property Based Testing” forAll("Product ID") { id: ProductId => val uri = Uri(s"/product/$id") val response: IO[Response] = service.orNotFound .run(Request(method = GET, uri = uri)) response.unsafeRunSync.status must be(expectedStatus) }
  24. Die Entwicklungsgeschwindigkeit erh¨ oht sich! Das klingt jetzt erstmal merkw¨

    urdig, aber • Refactorings werden viel leichter • Compiler sagt quasi was zu tun ist (“follow the types”) • Property Based Tests finden viel mehr Fehler • Code ist verst¨ andlicher (h¨ ohere Abstraktionsebene) • h¨ oheres Vertrauen in schnelle ¨ Anderungen
  25. Die Anwendungen laufen schneller! Auch das klingt merkw¨ urdig, aber

    • Das alte Vorurteil “Quick ’n dirty is faster!” ist gefallen! • nat¨ urlich abh¨ angig vom Kontext • Beispiel aus Webservice-Bereich • “pure” L¨ osung bis zu mal(!) schneller als “impure” • dazu weniger Systemlast https://leanpub.com/pfhais
  26. Erfahrungsbericht Datenintegration Die Datenintegrationsl¨ osung wurde in Monaten von Leuten

    konzipiert und entwickelt. Hier einige Eckdaten: • auf “Augenh¨ ohe” mit etablierten L¨ osungen (Talend, Informatica, etc.) • Features, die die anderen nicht hatten (Wahrung referentieller Integrit¨ at, Ber¨ ucksichtigung der Semantik von Daten) • Service basierte Architektur und Aktorensystem (beliebig skalierbar) • ca. . LoC (plus . LoC Tests) • vergleichbare Projekte gehen in die Millionen LoC • und wurden ¨ uber Zeitr¨ aume von - Jahren (und mehr) entwickelt
  27. Erfahrungsbericht Verkaufsplattform Onlineh¨ andler ( / ) Unterst¨ utzung eines

    großen internationalen H¨ andlers ¨ uber Jahre. • große Anzahl an Services und Bibliotheken (Java und Scala) • keine Dokumentation Hier einige Beispiele aus der Zeit... Anmerkung zu LoC Unsere Angabe f¨ ur die Zeilenanzahl (LoC) bezieht eventuelle vorhandene Dokumentation mit ein. Im Regelfall sind - Prozent der LoC in unserem Code Funktionsdokumentation. Im Kundenbereich geht dieser Wert normalerweise gegen Null!
  28. Erfahrungsbericht Verkaufsplattform Onlineh¨ andler ( / ) Beispiel “Datenkonvertierungsservice” •

    Mischung aus Java (mit Spring) und etwas Scala und Akka (Aktoren) • Kurzfassung: “horrible, mutable stuff” • etwa . LoC (ca. LoC Testcode) • selbst erfahrene Entwickler brauchten Tage f¨ ur kleinste ¨ Anderungen • viele Bugs, Abst¨ urze, “h¨ angen bleiben” • hohe Systemlast • “langsam” (mehrere Stunden pro Land)
  29. Erfahrungsbericht Verkaufsplattform Onlineh¨ andler ( / ) Beispiel “Datenkonvertierungsservice” •

    Rewrite komplett in Scala mit funktionaler Programmierung und Refined Types • LoC (davon sind LoC Testcode!) • viel leichter zug¨ anglich ⇒ schnelle ¨ Anderungen • Datenverarbeitung mit Streams • automatische Anpassung an Systemlast (keine ¨ Uberlastung) • “schnell” (mehrere Minuten(!) pro Land) • Fehler in ¨ ubernommener Verarbeitungslogik gefunden durch Property Based Testing
  30. Erfahrungsbericht Verkaufsplattform Onlineh¨ andler ( / ) Beispiel “Backend f¨

    ur Webshop” • komplett in Scala (Nutzung des Play Framework) • viele “Altlasten” (großes Projekt, unerfahrene Entwickler) • ca. . LoC (davon . Testcode) • Code sehr eng verdrahtet (tight coupling) • fragt verschiedenste Services an • aber geht davon aus, daß diese immer erreichbar sind!
  31. Erfahrungsbericht Verkaufsplattform Onlineh¨ andler ( / ) Beispiel “Backend f¨

    ur Webshop” • kein Rewrite, sondern Refactoring • Entkopplung und Modularisierung zur schrittweises Aufl¨ osen der engen Verdrahtung • Team entschied sich f¨ ur den Ansatz “Tagless Final“ (quasi “Interpreter Pattern”) Ziele wurden erreicht • Stabilisierung des Systems • Erh¨ ohung der Entwicklungsgeschwindigkeit • konsistente Fehlerbehandlung (MonadError)
  32. Welche Schwierigkeiten und H¨ urden gab und gibt es?

  33. Angst beim Kunden Was h¨ oren wir am h¨ aufigsten?

    “Unsere Leute verstehen das nicht!” Aber das ist Unfug! • lediglich Ber¨ uhrungs¨ angste • alte Vorurteile • “Das ist akademischer Unfug, weltfremd!” • “Daf¨ ur muß man Mathe studiert haben!” • mangelndes Vertrauen in Mitarbeiter Jeder kann das!
  34. H¨ oherer Einarbeitungsaufwand Trotzdem gibt es auch H¨ urden... •

    Denken zumeist stark imperativ gepr¨ agt • oft wenig bis gar keinen Kontakt mit funktionaler Programmierung Es braucht Zeit. • alte Denkmuster abzulegen • m¨ ogliche Abstraktionen zu erkennen
  35. Zusammenfassung

  36. Was bleibt unterm Strich? • Viele Wege f¨ uhren nach

    Rom! • oder auch “Es muß nicht immer < SpracheX > sein!” • Auswahl je nach Aufgabe und Umgebung • Nutzt statisch typisierte funktionale Programmierung! • je gr¨ oßer die Code-Basis, desto mehr hilft der Compiler • findet Fehler nicht erst zur Laufzeit • Nutzt eine “richtige” funktionale Sprache! • imperative Sprachen mit “rangeschraubten” funktionalen Features :-( • ausgebremst durch Grenzen der Sprache
  37. Jeder ist seines Gl¨ uckes Schmied! Habt keine Angst vorm

    Endofunktor! • Es ist gar nicht so schwer! • Es ist nicht umsonst, d.h. “Es gibt Jobs!” • Es erleichtert die Arbeit oder auch: “Softwareentwicklung ist schon schwer genug!” Hebt Euch ab von der Masse oder nach Paul Graham : “Beat the Averages!” http://www.paulgraham.com/avg.html
  38. Vielen Dank! Gibt es Fragen? Wegtam GmbH, Neuer Markt ,

    Rostock https://www.wegtam.com