fp in java - project lambda and beyond

39
FP in Java Project Lambda and beyond by Mario Fusco [email protected] twitter: @mariofusco

Upload: mario-fusco

Post on 10-May-2015

4.883 views

Category:

Technology


4 download

TRANSCRIPT

Page 1: FP in Java - Project Lambda and beyond

FP in JavaProject Lambda and beyond

by Mario [email protected]: @mariofusco

Page 2: FP in Java - Project Lambda and beyond

Project Lambda – A Brief History

• 2006 – Gosling: "We will never have closures in Java"

• 2007 – 3 different proposals for closures in Java

• 2008 – Reinhold: "We will never have closures in Java"

• 2009 – Start of project Lambda (JSR 335)

public boolean javaWillHaveClosure() {

return currentYear % 2 == 1;

}

Page 3: FP in Java - Project Lambda and beyond

From Single Method Interfaces …

public interface Comparator<T> {

int compare(T o1, T o2);

}

Collections.sort(strings, new Comparator<String>() {

public int compare(String s1, String s2) {

return s1.compareToIgnoreCase(s2);

}

});

� Bulky syntax

� Confusion surrounding the meaning of names and this

� Inability to capture non-final local variables

� Inability to abstract over control flow

Functional

Interface

Page 4: FP in Java - Project Lambda and beyond

… To Lambda Expressions

Collections.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));

Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);

Lambda expression are always converted to

instance of a functional interface

Compiler figures out the types

No need of changing the JVM to create a new

type for lambda expressions

Page 5: FP in Java - Project Lambda and beyond

Anatomy of a lambda expression

s -> s.length()

(int x, int y) -> x + y

() -> 42

(x, y, z) -> {

if (x) {

return y;

} else {

return z;

}

}

The formal parameters of

a lambda expression may

have either inferred or

declared types

A lambda expression is like a method: it provides a list of formal parameters and a body

A lambda body is either a

single expression or a block

Return is implicit and

can be omitted

Page 6: FP in Java - Project Lambda and beyond

Common JDK8 functional interfaces

� Predicate � a property of the object passed as argument

� Block � an action to be performed with the object passed as argument

� Function � transform a T to a U

� BiFunction � transform a (T, U) to a V

� Supplier � provide an instance of a T (such as a factory)

� UnaryOperator � a unary operator from T -> T

� BinaryOperator � a binary operator from (T, T) -> T

Page 7: FP in Java - Project Lambda and beyond

Why Lambdas?

�API designers can build more powerful, expressive APIs

�More room for generalization

o Pass behaviors to a method together with normal data

� Libraries remain in control of computation

o e.g. internal vs. external iteration

�More opportunities for optimization

o Laziness

o Parallelism

o Out-of-order execution

�More regular and then more readable code

o e.g. nested loops vs. pipelined (fluent) operations

�Better composability and reusability

Page 8: FP in Java - Project Lambda and beyond

An Example: Sorting

Comparator<Person> byAge = new Comparator<Person>() {

public int compare(Person p1, Person p2) {

return p1.getAge() – p2.getAge();

}

};

Collections.sort(people, byAge);

Comparator<Person> byAge = (p1, p2) -> p1.getAge() – p2.getAge();

Functional interface

Lambda expression

Collections.sort(people, (p1, p2) -> p1.getAge() – p2.getAge());

Page 9: FP in Java - Project Lambda and beyond

Can We Do Better?

Collections.sort(people, comparing(Person::getAge));

Method reference

Comparator<Person> byAge = Comparators.comparing(p -> p.getAge());

Comparator<Person> byAge = Comparators.comparing(Person::getAge);

Collections.sort(people, comparing(Person::getAge)

.compose(comparing(Person::getName)));

Collections.sort(people, comparing(Person::getAge).reverse());

Reusability

Readability

Composability

Page 10: FP in Java - Project Lambda and beyond

Extension methods

interface Iterator<E> {

boolean hasNext();

E next();

void remove();

default void forEach(Block<? super E> block) {

while (hasNext())

block.accept(next());

}

}

� Add methods to existing interfaces without breaking the backward compatibility

� Primary goal is API evolution, but useful as an inheritance mechanism on its own

� Add multiple inheritance of behavior to the always existed multiple inheritance of

type, but no multiple inheritance of state

Page 11: FP in Java - Project Lambda and beyond

Extension methods

interface Iterator<E> {

boolean hasNext();

E next();

void remove();

default void forEach(Block<? super E> block) {

while (hasNext())

block.accept(next());

}

}

� Add methods to existing interfaces without breaking the backward compatibility

� Primary goal is API evolution, but useful as an inheritance mechanism on its own

� Add multiple inheritance of behavior to the always existed multiple inheritance of

type, but no multiple inheritance of state

� Can be used to declare “optional” methods

