funktionale programmierung in java 8 und scala auf der codetalks hamburg am am 29.9.2016
TRANSCRIPT
Insert company logo
New Technology
Scala: Durch Java 8 überflüssig geworden?Lutz Hühnken / @lutzhuehnken
@lutzhuehnken 29/09/2016
Was macht funktionale Programmierung aus?
• Funktionen • (Unveränderliche) Werte • Keine Seiteneffekte
Funktionen
• Code vs. Daten
scala> val sum = (x:Int, y:Int) => x + y Type of sum is (Int, Int) => Int
Keine Seiteneffekte
• Ausdrücke evaluieren vs. Anweisungen ausführen • Ziel: Referenzielle Transparenz
Die Disziplinen
• Funktionen höherer Ordnung • Unveränderliche Werte und Datenstrukturen • Currying, partiell evaluierte Funktionen • Tupel • Pattern Matching • Ausdrücke • Rekursion
Die Teams
• Scala - http://www.scala-lang.org • Vereinigt OO mit FP
• Java8 mit Verstärkung: • Immutables - https://immutables.github.io • PCollections - http://pcollections.org • Javaslang - http://www.javaslang.io • Halva - https://github.com/soabase/soabase-halva
Funktionen
List<String> getNames(List<Customer> customers) { List<String> names = new ArrayList<String>(); for (customer : customers) { names.add(customer.getName()); } return names; }
Was wir nicht (mehr) wollen
Funktionen
List<String> names = myList.stream()
.map(c -> c.name)
.collect(Collectors.toList());
Java 8
Unveränderliche Werte
• Werte sind unveränderlich - nicht nur die Referenz (final), auch das Object, auf das sie zeigt! Werte
• Können geteilt werden • Haben stabilen Hashcode • Können übertragen werden
Werte
case class Time(hours: Int = 0, minutes: Int = 0)
val t1 = Time(12,0)
val t2 = Time(hours = 12)
val t3 = t2.copy(minutes = 30)
Scala
Werte
@Immutable public final class Time {
public final Integer hours; public final Integer minutes;
public Time(Integer hours, Integer minutes) { this.hours = hours; this.minutes = minutes; }
@Override public boolean equals(@Nullable Object other) { if (this == other) return true; if (!(other instanceof Time)) return false; Time otherTime = (Time) other;
return hours.equals(otherTime.hours) && minutes.equals(otherTime.minutes); }
@Override public int hashCode() { return Objects.hash(hours, minutes); }
@Override public String toString() { return MoreObjects.toStringHelper("Time") .add("hours", hours).add("minutes", minutes).toString(); } }
Java 8
Werte
import org.immutables.value.Value;
@Value.Immutable public abstract class Time { public abstract Integer hours(); public abstract Integer minutes(); }
Immutables
Werte
Halva
@CaseClass public interface Time { Integer hours(); Integer minutes(); }
Time t1 = new TimeCase(12,0);
Unveränderliche Datenstrukturen
Für Aggregate (Collections) muss das gleiche gelten wie für ihre Elemente!
Auch sie wollen wir teilen und übertragen können - kein „PlOP“.
Datenstrukturen (Collections)
scala> val v1 = Vector(1,2,3) v1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)
scala> v1.updated(0,3) res0: scala.collection.immutable.Vector[Int] = Vector(3, 2, 3)
scala> v1 res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)
Scala
Datenstrukturen (Collections)
import org.pcollections.PVector;import org.pcollections.TreePVector;
PVector<Integer> pv1 = TreePVector.from(Arrays.asList(1,2,3));
pv1.with(0,3);
System.out.println(pv1.toString()); // [1, 2, 3]
PCollections
Collections
• Fokussiert auf persistente Collections • Minimale API (plus, minus, with..) • Keine zusätzlichen Methoden, keine Lambda-
Unterstützung • Drop-In-Replacement (PVector implementiert
java.util.List, HashPMap impl. java.util.Map..)
Datenstrukturen (Collections)
import javaslang.collection.Vector;
Vector<Integer> v1 = Vector.of(1,2,3);
v1.update(0,3);
System.out.println(v1.mkString(",")); // (1,2,3)
Javaslang
Javaslang
• Bietet „reiche“ API (mit drop, take, permutation, map, flatMap, filter, collect, sliding, etc., entsprechend Scala-Collections.), inkl. Funktionen höherer Ordnung.
• Kein Drop-In-Replacement (implementiert nicht java.util.Collection etc.)
Currying
scala> def sum(a:Int)(b:Int) = a + b sum: (a: Int)(b: Int)Int
scala> val sum2: Function[Int,Int] = sum(2) sum2: Function[Int,Int] = <function1>
scala> sum2(4) res1: Int = 6
Scala
Currying
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
Function1<Integer, Integer> add2 = sum.curried().apply(2);
add2.apply(4) // 6
Javaslang
Tupel
scala> val java8 = ("Java", 8) java8: (String, Int) = (Java,8)
scala> java8._1 res0: String = Java
scala> java8._2 res1: Int = 8
scala> List("a","b","c").zip(List(1,2,3)) res2: List[(String, Int)] = List((a,1), (b,2), (c,3))
Scala
Tupel
Tuple2<String, Integer> java8 = Tuple.of("Java", 8);
String s = java8._1; // "Java"
Integer i = java8._2; // 8
List<Tuple2<String,Integer>> zipped = List.of("a", "b", "c").zip(List.of(1,2,3));System.out.println(zipped.mkString(", ")); // (a, 1), (b, 2), (c, 3)
Javaslang
Pattern Matching
String s = Match(i).of( Case($(1), "one"), Case($(2), "two"), Case($(), "?") );
Javaslang
Ausdrücke
• Ausdrücke („Expressions“) vs. Befehle („Statements“). • Statements haben kein Ergebnis und werden nur um
des Seiteneffekts wegen ausgeführt. • Wichtige Java-Kontrollstrukturen sind Statements (if,
for, while, try…). • In Scala ist alles ein Ausdruck.
Ausdrücke
for { n <- 1 to 3 m <- 1 to n } yield n * m
res0: Vector(1, 2, 4, 3, 6, 9)
scala> val b = if (3 < 4) "yes" else "no" b: String = yes
Scala
Rekursion
def factorial(n: Int, acc: BigInt = 1): BigInt = if (n == 0) acc else factorial(n - 1, n * acc)
Scala
Rekursion
• In Scala ok, da rechtsrekursiv (tail recursive) • In Java - java.lang.StackOverflowError
Problemfelder in Java
• Syntax - Unterstützung für Datenklassen (kommt in Java 10?), Tupel fehlt
• Unveränderliche Datenstrukturen (Collections) fehlen • Zu viele Anweisungen (if, switch, for..), zu wenig
Ausdrücke • Checked Exceptions • Keine Tail-Call-Optimierung für rechtsrekursive
Funktionen
Fazit
• Mit der Kombination Lambdas, Javaslang und Halva oder Immutables lässt sich brauchbar funktional programmieren - es gibt also keine Ausrede!
• Es gibt aber noch viel zu tun! Scala bleibt fürs erste die deutlich komfortablere Lösung.