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?”
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.
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
auch Zwerge haben klein angefangen! • erstmal noch “Java in Scala” mit funktionalem “Feenstaub” drauf • immutable Variablen • Pattern Matching • Monaden gemieden
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)
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
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!
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”)
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”
• 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”).
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!
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 ) ) ) )
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
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
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!
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 = ???
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) }
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
• 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
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
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!
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)
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
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!
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)
“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!
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
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
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