default void remove() { throw new UnsupportedOperationException(); }

Page 12: FP in Java - Project Lambda and beyond

Internal VS External iteration

for (Employee e : employees) {

e.setSalary(e.getSalary() * 1.03);

}

employees.forEach(e -> e.setSalary(e.getSalary() * 1.03));

Inherently serial

Client has to manage iteration

Nested loops are poorly readable

+ Library is in control � opportunity for internal optimizations as parallelization,

lazy evaluation, out-of-order execution

+ More what, less how � better readability

+ Fluent (pipelined) operations � better readability

+ Client can pass behaviors into the API as data �

possibility to abstract and generalize over behavior �

more powerful, expressive APIs

Not only a syntactic change!

Page 13: FP in Java - Project Lambda and beyond

Streams - Efficiency with laziness

� Represents a stream of values

� Not a data structure: doesn't store values

� Source can be Collection, array, generating function, I/O ....

� Encourages a pipelined ( "fluent" ) usage style

� Operations are divided between intermediate and terminal

� Lazy in nature: only terminal operations actually trigger a computation

employees.stream()

.filter(e -> e.getIncome() > 50000)

.map(e -> e.getName())

.forEach(System.out::println);

Page 14: FP in Java - Project Lambda and beyond

Streams - Efficiency with laziness

� Represents a stream of values

� Not a data structure: doesn't store values

� Source can be Collection, array, generating function, I/O ....

� Encourages a pipelined ( "fluent" ) usage style

� Operations are divided between intermediate and terminal

� Lazy in nature: only terminal operations actually trigger a computation

� Also available a parallel stream (using the Fork/Join framework)

employees.stream()

.filter(e -> e.getIncome() > 50000)

.map(e -> e.getName())

.forEach(System.out::println);

parallel()

Page 15: FP in Java - Project Lambda and beyond

So we have lambdas in Java …

… now what?

Page 16: FP in Java - Project Lambda and beyond

The OOP/FP dualism - OOPpublic class Bird { }

public class Cat {

private Bird catch;

private boolean full;

public void capture(Bird bird) {

catch = bird;

}

public void eat() {

full = true;

catch = null;

}

}

Cat cat = new Cat();

Bird bird = new Bird();

cat.capture(bird);

cat.eat();

The story

Page 17: FP in Java - Project Lambda and beyond

The OOP/FP dualism - FPpublic class Bird { }

public class Cat {

public CatWithCatch capture(Bird bird) { return new CatWithCatch(bird); }

}

public class CatWithCatch {

private final Bird catch;

public CatWithCatch(Bird bird) { catch = bird; }

public FullCat eat() { return new FullCat(); }

}

public class FullCat { }

BiFunction<Cat, Bird, FullCat> story =

((BiFunction<Cat, Bird, CatWithCatch>)Cat::capture)

.compose(CatWithCatch::eat);

FullCat fullCat = story.apply( new Cat(), new Bird() );

Immutability

Emphasis on verbs

instead of names

No need to test internal state: correctness enforced by the compiler

Page 18: FP in Java - Project Lambda and beyond

Better Logging with Lambdas

if (log.isDebugEnabled()) {

log.debug("The answer is " + answer);

}

Can we delay the String creation and execute it only when

strictly necessary without (explicitly) using an if?

log.debug(()-> "The answer is " + answer);

public void debug(Callable<String> lambda) {

if (isDebugEnabled()) {

debug(lambda.call());

}

}

Invokes answer.toString() and does the Strings

concatenation even when not necessary

Page 19: FP in Java - Project Lambda and beyond

Side-effect isolation � Reusabilityclass Player {

String name;

int score;

}

public void declareWinner(Player p) {

System.out.println(p.name + " wins!");

}

public void winner(Player p1, Player p2) {

if (p1.score > p2.score) declareWinner(p1)

else declareWinner(p2);

}

Page 20: FP in Java - Project Lambda and beyond

Side-effect isolation � Reusabilityclass Player {

String name;

int score;

}

public void declareWinner(Player p) {

System.out.println(p.name + " wins!");

}

public void winner(Player p1, Player p2) {

if (p1.score > p2.score) declareWinner(p1)

else declareWinner(p2);

}

public Player maxScore(Player p1, Player p2) {

return p1.score > p2.score ? p1 : p2;

}

public void winner(Player p1, Player p2) {

declareWinner(maxScore(p1, p2));

}

Separate

computational logic

from side effects

Page 21: FP in Java - Project Lambda and beyond

Side-effect isolation � Reusabilityclass Player {

String name;

int score;

}

public void declareWinner(Player p) {

System.out.println(p.name + " wins!");

}

public void winner(Player p1, Player p2) {

if (p1.score > p2.score) declareWinner(p1)

else declareWinner(p2);

}

