Successfully reported this slideshow.
Scalaz Einführung
für Java Programmierer
Bernhard Huemer | IRIAN
bernhard.huemer@irian.at
@bhuemer
Agenda
Keine Einführung in Scala
Keine Einführung in Scalaz
("Haskell für Scala")
Motivation
Transaktionen mit Monads
Type...
Referential transparency
"An expression is said to be referentially transparent if it can be replaced with its value witho...
Exceptions als Beispiel
def f(): Int = {
val y = g()
try {
y + y
} catch {
case e: Exception => 1337
}
}
def f(): Int = {
...
Exceptions als Beispiel (2)
class Cache
(cacheLoader: CacheLoader) {
def get(key: Key): Value = {
// ...
}
}
trait CacheLo...
Call Stack Abhängigkeit
Es treten immer dann Probleme auf,
wenn wir den Call Stack irgendwie ändern.
Multi-threaded Umgebu...
"Do or do not. There is no scala.util.Try[A]." - Yoda
scala.util.Try[A]
import scala.util.{Try, Success, Failure}
def divide: Try[Int] = for {
dividend <- Try(Console.readLine(...
"... the meaning of RT expressions does not depend on context and may be reasoned about locally, while the
meaning of non-...
Typsystem = 
light-weight proof-system
I was eventually persuaded of the need to design programming notations so as to
1. ...
Transaktionen - CMT/Annotations/XML
@Transactional
public void updateSomething() { /* ... */ }
@Transactional
public List<...
Transaktionen - 
BMT/Templates
public void updateSomething(EntityManagerFactory emf) {
transactionTemplate.execute(emf,

n...
Alternative: Transaktions Monad
Monad = Abstraktion zur sequentiellen Ausführung von einzelnen
Schritten in einem bestimmt...
Tx[A]-"Hello World"
def findAllProductIds() /* : Tx[List[Int]] */ = transactional {
em /* : EntityManager */ => // ...
}
d...
Tx<A> mit Java 8
Tx<A> = Keine Proxies, keine Konfiguration, keine sonstige "Magie",
eigentlich nur ein TransactionCallbac...
Tx[A] Ausführung
def run[A](emf: EntityManagerFactory, tx: Tx[A]): A = {
val em = emf.createEntityManager
try {
em.getTran...
Compiler kennt jetzt
Transaktionsklammern
// Oh! Wir haben @Transactional vergessen, sozusagen
def findEmployeeName(employ...
Tx[A] Verarbeitung 
(noch) unschön
def findManagerForEmployee(employeeId: String) = transactional {
em => findEmployee(emp...
Higher-order function: map
trait Tx[+A] { self => // Statt "Tx.this"
// ..
def map[B](f: A => B) = transactional {
em => f...
Verwendung von map() (1)
def findManagerForEmployee(empId: String)/*: Tx[Employee] */ =
findEmployee(empId) map {
employee...
Verwendung von map() (2)
public Tx<Employee> findManagerForEmployee(String empId) {
return findEmployee(empId)
.map(Employ...
Verwendung von map (2)
def findProductIds(searchCrit: String) = transactional {
em => /* ... */
}
def findProductNames(pro...
Higher-order function: flatMap
trait Tx[+A] { self =>
// ..
def flatMap[B](f: A => Tx[B]) = transactional {
em => f(self.r...
Vergleich mit Java 7
public Tx<List<String>>> findProductNamesFor(final String crit) {
return new Tx<List<String>>>() {
pu...
Verwendung von flatMap
def findProductNamesFor(searchCrit: String) =
findProductIds(searchCrit) flatMap {
productIds => fi...
Monad Definition für Scalaz
implicit val txMonad = new scalaz.Monad[Tx] {
def point[A](a: => A): Tx[A] = transactional {
/...
Monadic Functions (1)
// Um die Signatur zu verdeutlichen:
def sequenceM[M[_] : Monad, T](ms: List[M[T]]): M[List[T]] =
ms...
Monadic Functions (2)
def replicateM[M[_] : Monad, A](n: Int, ma: M[A]): M[List[A]] =
sequenceM(List.fill(n)(ma))
Referent...
Unterschiedliche Datenbanken
trait Tx[+A, Tag] {
def run: A
def map[B](A => B): Tx[B, Tag] = // ...
def flatMap[B, ArgModu...
Compiler kennt nun auch
Datenbankgrenzen (1)
def findEmployee(employeeId: String) = txUnit1 { /* .. */ }
def saveEmployee(...
Compiler kennt nun auch
Datenbankgrenzen (2)
// Müssen wir über verschachtelte Transaktionen machen
def findAndSaveEmploye...
Publikumsfrage: Funktioniert das?
@PersistenceContext(unitName = "db1Unit") private EntityManager em1;
@PersistenceContext...
Demo
Fazit
Ziel einer jeden API: LEGO(tm) Baukasten
Seiteneffekte kann man sehr gut einschränken
Deferred Execution / Interpret...
Q&A
Buchempfehlungen
Buchempfehlungen
Danke
Bernhard Huemer | IRIAN
bernhard.huemer@irian.at
@bhuemer
Scalaz introduction for Java programmers
Scalaz introduction for Java programmers
Upcoming SlideShare
Loading in …5
×

Scalaz introduction for Java programmers

730 views

Published on

Scalaz introduct

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Scalaz introduction for Java programmers

  1. 1. Scalaz Einführung für Java Programmierer Bernhard Huemer | IRIAN bernhard.huemer@irian.at @bhuemer
  2. 2. Agenda Keine Einführung in Scala Keine Einführung in Scalaz ("Haskell für Scala") Motivation Transaktionen mit Monads Type classes bzw. Ad-hoc polymorphismus ev. Arbeiten mit der Datenbank ohne* Seiteneffekte ev. Dependency Injection Funktionale APIs gestalten * Fast ohne
  3. 3. Referential transparency "An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program." (d.h. keine Seiteneffekte) def square(i: Int) = i * i // Bad! Referenziert auch etwas anderes als Parameter def now() = System.currentTimeMillis() Keine Reassignment von Variablen Keine Setter, keine Mutations Keine Exceptions Keine Konsolenein-/ausgabe Keine Netzwerkkommunikation ...
  4. 4. Exceptions als Beispiel def f(): Int = { val y = g() try { y + y } catch { case e: Exception => 1337 } } def f(): Int = { try { g() + g() } catch { case e: Exception => 1337 } } def g(): Int = { throw new Exception("No!"); } Checked vs. unchecked exceptions (etwas) irrelevant Compiler versichert, dass Seiteneffekte behandelt werden Besser: Sie treten gar nicht erst auf Viel zu triviales Beispiel, ich weiß!
  5. 5. Exceptions als Beispiel (2) class Cache (cacheLoader: CacheLoader) { def get(key: Key): Value = { // ... } } trait CacheLoader { def load(key: Key): Value // throws Exception! } class Cache (cacheLoader: CacheLoader) { def preload() { // ... } def get(key: Key): Value = { // ... } } Refactoring mit oder ohne Seiteneffekte? Exceptions bei preload: Falsch/teilw. initialisierter Cache, fehlendes Exception-Handling*, etc.? Exceptions bei get: Mühsam * z.B: Applikation startet nicht mehr, weil unbenützter Cache Key nicht geladen werden kann, andere Initialisierungen werden nicht eingeleitet, etc. - Autor von Cache hat keinen Einfluss darauf!
  6. 6. Call Stack Abhängigkeit Es treten immer dann Probleme auf, wenn wir den Call Stack irgendwie ändern. Multi-threaded Umgebungen Web Services RMI oder sonstiges Remoting generell jegl. Netzwerkkommunikation ...
  7. 7. "Do or do not. There is no scala.util.Try[A]." - Yoda
  8. 8. scala.util.Try[A] import scala.util.{Try, Success, Failure} def divide: Try[Int] = for { dividend <- Try(Console.readLine("Dividend:n").toInt) divisor <- Try(Console.readLine("Divisor:n").toInt) } yield (dividend / divisor) val result: Try[Int] = /* .. */ val resultInt = if (result.isSuccess) { result.get } else { 0 } val resultInt = result.getOrElse(0) val resultInt: Try[Int] = result.recover { case e: Exception => 0 } Nicht einfach nur ein Wrapper um Exceptions Macht aus einer partiellen Funktion eine totale (d.h. gut!)
  9. 9. "... the meaning of RT expressions does not depend on context and may be reasoned about locally, while the meaning of non-RT expressions is context dependent and requires more global reasoning." (FPiS)
  10. 10. Typsystem =  light-weight proof-system I was eventually persuaded of the need to design programming notations so as to 1. maximise the number of errors which cannot be made, 2. or if made, can be reliably detected at compile time. - Tony Hoare (ACM Turing Award Lecture) Typsystem kann viel mehr beweisen als "x has method f" Compile time = Build time, d.h. Unit Tests sind gut, aber nicht gut genug
  11. 11. Transaktionen - CMT/Annotations/XML @Transactional public void updateSomething() { /* ... */ } @Transactional public List<SomeType> querySomething() { /* ... */ } Offene Transaktion? @Transactional/Interceptor vergessen? Richtige Transaktion? Implizite Beziehung zwischen Transaktion und EntityManager Kopplung mit Transaktionsattributen (Propagation, Isolation, etc.), retry?
  12. 12. Transaktionen -  BMT/Templates public void updateSomething(EntityManagerFactory emf) { transactionTemplate.execute(emf,
 new TransactionCallbackWithoutResult() { protected void doInTransaction(EntityManager em) { updateOperation1(em); updateOperation2(em); } } ); } Wiederverwendung in anderer Transaktion? Offene/richtige Transaktion? (teilweise ..) EntityManager (o.ä.) müssen durchgereicht werden
  13. 13. Alternative: Transaktions Monad Monad = Abstraktion zur sequentiellen Ausführung von einzelnen Schritten in einem bestimmten Kontext (informell) trait Tx[+A] { 
 // Um die Transaktion "auszuführen" und den Inhalt zu bekommen def run(em: EntityManager): A } object Tx { // DSL für Transaktionen (konvertiert Funktion in Tx[A]) def transactional[A](body: EntityManager => A) = new Tx[A] { override def run(em: EntityManager) = body(em) } } Das ist noch kein Monad, nicht einmal informell. Ich habe unser Reiseziel nur vorweggenommen.
  14. 14. Tx[A]-"Hello World" def findAllProductIds() /* : Tx[List[Int]] */ = transactional { em /* : EntityManager */ => // ... } def findEmployee(employeeId: String) = transactional { em => /* ... */ } So einfach wie mit Annotations - type inference :) Erstmal vollkommen ohne Abhängigkeiten/Seiteneffekte* Unmöglich falschen EntityManager zu verwenden** * Für Skeptiker unter uns: Wie etwas sinnvolles programmieren ohne Seiteneffekte? ** Tony Hoar wäre stolz auf uns!
  15. 15. Tx<A> mit Java 8 Tx<A> = Keine Proxies, keine Konfiguration, keine sonstige "Magie", eigentlich nur ein TransactionCallback<T> mit Lambdas? @FunctionalInterface public interface Tx<T> { public T run(EntityManager em); // Nicht wirklich hilfreich, Return Type sparen wir uns nicht public static <T> Tx<T> transactional( Function<EntityManager, T> body) { return body::apply; } } public Tx<List<Integer>> findProductIds(String searchCrit) { return transactional(em -> /* ... */); } public Tx<Employee> findEmployee(String employeeId) { return em -> /* ... */; }
  16. 16. Tx[A] Ausführung def run[A](emf: EntityManagerFactory, tx: Tx[A]): A = { val em = emf.createEntityManager try { em.getTransaction.begin() val result = tx.run(em) em.getTransaction.commit() result } finally { // Rollback (eventuell) und clean-up } } Fazit bisher: Lediglich BMTs aufgesplittet? Interpretieren des Monads verursacht Seiteneffekte Andere Form der Abstrahierung - eine Beschreibung, viele Möglichkeiten diese zu interpretieren
  17. 17. Compiler kennt jetzt Transaktionsklammern // Oh! Wir haben @Transactional vergessen, sozusagen def findEmployeeName(employeeId: String) = findEmployee(employeeId).name // Wäre zwar nicht so schlimm .. // ERROR: // value name is not a member of ...Tx[...Employee] // Wir sind aber auch vergesslich! def findManagerForEmployee(employeeId: String) = // Lazy loading kann hier aber schon zu Problemen führen! findEmployee(employeeId).department.manager // findEmployee(employeeId).run(em).department.manager // ERROR: // value department is not a member of ...Tx[...Employee] Transaktionskontext benötigt, um Inhalt zu bearbeiten (bzw. eine EntityManager Referenz ..)
  18. 18. Tx[A] Verarbeitung  (noch) unschön def findManagerForEmployee(employeeId: String) = transactional { em => findEmployee(employeeId).run(em).department.manager } public Tx<Employee> findManagerForEmployee(String employeeId) { return em -> findEmployee(employeeId).run(em).getDepartment().getManager(); } Lesbarkeit eher suboptimal, etwas umständlich EntityManager sollten wir nicht durchreichen müssen Gesucht: Funktion mit Signatur Tx[A] => (A => B) => Tx[B]
  19. 19. Higher-order function: map trait Tx[+A] { self => // Statt "Tx.this" // .. def map[B](f: A => B) = transactional { em => f(self.body(em)) } } public interface Tx<T> { // .. default public <U> Tx<U> map(Function<T, U> f) { return em -> f.apply(run(em)); } } Nicht nur bei Collections nützlich Strukturerhaltende Verarbeitung von Elementen
  20. 20. Verwendung von map() (1) def findManagerForEmployee(empId: String)/*: Tx[Employee] */ = findEmployee(empId) map { employee => employee.department.manager } public Tx<Employee> findManagerForEmployee(String empId) { return findEmployee(empId).map( employee -> employee.getDepartment().getManager()); } Keine EntityManager, kein @Transactional - alles wird sozusagen inferred Erstmals wirklicher Unterschied zu der BMT Lösung
  21. 21. Verwendung von map() (2) public Tx<Employee> findManagerForEmployee(String empId) { return findEmployee(empId) .map(Employee::getDepartment) .map(Department::getManager); } def findDepartmentForEmployee(empId: String) = for { employee <- findEmployee(empId) } yield employee.department.manager For comprehensions lediglich syntactic sugar
  22. 22. Verwendung von map (2) def findProductIds(searchCrit: String) = transactional { em => /* ... */ } def findProductNames(productIds: List[Int]) = transactional { em => /* ... */ } def findProductNamesFor(searchCrit: String) = for { productIds <- findProductIds(searchCriteria) } yield findProductNames(productIds) // od. einfacher: def findProductNamesFor(searchCrit: String) = findProductIds(searchCrit) map findProductNames Kompiliert zwar, liefert aber Tx[Tx[List[String] Wir suchen: T[x] => (A => Tx[B]) => Tx[B]
  23. 23. Higher-order function: flatMap trait Tx[+A] { self => // .. def flatMap[B](f: A => Tx[B]) = transactional { em => f(self.run(em)).run(em) } } public interface Tx<T> { // .. default public <U> Tx<U> flatMap(Function<T, Tx<U>> f) { return em -> f.apply(run(em)).run(em); } } Ergebnis von Tx[A] als Eingabe für Tx[B] Tx[B] ersetzt sozusagen Tx[A], führt es aber auch aus Garantiert die selbe Transaktion Garantiert der selbe EntityManager
  24. 24. Vergleich mit Java 7 public Tx<List<String>>> findProductNamesFor(final String crit) { return new Tx<List<String>>>() { public List<String> run(EntityManager em) { List<Integer> productIds = findProductIds(crit).run(em); // self.run(em) return findProductNames(productIds).run(em); // f(..).run(em) } }; } Schwer zu lesen, aber nach wie vor nachvollziehbar(?) Vergleich mit BMT: Suboperationen für sich verwendbar
  25. 25. Verwendung von flatMap def findProductNamesFor(searchCrit: String) = findProductIds(searchCrit) flatMap { productIds => findProductNames(productIds) } def findProductNamesFor(searchCrit: String) = findProductIds(searchCrit) flatMap findProductNames def findProductNamesFor(searchCrit: String) = for { productIds <- findProductIds(searchCrit) productNames <- findProductNames(productIds) } yield productNames Zwei Datenbankqueries in der selben Transaktion Wiederum weder EntityManager noch @Transactional (o.ä.) - Kontext (hier Tx) wird wiederverwendet
  26. 26. Monad Definition für Scalaz implicit val txMonad = new scalaz.Monad[Tx] { def point[A](a: => A): Tx[A] = transactional { // EntityManger wird nicht benötigt, kann ignoriert werden. _ => a } def bind[A, B](fa: Tx[A])(f: A => Tx[B]): Tx[B] = fa flatMap f } Essenz: Wir hatten die Implementierung praktisch schon Gibt Zugriff zu nützlichen Monadic Functions Monad Laws beweisen sparen wir uns momentan ..
  27. 27. Monadic Functions (1) // Um die Signatur zu verdeutlichen: def sequenceM[M[_] : Monad, T](ms: List[M[T]]): M[List[T]] = ms.sequence def zipM[M[_] : Monad, T, U](mt: M[T], mu: M[U]): M[(T, U)] = for { t <- mt u <- mu } yield (t, u) // In Bezug auf Transaktionen bedeutet das: def joinTransactions[T](transactions: List[Tx[T]]): Tx[List[T]] = sequenceM(transactions) def joinTransactions[T, U](txT: Tx[T], txU: Tx[U]): Tx[(T, U)] = zipM(txT, txU) Kontext gibt dabei die jeweilige Semantik an
  28. 28. Monadic Functions (2) def replicateM[M[_] : Monad, A](n: Int, ma: M[A]): M[List[A]] = sequenceM(List.fill(n)(ma)) Referential transparency ist ja wieder gegeben
  29. 29. Unterschiedliche Datenbanken trait Tx[+A, Tag] { def run: A def map[B](A => B): Tx[B, Tag] = // ... def flatMap[B, ArgModule >: Module] (f: A => Tx[B, ArgModule]): Tx[B, Module] = // ... } object Tx { def transactional[A, Tag](body: EntityManager => A) = // ... } sealed trait Unit1 sealed trait Unit2 def txUnit1[A](body: EntityManager => A) = transactional[A, Unit1](body) def txUnit2[A](body: EntityManager => A) = transactional[A, Unit2](body) Tx bezieht sich nun auf bestimmte Datenbank
  30. 30. Compiler kennt nun auch Datenbankgrenzen (1) def findEmployee(employeeId: String) = txUnit1 { /* .. */ } def saveEmployee(employee: Employee) = txUnit2 { /* .. */ } // Wir wollen die zwei Operationen kombinieren def findAndSaveEmployee(employeeId: String) = findEmployee(employeeId) flatMap saveEmployee // Error:(..) type mismatch; // found : ..Tx[Unit,..Unit2] // required: ..Tx[?],..Unit1] // findEmployee(employeeId) flatMap saveEmployee // ^ Modellierung ist einem selbst überlassen - z.B. common Tx für alle?
  31. 31. Compiler kennt nun auch Datenbankgrenzen (2) // Müssen wir über verschachtelte Transaktionen machen def findAndSaveEmployee(employeeId: String) /*: Tx[Tx[Unit, Unit2], Unit1] */ = findEmployee(employeeId) map saveEmployee
  32. 32. Publikumsfrage: Funktioniert das? @PersistenceContext(unitName = "db1Unit") private EntityManager em1; @PersistenceContext(unitName = "db2Unit") private EntityManager em2; @Transactional("unit1Manager") @Transactional("unit2Manager") // Employee redundant in zwei Datenbanken speichern public void saveEmployee(String employeeName) { em1.persist(new Employee(employeeName)); em2.persist(new Employee(employeeName)); } def saveEmployee(employeeName: String) = ( txUnit1 { em1 => em1.persist(new Employee(employeeName)) }, txUnit2 { em2 => em2.persist(new Employee(employeeName)) } ) Wie sicher ist man bei der Antwort dieser Frage?
  33. 33. Demo
  34. 34. Fazit Ziel einer jeden API: LEGO(tm) Baukasten Seiteneffekte kann man sehr gut einschränken Deferred Execution / Interpretation, wenn man Seiteneffekte benötigt Der (Scala!) Compiler ist dein Freund!
  35. 35. Q&A
  36. 36. Buchempfehlungen
  37. 37. Buchempfehlungen
  38. 38. Danke Bernhard Huemer | IRIAN bernhard.huemer@irian.at @bhuemer

×