2. A Pure Function
Uses nothing other than i/p parameters
(and its definition) to produce o/p -
Deterministic in nature.
Neither modifies input arguments nor
reads/modifies external state - No
side-effects.
Call is substitutable by its body. To
understand the code, you don’t have to
look elsewhere.
class Calculator {
public Integer add(final Integer x, final Integer y) {
return x + y;
}
}
3. class Calculator {
private int memory = 0;
public Calculator(final int memory) {
this.memory = memory;
}
public Integer add(final int x, final int y) {
return x + y;
}
public Integer memoryPlus(final int n) {
memory = add(memory, n);
return memory;
}
}
Side-effecting Function
Modifies or interacts with things
outside of its scope, and may also
return a value.
Outside of
its scope
4. Side-effecting Function
Changes something somewhere at either
class or module or global or at world
level.
Performing side-effects like reading or
writing to socket/file/db etc…
Throwing an exception and using it to alter
the control flow or alter the program state.
This makes it difficult to reason about
the program.
Can produce different o/p for same i/p.
5. A Black-Hole like
Function
Always consumes, never returns anything
back.
It affects the world by generating a
side-effect, example - the setters.
class Calculator {
private Integer memory;
public void setMemory(final Integer value) {
memory = value;
}
}
6. A Mother-like Function
Gives unconditionally without asking
for anything.
Example - the getters.
class Calculator {
private Integer memory;
public Integer recallMemory() {
return memory;
}
}
9. class Calculator {
private Integer memory;
public Calculator(final Integer memory) {
this.memory = memory;
}
public Integer add(final Integer x, final Integer y) {
return x + y;
}
public Integer memoryPlus(final Integer n) {
memory = add(memory, n);
return memory;
}
}
Understanding
Referential
Transparency
10. c.add(2, 3); // 5
c.add(2, 4); // 6
c.add(2, 3); // 5
c.add(2, 4); // 6
Referential
Transparency
Calculator c = new Calculator();
Referentially Opaque memoryPlus :
1.Cannot replace it with resulting value.
2.Returns different results as time
progresses, as behaviour depends on history.
c.memoryPlus(2); // 2
c.memoryPlus(3); // 5
c.memoryPlus(2); // 7
c.memoryPlus(3); // 10
Time Time
Referentially Transparent add :
1.Substitute any expression with its
resulting value.
2. Returns same results all the time, as
behaviour does not depend on history.
11. How can we make
memoryPlus
Referential
Transparent?
12. Ref. Transparent
memoryPlus
class Calculator {
private final Integer memory;
public Calculator(final Integer memory) {
this.memory = memory;
}
public Integer add { … }
public Calculator memoryPlus(final Integer n) {
return new Calculator(add(memory, n));
}
}
Make memory
Immutable
Return new instance from
operation.
13. Reflections
Referential Transparency is about replacing
any expression (or function) with its
resulting value.
Referentially transparent functions are
context-free. In our example, the context
is time.
Use in different contexts.
Neither alters the meaning of the context.
Nor their behaviour.
14. Reflections
To be referentially transparent,
function will require to work with
immutable data.
To be referentially transparent, a
function will require to be pure.
15. Why use Immutability
and Pure Functions?
Immutablity.
Promotes caching of objects - Flyweights.
Enables concurrent operations.
Pure Functions.
Promote Memoization - caching results of
expensive computations.
16. Order of program evaluation can be
changed by compiler to take advantage
of multiple cores.
It becomes hard to debug functions with
side-effects as program behaviour
depends on history.
So, Immutability and Pure functions
together make it easier to reason about
program.
18. Chai Chat: OO & FP
We encapsulate data because we think
it will protect us from inadvertent
changes and build trust.
19. Chai Chat: OO & FP
We encapsulate data because we think
it will protect us from inadvertent
changes and build trust.
The data itself is immutable. As data
cannot change, trust is inherent.
f
20. Chai Chat: OO & FP
We encapsulate data because we think
it will protect us from inadvertent
changes and build trust.
The data itself is immutable. As data
cannot change, trust is inherent.
f
Data (structure) is hidden and the
client is not coupled to it.
21. Chai Chat: OO & FP
We encapsulate data because we think
it will protect us from inadvertent
changes and build trust.
The data itself is immutable. As data
cannot change, trust is inherent.
f
Data (structure) is hidden and the
client is not coupled to it.
If its immutable, why bother
encapsulating?
f
23. Knowing the innards of an object,
causes coupling to parts, which comes
in the way of refactoring as
requirements change.
Chai Chat: OO & FP
24. Knowing the innards of an object,
causes coupling to parts, which comes
in the way of refactoring as
requirements change.
Hmm… however an in-place update in OO
thru’ methods stores the latest value.
f
Chai Chat: OO & FP
25. Knowing the innards of an object,
causes coupling to parts, which comes
in the way of refactoring as
requirements change.
Hmm… however an in-place update in OO
thru’ methods stores the latest value.
f
This mutation to an OO object makes
it hard to reason about its past and
therefore its current state. It is easy
to miss the point that in OO, state and
time are conflated.
f
Chai Chat: OO & FP
27. Functions that operate on immutable
data are then pure functions. For any
transformation they produce new data,
leaving the old unmodified.
f
Chai Chat: OO & FP
28. Functions that operate on immutable
data are then pure functions. For any
transformation they produce new data,
leaving the old unmodified.
f
Time is never conflated with state,
because (immutable) data is a snapshot
at a point in time of a particular
state.
f
Chai Chat: OO & FP
29. Hmmm…One can work to make an object
immutable though!
Functions that operate on immutable
data are then pure functions. For any
transformation they produce new data,
leaving the old unmodified.
f
Time is never conflated with state,
because (immutable) data is a snapshot
at a point in time of a particular
state.
f
Chai Chat: OO & FP
30. Encapsulation Vs Open Immutable Data
(structures).
Immutability Vs State-Time Conflation.
Don’t we value both?
Immutability
Encapsulation
Reflections
31. Good Deeds Happen Anonymously
An anonymous function - Lambda
Hard to name things
public Integer add(final Integer x, final Integer y) {
return x + y;
}
public Integer add(final Integer x, final Integer y) {
return x + y;
}
(final Integer x, final Integer y) -> { x + y; }
(x, y) -> x + y;
Drop all inessentials
what remains
are essentials,
parameters and
body
32. new Button().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.print("essence");
}
});
new Button().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("essence");
}
});
new Button().addActionListener(e -> System.out.print("essence"));
Drop all inessentials
what remains
are essentials,
parameters and
body
SAMs become…
new Button().addActionListener(System.out::print);
OR
33. Lambda
Compiled as interface implementation with
synthesised method having signature of the
abstract method in that interface.
An interface with single abstract method
(SAM).
@FunctionalInterface - Though optional
annotation, its better to have it so that it
ensures that it stays as a lambda and not
become something more.
Function<Integer, Integer> twice = x -> 2 * x;
twice.apply(3); // 6 It would be have been nice if
some more syntactic sugar
was added by Java8 to make
this happentwice(3);Instead of
34. Functional Interfaces
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
…
}
Function<Float, Float> twice = x -> 2 * x;
twice.apply(3); // 6
// A function returns its argument unchanged
Function<Float, Float> identity = x -> x
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
…
}
BiFunction<Float, Float, Float> add = (x, y) -> x + y;
add.apply(2, 3); // 5
35. Existing
Functional Interfaces
public interface Comparator<T> {
public int compare(T o1, T o2);
}
// can be written as
(a, b) -> (a < b) ? -1 : (a == b) ? 0 : 1;
// or simply
(a, b) -> a - b;
36. A Black-Hole like
Functional Interface
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
…
}
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
…
}
Things entering
the black hole,
never return back!
Consumer<Float> corrupt = bribe -> { }
BiConsumer<Celestial, Celestial> blackHole =
(planet, asteroid) -> { }
corrupt.accept(100.34);
blackHole.accept(planet, asteriod);
37. Existing Consumers
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent e);
}
// Can be written as
event -> System.out.println(event);
42. Default & Static Methods
Default Methods
Helps in Evolving Interfaces.
However, default methods are for default
behaviours, and not for shoving in duplicate
behaviour.
In other words, don’t abuse it for
implementation inheritance and turn it in an
implementation class.
Static Methods
Use them as creation methods to create a
concrete object of that type.
43. static Boolean is(final Integer n, String op) {
Predicate<Integer> isEven = x -> x % 2 == 0;
Map<String, Predicate<Integer>> preds = new HashMap<>();
preds.put("even", isEven);
preds.put("odd", isEven.negate());
Predicate<Integer> falsify = x -> false;
return preds.getOrDefault(op, falsify).test(n);
}
is(5, "even");
is(5, "odd");
Defining Function within a
function - Encapsulate fns
Java does not allow you to define a
function within a function, but its ok
defining lambda.
45. Encapsulate Self-Ref
Anonymous Functions
class Reducer {
private static
BiFunction<Integer, List<Integer>, Integer> sum0 = (acc, xs) -> {
if (xs.isEmpty())
return acc;
else
return Reducer.sum0.apply(acc + xs.get(0), xs.subList(1,
xs.size()));
};
Integer sum(List<Integer> ns) {
return sum0.apply(0, ns);
}
}
To self-reference, you will need to…
46. Every-“Thing” is a
Lambda
Function as a Type.
Do we need booleans?
Basic Boolean operations (and, or, not)
https://github.com/CodeJugalbandi/FunctionalProgramming/tree/master/melodies/
functionsAreEntities
https://github.com/CodeJugalbandi/FunctionalProgramming/tree/master/melodies/
functionsAreTypes
Function as a Data Structure.
Do we need lists?
Do we need integers?
47. So, like Object…
A Function is also a thing, so
Pass a function to a function
Return a function from within a function
A Function that produces or consumes a
function is called as Higher Order
Function - HOF
Either pass existing method reference
(static or instance) or write an in-
place lambda where a method expects a
function parameter.
48. void iterate(int times, Runnable body) {
if (times <= 0) {
return;
}
body.run();
iterate(times - 1, body);
}
iterate(2, () -> System.out.println("Hello"));
// Hello
// Hello
Pass function to
a function
Subsumed ‘for’ loop.
Repetitive behaviour using Recursion.
Simplified iteration
without a
predicate, but you
get the idea.
No need for
explicit looping
constructs!
Just a function!
49. Function<Double, Double> power(double raiseTo) {
return x -> Math.pow(x, raiseTo);
}
Function<Double, Double> square = power(2.0);
square.apply(2.0); // 4.0
Function<Double, Double> cube = power(3.0);
cube.apply(2.0); // 8.0
Return function
from a function
Subsumed Factory Method.
50. Another Example
class Maker { }
class Checker { }
public interface Transaction {
public boolean approve();
public boolean reject(String reason);
}
public interface ApprovalStrategy {
boolean approve(Transactions transactions);
public static ApprovalStrategy valueOf(Transactions transactions) {
if (transactions.value() < 100000)
return new SingleMakerChecker();
else
return new DoubleMakerChecker();
}
}
51. Another Example
class Maker { }
class Checker { }
public interface Transaction {
public boolean approve();
public boolean reject(String reason);
}
public interface ApprovalStrategy {
boolean approve(Transactions transactions);
public static ApprovalStrategy valueOf(Transactions transactions) {
if (transactions.value() < 100000)
return new SingleMakerChecker();
else
return new DoubleMakerChecker();
}
}
Download the Gist Bank Maker-Checker Refactoring
52. Another Example
class DoubleMakerChecker implements ApprovalStrategy {
public DoubleMakerChecker(Maker m, Checker c1, Checker c2) { }
public boolean approve(Transactions ts) {
return true;
}
}
class SingleMakerChecker implements ApprovalStrategy {
public SingleMakerChecker(Maker m, Checker c) { }
public boolean approve(Transactions ts) {
return true;
}
}
ApprovalStrategy
SingleMakerChecker DoubleMakerChecker
53. class Transactions {
private final List<Transaction> transactions;
private Transactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public boolean approve(ApprovalStrategy aps) {
return aps.approve(ts);
}
public Double totalValue() {
// This is hard-coded for purpose of this example.
return 1000000d;
}
}
//main
Transactions transactions = new Transactions(Arrays.asList(…));
ApprovalStrategy approvalStrategy = ApprovalStrategy.valueOf(transactions);
transactions.approve(approvalStrategy);
54. class Transactions {
private final List<Transaction> transactions;
private Transactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public boolean approve(ApprovalStrategy aps) {
return aps.approve(ts);
}
public Double totalValue() {
// This is hard-coded for purpose of this example.
return 1000000d;
}
}
//main
Transactions transactions = new Transactions(Arrays.asList(…));
ApprovalStrategy approvalStrategy = ApprovalStrategy.valueOf(transactions);
transactions.approve(approvalStrategy);
Is there any scope to refactor this
code to a better one?
55. Subsumed Strategy
class Maker { }
class Checker { }
interface Transaction {
public boolean approve();
public boolean reject(String reason);
}
public class ApprovalStrategy {
static Predicate<Transactions, Boolean> valueOf(Transactions transactions) {
if (transactions.value() < 100000) {
SingleMakerChecker smc = new SingleMakerChecker(…);
return smc::approve;
} else {
DoubleMakerChecker dmc = new DoubleMakerChecker(…);
return dmc::approve;
}
}
class SingleMakerChecker {
public SingleMakerChecker(Maker m, Checker c) { }
public boolean approve(Transactions ts) {
return true;
}
}
56. class Transactions {
private final List<Transaction> transactions;
private Transactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public boolean approve(Predicate<Transactions> aps) {
return aps.test(ts);
}
public Double totalValue() {
// This is hard-coded for purpose of this example.
return 1000000;
}
}
//main
Transactions transactions = new Transactions(Arrays.asList(…));
transactions.approve(ApprovalStrategy.valueOf(transactions));
class DoubleMakerChecker {
public DoubleMakerChecker(Maker m, Checker c1, Checker c2) { }
public boolean approve(Transactions ts) {
return true;
}
}
Subsumed Strategy
65. Functions as a part
of data structures
List<BiFunction<Integer, Integer, Integer>> operations =
new ArrayList<BiFunction<Integer, Integer, Integer>>() {{
add((x, y) -> x + y);
add((x, y) -> x * y);
add((x, y) -> x - y);
}};
int x = 2, y = 3;
for (BiFunction<Integer, Integer, Integer> op : operations) {
System.out.println(op.apply(x, y));
}
66. Imperative Collecting
and Filtering
String sentence = "all mimsy were the borogoves and the momeraths";
String [] words = sentence.split(" ");
StringBuilder caps = new StringBuilder();
for (word : words) {
if (word.length() < 4) {
caps.append(word.toUpperCase());
caps.append(" ");
}
}
String capitalized = caps.toString().trim();
System.out.println(capitalized); // ALL THE AND THE
Enumeration
and
Filtering
interleaved
Collector
67. In Java8…
In-place mutation is a standing
invitation
Its hard to avoid falling into that trap.
One has to work hard to bring immutability.
Use ‘final’ wherever possible.
Use Expressions wherever possible
Statements effect change by mutation and
thus encourage mutability
Expressions evaluate to return values and
thus encourage immutability
69. Refactored Code
String join(final List<String> words) {
StringBuilder joined = new StringBuilder();
for (word : words) {
joined.append(" ");
}
return joined.toString().trim();
}
String sentence = "all mimsy were the borogoves and the mome raths";
String capitalized = join(capitalize(lessThan4(split(sentence))));
System.out.println(capitalized); // ALL THE AND THE
70. Additional Scenarios to consider
What about large data-set (memory concern)?
If I need only first few elements, then why do
I need to go through every element? Can I not
short-circuit the processing?
Pros Cons
No mutation and SRPier
functions
End-up creating many
intermediate lists in the
call chain.
Improved Readability
What about memory
utilisation and
performance?
71. Enter Streams
Collection where you access elements one
by one - amortised list.
Generating Stream that is backed by
finite elements - of
Stream<Integer> primes = Stream.of(2, 3, 5, 7, 9);
Generating a Stream backed by list.
Stream<Integer> primes = Arrays.asList(2, 3, 5, 7, 9)
.stream();
73. Stream Operations
Iterate each element (Terminal).
forEach
Transform each element (Non-Terminal).
map, flatMap
Retrieve Elements that satisfy certain
criterion
findFirst, filter, allMatch, anyMatch,
noneMatch
Debug each element.
peek
74. Stream Ops
Combine adjacent elements
reduce, min, max, count, sum
Sort and Unique
sorted and distinct
take and drop
limit, skip
76. Parallel Stream and
Side-Effects
String sentence = "all mimsy were the borogoves and the mome raths";
// Prints in the order the stream encountered
sentence.chars()
.forEach(ch -> System.out.println((char) ch));
// Does not print in the order the stream encountered
// If it prints in order you are just “lucky”! Try running again…
sentence.chars()
.parallel()
.forEach(ch -> System.out.println((char) ch));
// Prints in the order the stream encountered
sentence.chars()
.parallel()
.forEachOrdered(ch -> System.out.println((char) ch));
77. Compilation Boom!!
“local variables referenced
from a lambda expression
must be final or effectively
final”
.forEach(word -> capitalized += word);
Stateful
Effectively final or final
String sentence = "all mimsy were the borogoves and the mome raths";
String capitalized = "";
Stream.of(sentence.split(" "))
.filter(w -> w.length() < 4)
.map(String::toUpperCase)
.forEach(word -> capitalized += word);
78. You get clever!
String sentence = "all mimsy were the borogoves and the mome raths";
StringBuilder capitalized = new StringBuilder();
Stream.of(sentence.split(" "))
.filter(w -> w.length() < 4)
.map(String::toUpperCase)
.forEach(word -> capitalized.append(word).append(" "));
System.out.println(capitalized.toString().trim()); // ALL THE AND THE
79. You get clever!
String sentence = "all mimsy were the borogoves and the mome raths";
StringBuilder capitalized = new StringBuilder();
Stream.of(sentence.split(" "))
.filter(w -> w.length() < 4)
.map(String::toUpperCase)
.forEach(word -> capitalized.append(word).append(" "));
System.out.println(capitalized.toString().trim()); // ALL THE AND THE
Now try this!
String sentence = "all mimsy were the borogoves and the mome raths";
StringBuilder capitalized = new StringBuilder();
Stream.of(sentence.split(" "))
.parallel()
.filter(w -> w.length() < 4)
.map(String::toUpperCase)
.forEach(word -> capitalized.append(word).append(" "));
System.out.println(capitalized.toString().trim()); // THE AND ALL THE
81. Stream Ops
Mutable Reduction
collect - in which the reduced value is a
mutable result container (like ArrayList)
and elements are incorporated by updating
the state of the result rather than by
replacing the result.
collect using Collectors
toList, toSet etc…
maxBy, minBy, groupingBy, partitioningBy
mapping, reducing etc…
82. Stateless…
String sentence = "all mimsy were the borogoves and the mome raths";
String capitalized =
Stream.of(sentence.split(" "))
.filter(w -> w.length() < 4)
.map(String::toUpperCase)
.collect(Collectors.joining(" "));
System.out.println(capitalized); // ALL THE AND THE
String sentence = "all mimsy were the borogoves and the mome raths";
String capitalized =
Stream.of(sentence.split(" "))
.parallel()
.filter(w -> w.length() < 4)
.map(String::toUpperCase)
.collect(Collectors.joining(" "));
System.out.println(capitalized); // ALL THE AND THE
…and Parallel
84. Eager Evaluation
Java uses eager evaluation for method
arguments.
Args evaluated before passing to the
method.
class Eager<T> {
private final T value;
Eager(final T value) {
System.out.println("eager...");
this.value = value;
}
public T get() {
return value;
}
}
Integer twice(Integer n) {
System.out.println("twice...");
return 2 * n;
}
Eager<Integer> eager =
new Eager(twice(3));
// twice…
// eager…
System.out.println(eager.get());
// 6
85. Simulate Lazy Evaluation
Lazy - Don’t compute until demanded for.
To delay the evaluation of args (lazy),
wrap them in lambda.
Call the lambda when we need to evaluate.
class Lazy<T> {
private final Supplier<T> value;
Lazy(final Supplier<T> value) {
System.out.println("lazy...");
this.value = value;
}
public T get() {
return value.get();
}
}
Lazy<Integer> lazy =
new Lazy(() -> twice(3));
// lazy…
System.out.println(lazy.get());
// twice…
// 6
Representation of
computation and
not the
computation itself.
86. Generating Streams
Stream<Integer> naturals(int from) {
if (from < 0) throw new IllegalArgumentException();
// By one more than the one before
return Stream.iterate(from, x -> x + 1);
}
naturals(0).limit(3).forEach(System.out::println); // 0 1 2
Using iterate
Using generate
Stream<Integer> randoms() {
final Random random = new Random();
return Stream.generate(() -> random.nextInt(6)).map(x -> x + 1);
}
randoms().limit(3).forEach(System.out::println); // 0 1 2
87. Infinitely Lazy
Stream<Integer> naturals(int from) {
return Stream.iterate(from, x -> x + 1);
}
// will not terminate
naturals(0).forEach(System.out::println);
Streams, unlike lists (finite), are
infinite.
They have a starting point, but no end.
Streams, unlike lists (eager), are lazy.
88. Infinitely Lazy
Immutability and Purity makes lazy
evaluation possible.
The answer will be same at time t = 0
and at t = 10 as well.
Immutability and Purity are the key to
Laziness.
89. Lazy Evaluation and
Side-Effects
On the other hand, if you mutate (doing
side-effects), you will get different
answers later, so you have to be eager,
you cannot afford to be lazy.
In presence of side-effects, knowing
the order is a must.
Lazy-evaluation and side-effects can
never be together! It will make
programming very difficult.
90. Virtues of Laziness
With Streams, only essential space is
allocated upon materialization, the rest
is in ether :)
This reduces memory footprint (as you don’t bring
every item in memory).
A powerful modularization: Separating
Generation from Selection - John Hughes
This saves CPU cycles (as computation is delayed
until demand is placed).
Streams are pull-based, consumer decides
the pace of pull as producer is lazy.
91. Finite from Infinite
List<Integer> evens(int from, int howMany) {
return naturals(from)
.filter(n -> n % 2 == 0)
.limit(howMany)
.collect(Collectors.toList());
}
List<Integer> first5Evens = evens(0, 5);
System.out.println(first5Evens);
Terminal operations like forEach, collect,
reduce etc… place demand; whereas non-
terminal operations like filter, map, skip
etc… return another Stream.
Laziness makes it easy to compose programs
as we don’t do more work than essential.
92. Given two lists:
[‘a’, ‘b’] and [1, 2]
Generate the combinations given below:
[[‘a’, 1], [‘a’, 2], [‘b’, 1], [‘b’, 2]]
Imperative Solution
List<Character> alphabets = Arrays.asList('a', 'b');
List<Integer> numbers = Arrays.asList(1, 2);
List<List<?>> combinations = new ArrayList<>();
for (Character c : alphabets) {
for (Integer n : numbers) {
combinations.add(Arrays.asList(c, n));
}
}
System.out.println(combinations); // [[a, 1], [a, 2], [b, 1], [b, 2]]
94. Using Spliterator
Spliterators, like Iterators, are for
traversing the elements of a source.
Supports efficient parallel traversal in
addition to sequential traversal.
It splits the collection and partitions
elements. By itself, this not parallel
processing, its simply division of data.
Not meant for mutable data-sources.
Non-deterministic behaviour when the data-
source is structurally modified (add/remove/
update elements) during the traversal.
https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html
97. Streamable ResultSet
class Employee {
private …,
}
Sql.execute(dburl, "select * from employee limit 1",
rSet -> new Employee(rSet.getString(1), rSet.getDate(2)),
(Stream<Employee> es) -> {
// You can now use the employee stream here and
// go on pulling the records from database.
});
98. Composition
Function<Integer, Integer> square = x -> x * x;
Function<Integer, Integer> twice = x -> 2 * x;
Function<T, R> compose(Function<U, R> f, Function<T, U> g) {
return x -> f.apply(g.apply(x));
}
Function<Integer, Integer> twiceAndSquare = compose(square, twice);
twiceAndSquare.apply(2); // 16
Compose a function from other functions
by aligning types.
100. Composition
Function<String, Stream<String>> split =
s -> Stream.of(s.split(" "));
Function<Stream<String>, Stream<String>> capitalize =
words -> words.map(String::toUpperCase);
Function<Stream<String>, Stream<String>> lessThan4 =
words -> words.filter(word -> word.length() < 4);
Function<Stream<String>, String> join =
words -> words.collect(Collectors.joining(" "));
Function<String, String> composedSequence =
join.compose(lessThan4).compose(capitalize).compose(split);
composedSequence.apply("all mimsy were the borogoves"); // ALL THE
Composing behaviours…
Earlier, we saw…
String sentence = "all mimsy were the borogoves";
join(lessThan3(capitalize(split(sentence)))); // ALL THE
101. Function<String, String> composedSequence =
join.compose(lessThan4).compose(capitalize).compose(split);
composedSequence.apply("all mimsy were the borogoves"); // ALL THE
Function<String, String> andThenedSequence =
split.andThen(capitalize).andThen(lessThan4).andThen(join);
andThenedSequence.apply("all mimsy were the borogoves"); // ALL THE
For languages that support function
composition, look for a way to go with
the grain of thought.
In Java8, prefer using andThen
Composition
Think Right to Left
Read Left to Right
Read Left to Right
Think Left to Right
102. Why Composition?
Tackle complexity by composing
behaviours.
Enforce order of evaluation.
In imperative programming, statements enforce
order of evaluation.
In FP, the order of composition determines the
order of evaluation.
103. Reflections
Function composition (and not function
application) is the default way to
build sub-routines in Concatenative
Programming Style, a.k.a Point Free
Style.
Functions neither contain argument
types nor names, they are just laid out
as computation pipeline.
Lot of our domain code is just trying
to do this!
Makes code more succinct and readable.
http://codejugalbandi.github.io/codejugalbandi.org
104. Reflections
Composition is the way to tackle
complexity - Brian Beckman.
Compose larger functions from smaller
ones
Subsequently every part of the larger
function can be reasoned about
independently.
If the parts are correct, we can then
trust the correctness of the whole.
106. Currying
// Function with all args applied at the same time.
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
// Curried Function - one arg at a time.
Function<Integer, Function<Integer, Integer>> add = x -> y -> x + y;
add.apply(2).apply(3); // 5
Unavailable out-of-box in Java8.
Curried function is a nested structure,
just like Russian dolls, takes one arg at
a time, instead of all the args at once.
For each arg, there is
another nested
function, that takes
a arg and returns a
function taking the
subsequent arg, until
all the args are
exhausted.
107. Why Currying?
Helps us reshape and re-purpose the
original function by creating a
partially applied function from it.
Function<Integer, Function<Integer, Integer>> add =
x ->
y ->
x + y;
Function<Integer, Integer> increment = add.apply(1);
increment.apply(2); // 3
Function<Integer, Integer> decrement = add.apply(-1);
decrement.apply(2); // 1
108. Why Currying?
Function<Integer, Function<Integer, Integer>> add =
x ->
y ->
x + y;
Brings in composability at argument level.
Scope Contour 1
Scope Contour 2
Facilitates decoupling of arguments to
different scopes.
When ‘increment’ is expressed in terms of ‘add’
where ‘x’ is 1; ‘y’ can come from completely
different scope.
Uncurried functions (all Java fns) are
not composable at arg-level.
109. Currying
Function<Integer, Function<Integer, Function<Integer, Integer>>> add =
x ->
y ->
z ->
x + y + z;
Function type associates towards right.
In other words, arrow groups towards
right.
x -> y -> z -> x + y + z;
// is same as
x -> y -> (z -> x + y + z);
// is same as
x -> (y -> (z -> x + y + z));
110. Currying
Function application associates towards
left.
add.apply(2).apply(3).apply(4); // 9
// is same as
(add.apply(2)).apply(3).apply(4); // 9
// is same as
((add.apply(2)).apply(3)).apply(4); // 9
In other words, apply() groups towards
left.
112. Let’s say we have a Customer repository
class CustomerRepository {
public Customer findById(Integer id) {
if (id > 0)
return new Customer(id);
else
throw new RuntimeException("Customer Not Found");
}
}
Now, we want to allow authorised calls
to repo. So, Let’s write an authorise
function.
class Authoriser {
public Customer authorise(CustomerRepository rep, Request req) {
//Some auth code here which guards the request.
return rep.findById(req.get());
}
}
113. Let’s see them in action…
CustomerRepository repo = new CustomerRepository();
Authoriser authoriser = new Authoriser();
Request req1 = new Request();
Customer customer1 = authoriser.authorise(repo, req1);
Request req2 = new Request();
Customer customer2 = authoriser.authorise(repo, req2);
Requests vary, however the
CustomerRepository is same.
Can we avoid repeated injection of the
repo?
114. One way is to wrap the authorise function
in another function (also called
authorise) that consumes Request and
produces Customer.
It internally newifies the repository and
hard-wires it to the original authorise.
Solution 1
Customer authorise(Request req) {
CustomerRepository repo = new CustomerRepository();
return repo.findById(req.get());
}
Authoriser authoriser = new Authoriser();
Request req1 = new Request();
Customer customer1 = authoriser.authorise(req1);
Request req2 = new Request();
Customer customer2 = authoriser.authorise(req2);
115. One way is to wrap the authorise function
in another function (also called
authorise) that consumes Request and
produces Customer.
It internally newifies the repository and
hard-wires it to the original authorise.
Solution 1
Customer authorise(Request req) {
CustomerRepository repo = new CustomerRepository();
return repo.findById(req.get());
}
Authoriser authoriser = new Authoriser();
Request req1 = new Request();
Customer customer1 = authoriser.authorise(req1);
Request req2 = new Request();
Customer customer2 = authoriser.authorise(req2);
But newification
locally like this is
untestable!
116. One way is to wrap the authorise function
in another function (also called
authorise) that consumes Request and
produces Customer.
It internally newifies the repository and
hard-wires it to the original authorise.
Solution 1
Customer authorise(Request req) {
CustomerRepository repo = new CustomerRepository();
return repo.findById(req.get());
}
Authoriser authoriser = new Authoriser();
Request req1 = new Request();
Customer customer1 = authoriser.authorise(req1);
Request req2 = new Request();
Customer customer2 = authoriser.authorise(req2);
But newification
locally like this is
untestable!
R
eject
117. CustomerRepository repo = new CustomerRepository();
Function<Request, Customer> curriedAuthorise = authorise(repo);
Request req1 = new Request();
Customer customer1 = curriedAuthorise.apply(req1);
Request req2 = new Request();
Customer customer2 = curriedAuthorise.apply(req2);
class Authoriser {
public
Function<Request, Customer> authorise(CustomerRepository repo) {
//Some auth code here which guards the request.
return req -> repo.findById(req.get());
}
}
Re-shape authorise to accept only one
fixed parameter - CustomerRepository
Solution 2
118. CustomerRepository repo = new CustomerRepository();
Function<Request, Customer> curriedAuthorise = authorise(repo);
Request req1 = new Request();
Customer customer1 = curriedAuthorise.apply(req1);
Request req2 = new Request();
Customer customer2 = curriedAuthorise.apply(req2);
class Authoriser {
public
Function<Request, Customer> authorise(CustomerRepository repo) {
//Some auth code here which guards the request.
return req -> repo.findById(req.get());
}
}
Re-shape authorise to accept only one
fixed parameter - CustomerRepository
Solution 2
A
ccept
119. Solution 3
Making our own curry
<T, U, R>
Function<T, Function<U, R>> curry(BiFunction<T, U, R> fn) {
return t -> u -> fn.apply(t, u);
}
// Function with all args applied at the same time.
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
// Curried Function - one arg at a time.
Function<Integer, Function<Integer, Integer>> cAdd = curry(add);
Function<Integer, Integer> increment = cAdd.apply(1);
increment.apply(2); // 3
Function<Integer, Integer> decrement = cAdd.apply(-1);
decrement.apply(2); // 1
It would be nice if
Java8 provided this
out-of-box on
BiFunction
Scala calls
this curried
120. class Authoriser {
public Customer authorise(CustomerRepository rep, Request req) {
//Some auth code here which guards the request.
return repo.findById(req.get());
}
}
Parameterize CustomerRepository instead.
CustomerRepository repo = new CustomerRepository();
Function<Request, Customer> curriedAuthorise =
curry(Authoriser::authorise).apply(repo);
Request req1 = new Request();
Customer customer1 = curriedAuthorise.apply(req1);
Request req2 = new Request();
Customer customer2 = curriedAuthorise.apply(req2);
Solution 3
Making our own curry
121. class Authoriser {
public Customer authorise(CustomerRepository rep, Request req) {
//Some auth code here which guards the request.
return repo.findById(req.get());
}
}
Parameterize CustomerRepository instead.
CustomerRepository repo = new CustomerRepository();
Function<Request, Customer> curriedAuthorise =
curry(Authoriser::authorise).apply(repo);
Request req1 = new Request();
Customer customer1 = curriedAuthorise.apply(req1);
Request req2 = new Request();
Customer customer2 = curriedAuthorise.apply(req2);
Solution 3
Making our own curry
A
ccept
122. Observations
We don’t have to provide all the
arguments to the function at one go!
This is partially applying the function.
In other words, currying enables Partial
Function Application, a.k.a - Partially
Applied Function (PFA).
NOTE: Partially Applied Function (PFA)
is completely different from Partial
Function.
123. Uncurry back or
Tuple it!
<T, U, R>
BiFunction<T, U, R> uncurry(Function<T, Function<U, R>> fn) {
return (t, u) -> fn.apply(t).apply(u);
}
// Curried Function - one arg at a time.
Function<Integer, Function<Integer, Integer>> add = x -> y -> x + y;
// Function with all args applied at the same time.
BiFunction<Integer, Integer, Integer> ucAdd = uncurry(add);
ucAdd.apply(2, 3); // 5
It would be nice if
Java8 provided this
out-of-box on
Function
Scala calls
this tupled
124. Want More Spice?
In the last example, we saw how currying
decouples function arguments to
facilitate just-in-time dependency
injection.
How about constructor or setter
dependency injection?
Lets see how currying acts as a powerful
decoupler, not just limited to the site
function arguments (at least in OO
languages).
125. Regular DI
interface Transaction { }
interface ApprovalStrategy {
boolean approve(List<Transaction> ts);
//…
}
class Clearing {
private final ApprovalStrategy aps;
Clearing(ApprovalStrategy aps) {
this.aps = aps;
}
public boolean approve(List<Transaction> ts) {
return aps.approve(ts);
}
}
//main
ApprovalStrategy singleMakerChecker = new SingleMakerChecker();
Clearing clearing = new Clearing(singleMakerChecker);
clearing.approve(ts);
126. Curried DI
interface Transaction { }
interface ApprovalStrategy {
boolean approve(List<Transaction> ts);
//…
}
class Clearing {
public
Function<ApprovalStrategy, Boolean> approve(List<Transaction> ts) {
return aps -> aps.approve(ts);
}
}
//main
Clearing clearing = new Clearing();
// ApprovalStrategy can now be injected from different contexts,
// one for production and a different one - say mock for testing,
// Just like in case of Regular DI.
clearing.approve(ts).apply(new SingleMakerChecker());
127. Reflections
Currying refers to the phenomena of
rewriting a N-arg function to a nest
of functions, each taking only 1-arg
at a time.
It replaces the need for having to
explicitly “wrap” the old function
with a different argument list - Keeps
code DRY.
You curry strictly from left-to-right.
DI is achieved, not just by injecting
functions, but also by currying
functions. When we curry arguments,
we are injecting dependency.
http://codejugalbandi.github.io/codejugalbandi.org
128. Make Absence Explicit
Optional<T>
Stop null abuse!
Don’t return null, use Optional<T> so that it
explicitly tells that in the type.
A container or view it as a collection
containing single value or is empty.
Presence
Absence
Input(s)
Value
null
129. class Event {
private final Date occurredOn;
private final Optional<String> type;
public Event(final String type) {
occurredOn = new Date();
this.type = Optional.ofNullable(type);
}
public Date occurredOn() { return occurredOn; }
public Optional<String> type() { return type; }
}
Event diwali = new Event("Holiday");
System.out.println(diwali.type().get()); // Holiday
Create Optional<T> - of, ofNullable
Get the value back -> get()
130. Boom!
Set Sensible Default
Avoid get() - it throws Exception for
absence of value
Instead use orElse, orElseGet, orElseThrow
Event shopping = new Event(null);
System.out.println(shopping.type()
.orElse("Other")); // Other
System.out.println(shopping.type()
.orElseGet(() -> "Other"));// Other
shopping.type().orElseThrow(() ->
new IllegalArgumentException("Empty or null")));
Event shopping = new Event(null);
System.out.println(shopping.type().get());
131. Do side-effects if
value is present
Imperative check for presence of value.
Instead use ifPresent()
Event diwali = new Event("Holiday");
if (diwali.type().isPresent()) {
System.out.println(diwali.type().get()); // Holiday
}
Event diwali = new Event("Holiday");
diwali.type().ifPresent(System.out::println); // Holiday
135. Option 1: Bubble up…
Re-throw the exception as unchecked
exception.
String capitalize(String s) throws Exception {
if (null == s)
throw new Exception("null");
return s.toUpperCase();
}
Arrays.asList("Hello", null).stream()
.map(s ->
{
try {
return capitalize(s);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.forEach(System.out::println);
Lambda looks
grotesque.
136. Option 2: Deal with it…
Handle the exception in the lambda
String capitalize(String s) throws Exception {
if (null == s)
throw new Exception("null");
return s.toUpperCase();
}
Arrays.asList("Hello", null).stream()
.map(s ->
{
try {
return capitalize(s);
} catch (Exception e) {
return "#fail";
}
})
.collect(Collectors.toList()); // [HELLO, #fail]
1.Success and Failure are
indistinguishable, despite a
#fail
2.Lambda still looks grotesque.
137. Option 3: Wrap using
Exceptional SAM
http://mail.openjdk.java.net/pipermail/lambda-dev/2013-January/007662.html
@FunctionalInterface
interface FunctionThrowsException<T, R, E extends Throwable> {
public R apply(T t) throws E;
}
abstract class Try {
public static<T, R, E extends Throwable>
Function<T, R> with(FunctionThrowsException<T, R, E> fte) {
return t -> {
try { return fte.apply(t); }
catch(Throwable e) { throw new RuntimeException(e); }
};
}
}
138. Beauty of
Lambda
restored.
class Test {
String capitalize(String s) throws Exception {
if (null == s)
throw new Exception("null");
return s.toUpperCase();
}
public static void main(String[] args) {
Arrays.asList("Hello", null)
.stream()
.map(s -> Try.with(Test::capitalize).apply(s))
.forEach(System.out::println);
}
}
Option 3: Wrap using
Exceptional SAM
139. Make all Failure
Explicit - Try<T>
View it as a singleton collection
containing result of execution or failure.
Translated from Scala to Java - http://
dhavaldalal.github.io/Java8-Try
Checked or
Unchecked
Exception.
Success
Failure
Input(s)
Value
140. Boom!
Try<String> success = Try.with(() -> "Holiday");
success.get(); // Holiday
//throws unchecked ArithmeticException
Try<Integer> failure = Try.with(() -> 2 / 0);
failure.get(); //throws exception - failure does not return
with - Supplier, Consumer, Functions
and Predicates.
Get the value back -> get()
Creating Try<T>
141. Avoid get() - Failure throws Exception
Set Sensible Default
Integer value = Try.with(() -> Test.methodThatThrows())
.getOrElse(2);
System.out.println(value); // 2
class Test {
static String methodThatThrows() throws Exception {
throw new Exception("I never work");
}
}
//throws checked Exception
Try<Integer> failure = Try.with(() -> Test.methodThatThrows());
failure.get(); //throws exception - failure does not return
Instead, use getOrElse()
Boom!
142. Try this or try that
Try<Boolean> authenticated =
Try.with(() -> login(name, password))
.orElse(Try.with(() -> gmail(id, pwd))
.orElse(Try.with(() -> fbLogin(fbUser, fbPwd))
.orElse(Try.with(() -> false);
Or even that - Chain of Responsibility
Try<Boolean> authenticated =
Try.with(() -> login(name, password))
.orElse(Try.with(() -> gmail(id, password));
143. Doing side-effects
Avoid imperative check for presence of
value.
Instead use forEach()
Try<String> holiday = Try.with(() -> "Diwali");
if (holiday.isSuccess()) {
System.out.println(holiday.get()); // Diwali
}
Try<String> holiday = Try.with(() -> "Diwali");
holiday.forEach(System.out::println); // Diwali
Try<Integer> failure = Try.with(() -> 2 / 0);
failure.forEach(System.out::println); // Prints Nothing
145. flatMapping a Try
FunctionThrowsException<String, Connection, SQLException>
getConnection = DriverManager::getConnection;
String url = "jdbc:oracle:oci8:scott/tiger@myhost";
//Try getting a connection first
Try<Connection> connection = Try.with(getConnection, url);
146. flatMapping a Try
FunctionThrowsException<String, Connection, SQLException>
getConnection = DriverManager::getConnection;
String url = "jdbc:oracle:oci8:scott/tiger@myhost";
//Try getting a connection first
Try<Connection> connection = Try.with(getConnection, url);
FunctionThrowsException<Connection, Statement, SQLException>
createStatement = c -> c.createStatement();
//Try creating a connection from statement
Try<Try<Statement>> statement =
connection.map(c -> Try.with(createStatement, c));
147. flatMapping a Try
FunctionThrowsException<String, Connection, SQLException>
getConnection = DriverManager::getConnection;
String url = "jdbc:oracle:oci8:scott/tiger@myhost";
//Try getting a connection first
Try<Connection> connection = Try.with(getConnection, url);
FunctionThrowsException<Connection, Statement, SQLException>
createStatement = c -> c.createStatement();
//Try creating a connection from statement
Try<Try<Statement>> statement =
connection.map(c -> Try.with(createStatement, c));
BiFunctionThrowsException<Statement, String, ResultSet, SQLException>
execute = (stmt, query) -> {
stmt.execute(query);
return stmt.getResultSet();
};
String sql = "select * from events limit 1";
//Try creating a result set from statement
Try<Try<Try<ResultSet>>> resultSet =
statement.map(c -> c.map(s -> Try.with(execute, s, sql)));
148. FunctionThrowsException<ResultSet, Event, SQLException> toEvent =
r -> {
String type = r.getString(1);
return new Event(type);
};
//Try creating an event from result set
Try<Try<Try<Try<Event>>>> event =
resultSet.map(c -> c.map(s -> s.map(r -> Try.with(toEvent, r))));
//====== Summarizing what we did ========
Try<Try<Try<Try<Event>>>> nestedEvent =
Try.with(getConnection, url)
.map(c -> Try.with(createStatement, c))
.map(c -> c.map(s -> Try.with(execute, s, sql)))
.map(c -> c.map(s -> s.map(r -> Try.with(toEvent, r))));
flatMapping a Try
149. Try<Try<Try<Try<Event>>>> nestedEvent =
Try.with(getConnection, url)
.map(c -> Try.with(createStatement, c))
.map(c -> c.map(s -> Try.with(execute, s, sql)))
.map(c -> c.map(s -> s.map(r -> Try.with(toEvent, r))));
Look at
that nest of maps
to get to event.
Connection Statement ResultSet Event
Try<Try<Try<Try<Event>>>>
Actual
Event
This pattern is very common in FP when
chaining map operations like this - its
called Monad.
To reduce ‘map’ noise, use flatMap - it
flattens and then maps.
At Each increasing
level the actual
code is pushed
inside by one level
of indentation
150. //flatMapping on Connection, Statement and ResultSet
Try<Event> flattenedEvent =
Try.with(getConnection, url)
.flatMap(c -> Try.with(createStatement, c))
.flatMap(s -> Try.with(execute, s, sql))
.flatMap(r -> Try.with(toEvent, r));
flatMapping a Try
The nest is
now flattened
flatMap unpacks the result of Try and
maps to another Try.
flatMap is the adobe for programmers -
Erik Meijer
ConnectionTry StatementTry ResultSetTry EventTry
151. Error Handling
So far we focussed on happy path.
Question:
How can we react to errors in a functional
style, especially now that we can chain the
Trys?
Answer:
Lets look at chain of responsibility in
error handling.
157. class EventRepository {
private final Map<Integer, Event> events = new HashMap<>();
public EventRepository() { … }
public Optional<Event> findById(final Integer id) {
Objects.requireNonNull(id); // throws NPE
return Optional.ofNullable(events.getOrDefault(id, null));
}
}
EventRepository repository = new EventRepository();
// Making NPE Explicit using Try
Try<Optional<Date>> occurred = Try.with(() ->
repository.findById(1).map(Event::occurredOn));
Date eventDate = occurred.toOptional() // Optional<Optional<Date>>
.flatMap(x -> x) // Optional<Date>
.orElse(null); // Date
System.out.println(eventDate);
Try -> Optional
158. Make Latency Explicit
CompletableFuture<T>
Latency could be because of a CPU or an
I/O intensive computation.
View CompletableFuture<T> as a singleton
collection containing result of latent
computation.
It is Asynchronous.
Success
Failure
Input(s)
Value
Caller does not wait for future
to complete as Future is non-
blocking. Caller immediately
returns and continues execution
on its thread.
Future runs in its own
thread and calls back
with result when the
latent task is complete.
159. Creating a Future
CompletableFuture.supplyAsync
Integer expensiveSquare(Integer n) {
try {
Thread.sleep(1000);
return n * n;
} catch (InterruptedException ie) { }
}
public static void main(String[] args) {
CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> expensiveSquare(2.0));
System.out.println("Waiting for future to complete...");
try {
Integer result = future.get(); //wait for it to complete
System.out.println("Result = " + result);
}
catch (InterruptedException e) { }
catch (ExecutionException e) { }
}
160. Creating a Future
CompletableFuture<Integer> square(Integer x, boolean canSucceed) {
CompletableFuture<Integer> future = new CompletableFuture<>();
future.runAsync(() -> {
System.out.println("Running future...");
try { Thread.sleep(1000); }
catch (InterruptedException e) { }
if (canSucceed) future.complete(x * x);
else future.completeExceptionally(new RuntimeException("FAIL"));
});
System.out.println("Returning future...");
return future;
}
public static void main(String[] args) {
CompletableFuture<Integer> future = square(2, true);
System.out.println("Waiting for future to complete...");
try {
Integer result = future.get(); //wait for it to complete
System.out.println("Result = " + result);
} catch (InterruptedException e) { }
catch (ExecutionException e) { }
}
Using new
161. Getting Result
get() blocks until result is available.
For future completing with exception,
get() throws
CompletableFuture<Integer> future = square(2, false);
System.out.println("Waiting for future to complete...");
try {
Integer result = future.get();
System.out.println("Result = " + result);
}
catch (InterruptedException e) { e.printStackTrace(); }
catch (ExecutionException e) { e.printStackTrace(); }
// Returning future...
// Running future...
// java.util.concurrent.ExecutionException:java.lang.RuntimeException:FAIL
Boom!
162. Doing Side-effects
Avoid get(), instead use thenAccept()/
thenRun() or thenAcceptAsync()/
thenRunAsync()
Async methods run in a different thread
from the thread pool
Non-async methods in the same thread on
which the future completed.
CompletableFuture.supplyAsync(() -> expensiveSquare(2))
.thenAccept(System.out::println); // 4
CompletableFuture.supplyAsync(() -> expensiveSquare(2))
.thenAcceptAsync(System.out::println); // 4
165. Error handling and
Recovery
So far we focussed on Happy path.
A future may not give what you expected,
instead turn exceptional.
Recover using exceptionally()
CompletableFuture<Integer> future = square(2, false);
future.exceptionally(ex -> -1)
.thenAccept(System.out::println);
// Returning future...
// Running future...
// -1
166. CompletableFuture<Integer> future = square(2, false);
future.handle((result, ex) -> {
if (result != null) return result;
else return -1;
})
.thenAccept(System.out::println);
// Returning future...
// Running future...
// -1 when future fails or 4 when it succeeds.
Success and Recovery using handle().
Error handling and
Recovery
169. Accepting Either
Future Results (or)
// If fastUnpredictableSquare completes first with an answer,
// then it will print. In an event fastUnpredictableSquare does
// not return, then slowPredictableSquare will give the answer.
slowPredictableSquare(2)
.acceptEither(fastUnpredictableSquare(2), System.out::println);
// fastUnpredictableSquare
// 4
// Mapping whichever completes first.
slowPredictableSquare(2)
.applyToEither(fastUnpredictableSquare(2), result -> 2 * result)
.thenAccept(System.out::println);
// fastUnpredictableSquare without answer
// slowPredictableSquare
// 8
170. Combining Many Futures
CompletableFuture<Integer> [] futures = new CompletableFuture [] {
slowPredictableSquare(1),
slowPredictableSquare(2),
slowPredictableSquare(3),
slowPredictableSquare(4),
slowPredictableSquare(5),
slowPredictableSquare(6)
};
CompletableFuture.allOf(futures);
Using allOf().
Ensures all the futures are completed,
returns a Void future.
171. Any of the Many Futures
Using anyOf().
Ensures any one of the futures is
completed, returns result of that
completed future.
CompletableFuture<Integer> [] futures = new CompletableFuture [] {
slowPredictableSquare(1),
slowPredictableSquare(2),
slowPredictableSquare(3),
slowPredictableSquare(4),
slowPredictableSquare(5),
slowPredictableSquare(6)
};
CompletableFuture.anyOf(futures)
.thenAccept(System.out::println);
172. Essence
A functional program empowers the
developer with:
Reasoning with ease.
Testability.
Refactoring safely.
Composability.
173. Essence
Immutability and purity makes laziness,
referential transparency possible.
All effects that are not apparent in the
return type of a method are abstracted and
made explicit using a type.
For example
Dealing with null values is made explicit in
Optional<T>
Exceptions as a effect is made explicit in
Try<T>
Latency as a effect is made explicit in
CompletableFuture<T>
174. References
http://codejugalbandi.github.io/codejugalbandi.org
Ryan Lemmer, Dhaval Dalal
Functional Programming in Java
Venkat Subramaniam
Neophyte’s Guide to Scala
Daniel Westheide
Principles of Reactive Prog. Coursera Course
Martin Odersky, Erik Meijer and Roland Kuhn
http://en.wikipedia.org/wiki/
Concatenative_programming_language
http://www.nurkiewicz.com/2013/05/java-8-definitive-
guide-to.html