public Player maxScore(Player p1, Player p2) {

return p1.score > p2.score ? p1 : p2;

}

public void winner(Player p1, Player p2) {

declareWinner(maxScore(p1, p2));

}

Separate

computational logic

from side effects

declareWinner(players.stream().reduce(this::maxScore).get())

reuse maxScore as a BinaryOperator

to compute the winner among a list of players

Page 22: FP in Java - Project Lambda and beyond

Using Streamspublic boolean isPrimeImperative(int number) {

if (number < 2) {

return false;

}

for (int i = 2; i < (int) Math.sqrt(number) + 1; i++) {

if ( number % i == 0 ) {

return false;

}

}

return true;

}

public boolean isPrimeFunctional(int number) {

return number > 1 &&

Streams.intRange(2, (int) Math.sqrt(number) + 1)

.noneMatch(divisor -> number % divisor == 0);

}

Page 23: FP in Java - Project Lambda and beyond

Working with infinite Streams

Stream<Integer> integers = Streams.iterate(1, i -> i + 1);

int[] result = integers.map(i -> i ^ 2).limit(25).toArray();

take 25 (map (^2) [1..])

(take 25 (squares-of (integers)))

Stream.from(1).map(_ ^ 2).take(25).toArray

Page 24: FP in Java - Project Lambda and beyond

Null references? No, Thanks

� Errors source � NPE is by far the most common exception in Java

� Bloatware source � Worsen readability by making necessary to fill our

code with null checks

� Meaningless � Don't have any semantic meaning and in particular are the

wrong way to model the absence of a value in a statically typed language

� Breaks Java philosophy � Java always hides pointers to developers, except

in one case: the null pointer

� A hole in the type system � Null has the bottom type, meaning that it can

be assigned to any reference type: this is a problem because, when

propagated to another part of the system, you have no idea what that null

was initially supposed to be

Tony Hoare, who invented the null reference in 1965 while working on

an object oriented language called ALGOL W, called its invention his

“billion dollar mistake”

Page 25: FP in Java - Project Lambda and beyond

Options: the functional alternativepublic abstract class Option<A> implements Iterable<A> {

private Option() { }

public abstract <B> Option<B> map(Function<A, B> mapper);

public abstract <B> Option<B> flatMap(Function<A, Option<B>> mapper);

public abstract Option<A> filter(Predicate<A> predicate);

public abstract A getOrElse(A def);

public abstract boolean isDefined();

public static <A> Some<A> some(A value) {

if (value == null) throw new NullPointerException();

return new Some<A>(value);

}

public static <A> None<A> none() { return None.NONE; }

public static <A> Option<A> option(A value) {

return value == null ? none() : some(value);

}

public static final class None<A> extends Option<A> { ... }

public static final class Some<A> extends Option<A> { ... }

}

Page 26: FP in Java - Project Lambda and beyond

Somepublic static final class Some<A> extends Option<A> {

private final A value;

private Some(A value) { this.value = value; }

public <B> Option<B> map(Function<A, B> mapper) {

return some( mapper.apply(value) );

}

public <B> Option<B> flatMap(Function<A, Option<B>> mapper) {

return (Option<B>) mapper.apply(value);

}

public Option<A> filter(Predicate<? super A> predicate) {

return predicate.test(value)) ? this : None.NONE;

}

public A getOrElse(A def) { return value; }

public boolean isDefined() { return false; }

}

Page 27: FP in Java - Project Lambda and beyond

Nonepublic static final class None<A> extends Option<A> {

public static final None NONE = new None();

private None() { }

public <B> Option<B> map(Function<A, B> mapper) { return NONE; }

public <B> Option<B> flatMap(Function<A, Option<B>> mapper) {

return NONE;

}

public Option<A> filter(Predicate<A> predicate) { return NONE; }

public A getOrElse(A def) { return def; }

public boolean isDefined() { return false; }

}

Page 28: FP in Java - Project Lambda and beyond

Example: if the value associated with a given key

is a String representing a positive integer returns

that integer, but returns zero in all other case@Test

public void testReturnPositiveIntegersOrZero() {

Map<String, String> param = new HashMap<String, String>();

param.put("a", "5");

param.put("b", "true");

param.put("c", "-3");

// the value of the key "a" is a String representing a

// positive int so return it

assertEquals(5, readPositiveIntParam(param, "a"));

// returns zero since the value of the key "b" is not an int

assertEquals(0, readPositiveIntParam(param, "b"));

// returns zero since the value of the key "c" is a negative int

assertEquals(0, readPositiveIntParam(param, "c"));

// returns zero since there is no key "d" in the map

assertEquals(0, readPositiveIntParam(param, "d"));

}

Page 29: FP in Java - Project Lambda and beyond

