6. Amdahl's
law
The speedup of a program using multiple processors
in parallel computing is limited by the time needed
for the sequential fraction of the program
7. Concurrency & Parallelism
Parallel programming
Running multiple tasks at
the same time
Concurrent programming
Managing concurrent requests
Both are hard!
8. The cause of the problem …
Mutable state +
Parallel processing =
Non-determinism
9. The cause of the problem …
Mutable state +
Parallel processing =
Non-determinism
Functional
Programming
11. Too hard to think about them!
Race conditions
Deadlocks
Starvation
Livelocks
… and its effects
12. The native Java concurrency model
Based on:
They are sometimes plain evil …
… and sometimes a necessary pain …
… but always the wrong default
Threads
Semaphores
SynchronizationLocks
14. Summing attendants ages (Threads)
class Blackboard {
int sum = 0;
int read() { return sum; }
void write(int value) { sum = value; }
}
class Attendant implements Runnable {
int age;
Blackboard blackboard;
public void run() {
synchronized(blackboard) {
int oldSum = blackboard.read();
int newSum = oldSum + age;
blackboard.write(newSum);
}
}
}
15. Summing attendants ages (Actors)
class Blackboard extends UntypedActors {
int sum = 0;
public void onReceive(Object message) {
if (message instanceof Integer) {
sum += (Integer)message;
}
}
}
class Attendant {
int age;
Blackboard blackboard;
public void sendAge() {
blackboard.sendOneWay(age);
}
}
16. Summing attendants ages (Functional)
class Blackboard {
final int sum;
Blackboard(int sum) { this.sum = sum; }
}
class Attendant {
int age;
Attendant next;
public Blackboard addMyAge(Blackboard blackboard) {
final Blackboard b = new Blackboard(blackboard.sum + age);
return next == null ? b : next.myAge(b);
}
}
18. OOP makes code understandable
by encapsulating moving parts
FP makes code understandable
by minimizing moving parts
- Michael Feathers
OOP vs FP
19. The OOP/FP dualism - OOP
public 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
20. The OOP/FP dualism - FP
public 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() );
21. The OOP/FP dualism - FP
public 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
22. 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!
Internal vs External Iteration
23. 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);
Streams - Efficiency with laziness
24. 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);
parallelStream()
… and parallelism for free
Streams - Efficiency with laziness
26. Probably yes …
… but we need functional
forks and knives to eat it
27. Reassigning a variable
Modifying a data structure in place
Setting a field on an object
Throwing an exception or halting with an error
Printing to the console
Reading user input
Reading from or writing to a file
Drawing on the screen
A program created using only pure functions
What is a functional program?
No side effects allowed like:
Functional programming is a restriction on how we
write programs, but not on what they can do
}
}
28. What is a (pure) function?
A function with input type A and output type B
is a computation which relates every value a of
type A to exactly one value b of type B such
that b is determined solely by the value of a
But, if it really is a
function, it will do
nothing else
29. Referential transparency
An expression e is referentially transparent if for all programs p,
all occurrences of e in p can be replaced by the result of
evaluating e, without affecting the observable behavior of p
A function f is pure if the expression f(x) is referentially
transparent for all referentially transparent x
31. RT wins
Under a developer point of view:
Easier to reason about since effects of evaluation are
purely local
Use of the substitution model: it's possible to replace a
term with an equivalent one
Under a performance point of view:
The JVM is free to optimize the code by safely reordering
the instructions
No need to synchronize access to shared data
Possible to cache the result of time consuming functions
(memoization), e.g. with
Map.computeIfAbsent(K key,
Function<? super K,? extends V> mappingFunction)
32. Mutability
Parameter binding is about assigning names to things
Mutating variables is about assigning things to names
Does that second
one sound weird?
… well it's because
it IS weird
33. Immutability
Immutable objects can be shared among
many threads exactly because none of
them can modify it
In the same way immutable (persistent)
data structures can be shared without any
need to synchronize the different threads
accessing them
38. Modularity
class 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);
}
39. Modularity
class 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
40. Modularity
class 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.reduce(maxScore).get())
reuse maxScore to compute the winner among a list of players
41. A pure functional core
functional
core
Any function with side-effects can be split into a pure function at the
core and a pair of functions with side-effect. This transformation can
be repeated to push side-effects to the outer layers of the program.
42. Key Takeaways
Strive for immutability
Confine side-effects
Avoid blocking code
Cede control with higher-order functions
Leverage referential transparency
Use FP to design more composable and reusable API
… but there are no dogmas
Be pragmatic and use the right tool for the job at hand
Poly-paradigm programming is more powerful
and effective than polyglot programming
43.
44. Mario Fusco
Red Hat – Senior Software Engineer
mario.fusco@gmail.com
twitter: @mariofusco
Q A
Thanks … Questions?