40
Scalaz Einführung für Java Programmierer Bernhard Huemer | IRIAN [email protected] @bhuemer

Scalaz introduction for Java programmers

Embed Size (px)

DESCRIPTION

Scalaz introduct

Citation preview

Page 1: Scalaz introduction for Java programmers

Scalaz Einführungfür Java Programmierer

Bernhard Huemer | [email protected]

@bhuemer

Page 2: Scalaz introduction for Java programmers
Page 3: Scalaz introduction for Java programmers

Agenda

Keine Einführung in ScalaKeine Einführung in Scalaz("Haskell für Scala")

MotivationTransaktionen mit MonadsType classes bzw. Ad-hoc polymorphismusev. Arbeiten mit der Datenbank ohne* Seiteneffekteev. Dependency Injection

Funktionale APIs gestalten

* Fast ohne

Page 4: Scalaz introduction for Java programmers

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 Parameterdef now() = System.currentTimeMillis()

Keine Reassignment von VariablenKeine Setter, keine MutationsKeine ExceptionsKeine Konsolenein-/ausgabeKeine Netzwerkkommunikation...

Page 5: Scalaz introduction for Java programmers

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) irrelevantCompiler versichert, dass Seiteneffekte behandelt werdenBesser: Sie treten gar nicht erst auf

Viel zu triviales Beispiel, ich weiß!

Page 6: Scalaz introduction for Java programmers

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, fehlendesException-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!

Page 7: Scalaz introduction for Java programmers

Call Stack AbhängigkeitEs treten immer dann Probleme auf,

wenn wir den Call Stack irgendwie ändern.

Multi-threaded UmgebungenWeb ServicesRMI oder sonstiges Remotinggenerell jegl. Netzwerkkommunikation...

Page 8: Scalaz introduction for Java programmers

"Do or do not. There is no scala.util.Try[A]." - Yoda

Page 9: Scalaz introduction for Java programmers

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 ExceptionsMacht aus einer partiellen Funktion eine totale (d.h. gut!)

Page 10: Scalaz introduction for Java programmers

"... the meaning of RT expressions does not depend on context and may be reasoned about locally, while themeaning of non-RT expressions is context dependent and requires more global reasoning." (FPiS)

Page 11: Scalaz introduction for Java programmers

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 gutgenug

Page 12: Scalaz introduction for Java programmers
Page 13: Scalaz introduction for Java programmers

Transaktionen - CMT/Annotations/XML@Transactionalpublic void updateSomething() { /* ... */ }

@Transactionalpublic List<SomeType> querySomething() { /* ... */ }

Offene Transaktion? @Transactional/Interceptor vergessen?Richtige Transaktion? Implizite Beziehung zwischen Transaktion undEntityManagerKopplung mit Transaktionsattributen (Propagation, Isolation, etc.), retry?

Page 14: Scalaz introduction for Java programmers

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

Page 15: Scalaz introduction for Java programmers

Alternative: Transaktions Monad

Monad = Abstraktion zur sequentiellen Ausführung von einzelnenSchritten 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.

Page 16: Scalaz introduction for Java programmers

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!

Page 17: Scalaz introduction for Java programmers

Tx<A> mit Java 8

Tx<A> = Keine Proxies, keine Konfiguration, keine sonstige "Magie",eigentlich nur ein TransactionCallback<T> mit Lambdas?

@FunctionalInterfacepublic 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 -> /* ... */;}

Page 18: Scalaz introduction for Java programmers

Tx[A] Ausführungdef 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 SeiteneffekteAndere Form der Abstrahierung - eine Beschreibung, vieleMöglichkeiten diese zu interpretieren

Page 19: Scalaz introduction for Java programmers

Compiler kennt jetztTransaktionsklammern

// Oh! Wir haben @Transactional vergessen, sozusagendef 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 ..)

Page 20: Scalaz introduction for Java programmers

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ändlichEntityManager sollten wir nicht durchreichen müssenGesucht: Funktion mit Signatur Tx[A] => (A => B) => Tx[B]

Page 21: Scalaz introduction for Java programmers

Higher-order function: maptrait 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ützlichStrukturerhaltende Verarbeitung von Elementen

Page 22: Scalaz introduction for Java programmers

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 sozusageninferredErstmals wirklicher Unterschied zu der BMT Lösung

Page 23: Scalaz introduction for Java programmers

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

Page 24: Scalaz introduction for Java programmers

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]

Page 25: Scalaz introduction for Java programmers

Higher-order function: flatMaptrait 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 ausGarantiert die selbe TransaktionGarantiert der selbe EntityManager

Page 26: Scalaz introduction for Java programmers

Vergleich mit Java 7public 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

Page 27: Scalaz introduction for Java programmers

Verwendung von flatMapdef 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 TransaktionWiederum weder EntityManager noch @Transactional (o.ä.) - Kontext(hier Tx) wird wiederverwendet

Page 28: Scalaz introduction for Java programmers

Monad Definition für Scalazimplicit 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 schonGibt Zugriff zu nützlichen Monadic FunctionsMonad Laws beweisen sparen wir uns momentan ..

Page 29: Scalaz introduction for Java programmers

Monadic Functions (1)// Um die Signatur zu verdeutlichen:def sequenceM[M[_] : Monad, T](ms: List[M[T]]): M[List[T]] = ms.sequencedef 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

Page 30: Scalaz introduction for Java programmers

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

Page 31: Scalaz introduction for Java programmers

Unterschiedliche Datenbankentrait 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 Unit1sealed 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

Page 32: Scalaz introduction for Java programmers

Compiler kennt nun auchDatenbankgrenzen (1)

def findEmployee(employeeId: String) = txUnit1 { /* .. */ }def saveEmployee(employee: Employee) = txUnit2 { /* .. */ }

// Wir wollen die zwei Operationen kombinierendef 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?

Page 33: Scalaz introduction for Java programmers

Compiler kennt nun auchDatenbankgrenzen (2)

// Müssen wir über verschachtelte Transaktionen machendef findAndSaveEmployee(employeeId: String) /*: Tx[Tx[Unit, Unit2], Unit1] */ = findEmployee(employeeId) map saveEmployee

Page 34: Scalaz introduction for Java programmers

Publikumsfrage: Funktioniert das?@PersistenceContext(unitName = "db1Unit") private EntityManager em1;@PersistenceContext(unitName = "db2Unit") private EntityManager em2;

@Transactional("unit1Manager")@Transactional("unit2Manager") // Employee redundant in zwei Datenbanken speichernpublic 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?

Page 35: Scalaz introduction for Java programmers

Demo

Page 36: Scalaz introduction for Java programmers

Fazit

Ziel einer jeden API: LEGO(tm) BaukastenSeiteneffekte kann man sehr gut einschränkenDeferred Execution / Interpretation, wenn man Seiteneffekte benötigtDer (Scala!) Compiler ist dein Freund!

Page 37: Scalaz introduction for Java programmers

Q&A

Page 38: Scalaz introduction for Java programmers

Buchempfehlungen

Page 39: Scalaz introduction for Java programmers

Buchempfehlungen

Page 40: Scalaz introduction for Java programmers

Danke

Bernhard Huemer | [email protected]

@bhuemer