Null vs. Optionint readPositiveIntParam(Map<String, String> params, String name) {

String value = params.get(name);

if (value == null) return 0;

int i = 0;

try {

i = Integer.parseInt(value);

} catch (NumberFormatException e) { }

return i < 0 ? 0 : i;

}

int readPositiveIntParam(Map<String, String> params, String name) {

return asOption(params.get(name))

.flatMap(s -> {

try { return some(Integer.parseInt(s)); }

catch (NumberFormatException e) { return none(); }

})

.filter(i -> i > 0)

.getOrElse(0);

}

Page 30: FP in Java - Project Lambda and beyond

Exceptions? Yes, but …� Often abused, especially for flow control

� Checked Exceptions harm API extensibility/modificability

� Not composable: in presence of multiple errors only the first one is

reported

� In the end just a GLORIFIED MULTILEVEL GOTO

Page 31: FP in Java - Project Lambda and beyond

Exceptions? Yes, but …� Often abused, especially for flow control

� Checked Exceptions harm API extensibility/modificability

� Not composable: in presence of multiple errors only the first one is

reported

� In the end just a GLORIFIED MULTILEVEL GOTO

Either/Validation: the functional alternative

� The functional way of returning a value which can actually be one of two

values: the error/exception (Left) or the correct value (Right)

Validation<Exception, Value>

Page 32: FP in Java - Project Lambda and beyond

Exceptions? Yes, but …� Often abused, especially for flow control

� Checked Exceptions harm API extensibility/modificability

� Not composable: in presence of multiple errors only the first one is

reported

� In the end just a GLORIFIED MULTILEVEL GOTO

Either/Validation: the functional alternative

� The functional way of returning a value which can actually be one of two

values: the error/exception (Left) or the correct value (Right)

� Composable: can accumulate multiple errors

Validation<Exception, Value>Validation<List<Exception>, Value>

Page 33: FP in Java - Project Lambda and beyond

SalaryCalculatorpublic class SalaryCalculator {

// B = basic + 20%

public double plusAllowance(double d) { return d * 1.2; }

// C = B + 10%

public double plusBonus(double d) { return d * 1.1; }

// D = C - 30%

public double plusTax(double d) { return d * 0.7; }

// E = D - 10%

public double plusSurcharge(double d) { return d * 0.9; }

public double calculate(double basic, boolean... bs) {

double salary = basic;

if (bs[0]) salary = plusAllowance(salary);

if (bs[1]) salary = plusBonus(salary);

if (bs[2]) salary = plusTax(salary);

if (bs[3]) salary = plusSurcharge(salary);

return salary;

}

}

Page 34: FP in Java - Project Lambda and beyond

Endomorphisms & Monoids

interface Endomorphism<A> extends Function<A, A> { }

interface Monoid<A> {

A append(A a1, A a2);

A zero();

}

interface EndoMonoid<A> extends Monoid<Endomorphism<A>> {

@Override

default Endomorphism<A> append(Endomorphism<A> a1, Endomorphism<A> a2) {

return (A a) -> a2.apply(a1.apply(a));

}

@Override

default Endomorphism<A> zero() {

return a -> a;

}

}

Page 35: FP in Java - Project Lambda and beyond

FluentEndoMonoidpublic class FluentEndoMonoid<A> implements EndoMonoid<A> {

private final Endomorphism<A> endo;

public FluentEndoMonoid(Endomorphism<A> endo) { this.endo = endo; }

public FluentEndoMonoid(Endomorphism<A> endo, boolean b) {

this.endo = b ? endo : zero();

}

public FluentEndoMonoid<A> add(Endomorphism<A> other) {

return new FluentEndoMonoid<A>(append(endo, other));

}

public FluentEndoMonoid<A> add(Endomorphism<A> other, boolean b) {

return add(b ? other : zero());

}

public Endomorphism<A> get() { return endo; }

public static <A> FluentEndoMonoid<A> endo(Endomorphism<A> f, boolean b) {

return new FluentEndoMonoid<A>(f, b);

}

}

Page 36: FP in Java - Project Lambda and beyond

Functional SalaryCalculator

public class SalaryCalculator {

public double calculate(double basic, boolean... bs) {

return endo((Endomorphism<Double>) this::plusAllowance, bs[0])

.add(this::plusBonus, bs[1])

.add(this::plusTax, bs[2])

.add(this::plusSurcharge, bs[3])

.get()

.apply(basic);

}

}

Page 37: FP in Java - Project Lambda and beyond

The bottom line

Java is getting functional

EMBRACE IT!

Page 38: FP in Java - Project Lambda and beyond

References

Page 39: FP in Java - Project Lambda and beyond

Mario Fusco

Red Hat – Senior Software Engineer

[email protected]

twitter: @mariofusco

Q A

Thanks … Questions?