FP in Java
Project Lambda and beyond
by Mario Fusco
mario.fusco@gmail.com
twitter: @mariofusco
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;
}
From Single Method Interfaces …
public interface Comparator<T> { Functional
int compare(T o1, T o2);
} Interface
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
… To Lambda Expressions
Collections.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));
Lambda expression are always converted to
instance of a functional interface
Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);
Compiler figures out the types
No need of changing the JVM to create a new
type for lambda expressions
Anatomy of a lambda expression
A lambda expression is like a method: it provides a list of formal parameters and a body
The formal parameters of
a lambda expression may
have either inferred or
s -> s.length()
declared types
(int x, int y) -> x + y
() -> 42 Return is implicit and
can be omitted
(x, y, z) -> {
if (x) {
return y; A lambda body is either a
} else { single expression or a block
return z;
}
}
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
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
An Example: Sorting
Comparator<Person> byAge = new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.getAge() – p2.getAge();
Functional interface
}
};
Lambda expression
Collections.sort(people, byAge);
Comparator<Person> byAge = (p1, p2) -> p1.getAge() – p2.getAge();
Collections.sort(people, (p1, p2) -> p1.getAge() – p2.getAge());
Can We Do Better?
Comparator<Person> byAge = Comparators.comparing(p -> p.getAge());
Comparator<Person> byAge = Comparators.comparing(Person::getAge);
Method reference
Readability
Collections.sort(people, comparing(Person::getAge));
Reusability
Collections.sort(people, comparing(Person::getAge).reverse());
Composability
Collections.sort(people, comparing(Person::getAge)
.compose(comparing(Person::getName)));
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
Extension methods
interface Iterator<E> {
boolean hasNext();
E next();
default void remove(); { throw new UnsupportedOperationException(); }
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
Internal VS External iteration
for (Employee e : employees) {
e.setSalary(e.getSalary() * 1.03);
}
̶ Inherently serial
̶ Client has to manage iteration
̶ Nested loops are poorly readable
employees.forEach(e -> e.setSalary(e.getSalary() * 1.03));
Not only a syntactic change!
+ 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
Streams - Efficiency with laziness
employees.stream()
.filter(e -> e.getIncome() > 50000)
.map(e -> e.getName())
.forEach(System.out::println);
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
Streams - Efficiency with laziness
parallel()
employees.stream()
.filter(e -> e.getIncome() > 50000)
.map(e -> e.getName())
.forEach(System.out::println);
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)
The OOP/FP dualism - OOP
public class Bird { }
public class Cat {
private Bird catch;
private boolean full;
public void capture(Bird bird) {
catch = bird;
The story
}
public void eat() {
full = true;
catch = null;
}
}
Cat cat = new Cat();
Bird bird = new Bird();
cat.capture(bird);
cat.eat();
The OOP/FP dualism - FP
public class Bird { }
public class Cat {
public CatWithCatch capture(Bird bird) { return new CatWithCatch(bird); }
}
public class CatWithCatch { Immutability
private final Bird catch;
public CatWithCatch(Bird bird) { catch = bird; }
public FullCat eat() { return new FullCat(); } Emphasis on verbs
}
instead of names
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() );
No need to test internal state: correctness enforced by the compiler
Better Logging with Lambdas
if (log.isDebugEnabled()) {
log.debug("The answer is " + answer);
}
Invokes answer.toString() and does the Strings
concatenation even when not necessary
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());
}
}
Side-effect isolation Reusability
class Player {
String name; public void declareWinner(Player p) {
int score; System.out.println(p.name + " wins!");
} }
public void winner(Player p1, Player p2) {
if (p1.score > p2.score) declareWinner(p1)
else declareWinner(p2);
}
Side-effect isolation Reusability
class Player {
String name; public void declareWinner(Player p) {
int score; System.out.println(p.name + " wins!");
} }
public void winner(Player p1, Player p2) {
if (p1.score > p2.score) declareWinner(p1)
else declareWinner(p2);
}
Separate
computational logic
public Player maxScore(Player p1, Player p2) { from side effects
return p1.score > p2.score ? p1 : p2;
}
public void winner(Player p1, Player p2) {
declareWinner(maxScore(p1, p2));
}
Side-effect isolation Reusability
class Player {
String name; public void declareWinner(Player p) {
int score; System.out.println(p.name + " wins!");
} }
public void winner(Player p1, Player p2) {
if (p1.score > p2.score) declareWinner(p1)
else declareWinner(p2);
}
Separate
computational logic
public Player maxScore(Player p1, Player p2) { from side effects
return p1.score > p2.score ? p1 : p2;
}
public void winner(Player p1, Player p2) {
declareWinner(maxScore(p1, p2));
}
declareWinner(players.stream().reduce(this::maxScore).get())
reuse maxScore as a BinaryOperator
to compute the winner among a list of players
Using Streams
public 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);
}
Working with infinite Streams
take 25 (map (^2) [1..])
(take 25 (squares-of (integers)))
Stream.from(1).map(_ ^ 2).take(25).toArray
Stream<Integer> integers = Streams.iterate(1, i -> i + 1);
int[] result = integers.map(i -> i ^ 2).limit(25).toArray();
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”
Options: the functional alternative
public 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> { ... }
}
Some
public 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; }
}
None
public 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; }
}
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"));
}
Null vs. Option
int 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);
}
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
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>
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<List<Exception>, Value>
Validation<Exception, Value>
SalaryCalculator
public 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;
}
}
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;
}
}
FluentEndoMonoid
public 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);
}
}
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);
}
}