SlideShare a Scribd company logo
Jumping With Java8
Come, Lambda Along!
dhaval.dalal@software-artisan.com
@softwareartisan
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;
}
}
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
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.
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;
}
}
A Mother-like Function
Gives unconditionally without asking
for anything.
Example - the getters.
class Calculator {
private Integer memory;
public Integer recallMemory() {
return memory;
}
}
public String concat(String x, String y) {
return x + y;
}
String a = "referential";
String b = " transparency ";
String r1 = concat(a, b); // referential transparency
String r2 = concat(a, b); // referential transparency
Understanding
Referential
Transparency
public String concat(StringBuilder x, String y) {
return x.append(y).toString();
}
StringBuilder a = new StringBuilder("referential");
String b = " transparency ";
String r1 = concat(a, b); // referential transparency
String r2 = concat(a, b); // referential transparency referential
Understanding
Referential
Transparency
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
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.
How can we make
memoryPlus
Referential
Transparent?
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.
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.
Reflections
To be referentially transparent,
function will require to work with
immutable data.
To be referentially transparent, a
function will require to be pure.
Why use Immutability
and Pure Functions?
Immutablity.
Promotes caching of objects - Flyweights.
Enables concurrent operations.
Pure Functions.
Promote Memoization - caching results of
expensive computations.
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.
Chai Chat: OO & FP
Chai Chat: OO & FP
We encapsulate data because we think
it will protect us from inadvertent
changes and build trust.
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
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.
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
Chai Chat: OO & FP
Knowing the innards of an object,
causes coupling to parts, which comes
in the way of refactoring as
requirements change.
Chai Chat: OO & FP
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
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
Chai Chat: OO & FP
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
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
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
Encapsulation Vs Open Immutable Data
(structures).
Immutability Vs State-Time Conflation.
Don’t we value both?
Immutability
Encapsulation
Reflections
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
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
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
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
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;
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);
Existing Consumers
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent e);
}
// Can be written as
event -> System.out.println(event);
A Mother-like
Functional Interface
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Things Emerge
without
asking for anything
Supplier<String> mother = () -> "Love";
mother.get(); // Love
Existing Suppliers
public interface Callable<V> {
public V call();
}
// Can be written as:
() -> 2;
Mr.Spock-like
Functional Interface
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
…
}
Predicate<T> affirmative = x -> true;
affirmative.test(1); // true
Predicate<T> negative = x -> false;
negative.test(1); // false
Predicate<Integer> isEven = x -> x % 2 == 0;
isEven.test(2); // true
isEven.test(3); // false
Functional
Interface
Unimplemented
Method
Static Method
Default Method
1
0..*
0..*
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.
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.
Encapsulate Self-Ref
Anonymous Functions?
Integer sum(List<Integer> ns) {
BiFunction<Integer, List<Integer>, Integer> sum0 = (acc, xs) -> {
if (xs.isEmpty())
return acc;
else
return sum0.apply(acc + xs.get(0), xs.subList(1, xs.size()));
};
return sum0.apply(0, ns);
}
Variable ‘sum0’ might have not been
initialized! Compilation
Boom!
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…
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?
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.
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!
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.
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();
}
}
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
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
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);
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?
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;
}
}
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
abstract class Logger {
enum Level { INFO, WARN, ERROR, FATAL };
public void log(Level level, String message) {
String logMessage = enrich(level, message);
write(logMessage);
}
// Hook
protected String enrich(Level level, String message) {
return String.format("%s [%s] %s %s", new Date(), Thread.currentThread(), level,
message);
}
//Mandate
abstract void write(String message);
}
class DatabaseLogger extends Logger {
DatabaseLogger(String url) { }
void write(String message) {
System.out.println("Database Logger writing => " + message);
}
}
class ConsoleLogger extends Logger {
void write(String message) {
System.out.println("Console Logger writing => " + message);
}
}
Can the design be improved?
Download the Gist Logger Refactoring
Subsumed Templateclass Logger {
private final Consumer<String> destination;
enum Level { INFO, WARN, ERROR, FATAL };
Logger(Consumer<String> destination) { this.destination = destination; }
public void log(Level level, String message) {
String logMessage = enrich(level, message);
destination.accept(logMessage);
}
protected String enrich(Level level, String message) {
return String.format("%s [%s] %s %s", new Date(), Thread.currentThread(), level,
message);
}
}
class DatabaseWriter {
DatabaseWriter(String url) { }
void write(String message) {
System.out.println("Database Logger writing => " + message);
}
}
public static void main(String[] args) throws Exception {
Logger db = new Logger(new DatabaseWriter("url")::write);
db.log(Logger.Level.INFO, "Hello");
Logger console = new Logger(System.out::println);
console.log(Logger.Level.INFO, "Hello");
}
Subsumed Proxy
Memoization
public<T, R> Function<T, R> memoize(Function<T, R> fn) {
final Map<T, R> cache = new ConcurrentHashMap<>();
return t -> {
if (!cache.containsKey(t)) {
R r = fn.apply(t);
cache.put(t, r);
return r;
}
return cache.get(t);
};
}
Function<Double, Double> memoizedDoubler = memoize(x -> {
System.out.println("Evaluating...");
return x * 2;
});
memoizedDoubler.apply(2.0); // Evaluating…
memoizedDoubler.apply(2.0);
memoizedDoubler.apply(3.0); // Evaluating…
memoizedDoubler.apply(3.0);
Look Ma!
Its Raining Lambdas
Subsumed Aspect - AOP around style.
public<T, R> Function<T, R> time(Function<T, R> fn) {
return t -> {
long startTime = System.currentTimeMillis();
R result = fn.apply(t);
long timeTaken = System.currentTimeMillis() - startTime;
System.out.println("Time: " + timeTaken + " ms");
return result;
}
}
Subsumed Decorator
Function<Integer, Integer> expensiveSquare = x -> {
System.out.println("Now Squaring...");
try { Thread.sleep(2 * 1000); }
catch (Exception e) { }
return x * x;
};
// Decorate with time and memoize
Function<Integer, Integer> tmSquare =
time(memoize(expensiveSquare));
System.out.println(tmSquare.apply(3));
System.out.println(tmSquare.apply(3));
With the ability to pass and return funcs,
OO design patterns like Strategy, Proxy,
Decorator etc… get subsumed in FP.
Canthisbe
improved?
class Sql {
static List<Employee> execute(String dburl, String sql) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
List<Employee> employees = new ArrayList<Employee>();
try {
connection = DriverManager.getConnection(dburl);
statement = connection.createStatement();
statement.execute(sql);
resultSet = statement.getResultSet();
while (resultSet.next()) {
int empId = resultSet.getInt(0);
String name = resultSet.getString(1);
employees.add(new Employee(empId, name));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
if (statement != null) {
statement.close();
if (resultSet != null)
resultSet.close();
}
}
}
return employees;
}
}
Download the Gist
Smelly JDBC Code
Loan My Resourceinterface ConsumerThrowsException<T, E extends Throwable> {
public void accept(T t) throws E;
}
class Sql {
static<T> void execute(String dburl, String sql,
ConsumerThrowsException<ResultSet, SQLException> fn) throws
SQLException {
try (Connection connection = DriverManager.getConnection(dburl)) {
try (Statement statement = connection.createStatement()) {
statement.execute(sql);
try (ResultSet resultSet = statement.getResultSet()) {
fn.accept(resultSet);
}
}
} catch (SQLException e) {
throw e;
}
}
}
Sql.execute(dburl, "select * from events limit 1", resultSet -> {
//loop through result set and map to List<Event>
});
Loan My Resource
Acquire
Loan resource for use
Release
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));
}
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
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
Refactored Code
List<String> split(String sentence) {
return Arrays.asList(sentence.split(" "));
}
List<String> capitalize(List<String> words) {
List<String> upperCased = new ArrayList<String>();
for (word : words) {
upperCased.add(word.toUpperCase());
}
return upperCased;
}
List<String> lessThan4(List<String> words) {
List<String> filtered = new ArrayList<String>();
for (word : words) {
if (word.length() < 4) {
filtered.add(word);
}
}
return filtered;
}
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
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?
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();
Sequencing
Stream.of("Brahma", "Vishnu", "Mahesh")
.map(String::toUpperCase)
.forEach(System.out::println); // BRAHMA
// VISHNU
// MAHESH
Chaining and applying operations one
after the other.
Moving data down the processing pipes.
Operate on collection elements one at a
time, rather than operate on all
elements at once.
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
Stream Ops
Combine adjacent elements
reduce, min, max, count, sum
Sort and Unique
sorted and distinct
take and drop
limit, skip
Parallel Stream
Double expensiveSquare(Double n) {
try {
Thread.sleep(2 * 1000); // 2 secs
return n * n;
} catch (InterruptedException ie) {
return Double.NaN;
}
}
List<Double> timer(Stream<Double> numbers) {
long startTime = System.currentTimeMillis();
List<Double> squares = numbers.map(n -> expensiveSquare(n))
.collect(Collectors.toList());
long timeTaken = System.currentTimeMillis() - startTime;
System.out.println("Time: " + timeTaken + " ms");
return squares;
}
List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
timer(numbers.stream()); // Time: 10078 ms
timer(numbers.parallelStream()); // Time: 2013 ms
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));
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);
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
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
Stateful
+
Parallel
==
Problem!
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…
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
Stateless
+
Parallel
==
No Problem!
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
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.
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
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.
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.
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.
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.
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.
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]]
Subsumed Nested Loops
List<List<?>> combinations(List<?> one, List<?> two) {
return one.stream()
.flatMap(o ->
two.stream().map(t ->
Arrays.asList(o, t)))
.collect(Collectors.toList());
}
List<Character> alphabets = Arrays.asList('a', 'b');
List<Integer> numbers = Arrays.asList(1, 2);
List<List<?>> combo = combinations(alphabets, numbers);
System.out.println(combo); // [[a, 1], [a, 2], [b, 1], [b, 2]]
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
Streamable ResultSet
class ResultSetIterator<T> implements Iterator<T> {
private ResultSet resultSet;

private FunctionThrowsException<ResultSet, T, SQLException> mapper;



ResultSetIterator(ResultSet resultSet,
FunctionThrowsException<ResultSet, T, SQLException> mapper) {

this.resultSet = resultSet;

this.mapper = mapper;

}

@Override

public boolean hasNext() {

try { return resultSet.next(); }
catch (SQLException e) { throw new RuntimeException(e); }

}

@Override

public T next() {

try { return mapper.apply(resultSet); }
catch (SQLException e) { throw new RuntimeException(e); }

}

}
interface FunctionThrowsException<T, R, E extends Throwable> {
public R apply(T t) throws E;
}
class Sql {
static void execute(String dburl, String sql,
FunctionThrowsException<ResultSet, R, SQLException> mapper,
Consumer<Stream<R>> consumer) throws SQLException {
try (Connection connection = DriverManager.getConnection(dburl)) {
try (Statement statement = connection.createStatement()) {
statement.execute(sql);
try (ResultSet resultSet = statement.getResultSet()) {
Iterator rsi = new ResultSetIterator(resultSet, mapper);
Stream<R> stream = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(rsi,
Spliterator.ORDERED),
false);
consumer.accept(stream);
}
}
} catch (SQLException e) {
throw e;
}
}
}
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.
});
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.
Composition
square.andThen(twice).apply(2); // 8
Java provides composition on Function.
f ⨁ g
Function composition is not commutative.
f ⨁ g != g ⨁ f
square.compose(twice).apply(2); // 16
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
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
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.
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
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.
Outside
World
Side
Effecting
Functions
Pure
Functions
Compose behaviours
using pure Functions.
Data flows through
composed pipes.
Interact with outside
world using side-
effecting functions
or Monads.
Circle of Purity
Courtesy: Venkat
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.
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
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.
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));
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.
But, how does that
still make
programming spicier?
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());
}
}
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?
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);
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!
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
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
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
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
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
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
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.
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
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).
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);
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());
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
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
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()
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());
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
Optional Operations
Event diwali = new Event("Holiday");
diwali.type()
.map(String::length) // Optional<Integer>
.ifPresent(System.out::println); // 7
Transforming Optional - map
Event diwali = new Event("Holiday");
diwali.type()
.filter(t -> t.equalsIgnoreCase("holiday"))
.ifPresent(System.out::println); // Holiday
Filtering an Optional
Gets rid of
nested Optionals
class EventRepository {
private final Map<Integer, Event> events = new HashMap<>();
public EventRepository() {
events.put(1, new Event("Personal"));
events.put(2, new Event("Work"));
}
public Optional<Event> findById(final Integer id) {
return Optional.ofNullable(events.get(id));
}
}
Optional flatMap
EventRepository repository = new EventRepository();
repository.findById(1)
.map(Event::type) // Optional<Optional<String>>
.ifPresent(System.out::println);
repository.findById(1)
.flatMap(Event::type) // Optional<String>
.ifPresent(System.out::println);
repository.findById(1)
.flatMap(Event::type) // Optional<String>
.map(t -> String.format("{'type': '%s'}", t)) // Map to Json
.orElse("{'error' : 'No Event Found'}"); // Default Json
Checked Exceptions
+
Lambda
==
Pain!
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.
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.
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); }
};
}
}
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
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
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>
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!
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));
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
Try Operations
Transforming - map
Filtering
Try<String> success = Try.with(() -> "Diwali");
success.map(String::length)
.forEach(System.out::println); // 6
String nothing = null;
Try<Integer> failure = Try.with(() -> nothing.length());
failure.map(length -> length * 2)
.forEach(System.out::println); // Prints Nothing
Try<String> success = Try.with(() -> "Diwali");
success.filter(s -> s.length() >= 6)
.forEach(System.out::println); // Diwali
success.filter(s -> s.length() < 6)
.forEach(System.out::println); // Prints Nothing
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);
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));
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)));
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
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
//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
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.
The Happy Path
Try.with(() -> 2 / 2)
.recover((Throwable t) -> Double.NaN)
.forEach(System.out::println); // 1.0
1
Try.with ( … )
recover ( … )
1
Try.with(() -> 2 / 0) // throws ArithmeticException
.recover((Throwable t) -> Double.NaN)
.forEach(System.out::println); // NaN
Failure Recovery
NaN
Try.with ( … )
recover ( … )
Failure Propagation
Try.with(() -> Integer.valueOf(null))
.recover(t -> {
if (t.getClass() == ArithmeticException.class) {
return Double.NaN;
}
throw new RuntimeException(t); // Re-throw t
})
.forEach(System.out::println);
Try.with ( … )
recover ( … )
Chain of Recovery
Try<URL> nextURL =
Try.with(() -> login(name, password))
.recover(ex -> {
if (ex.getClass() == PasswordMismatchException.class)
return forgotURL; // default
throw new RuntimeException(ex); // Re-throw
})
// 2 factor authentication returns dashboardURL
.flatMap(user -> Try.with(() -> twoFactorAuth(user, _2FPasswd))
.recover(t -> loginURL);
Try.with ( … )
recover ( … )
recover ( … )
flatMap ( … )
URL
Unhandled
Exceptions
RecoverWith
flatMap like
Try<URL> startURL =
Try.with(() -> login(name, password))
// google login returns user
.recoverWith(ex -> Try.with(() -> googleLogin(gmail));
// 2 factor authentication returns dashboardURL
.flatMap(user -> Try.with(() -> twoFactorAuth(user, _2FPasswd))
.recover(t -> loginURL);
Try.with ( … )
recoverWith( … )
recover ( … )
flatMap ( … )
URL
Unhandled
Exceptions
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
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.
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) { }
}
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
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!
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
Mapping Future
CompletableFuture.supplyAsync(() -> expensiveSquare(2))
.thenApply(result -> 2 * result)
.thenAccept(System.out::println); // 8
CompletableFuture.supplyAsync(() -> expensiveSquare(2))
.thenApplyAsync(result -> 2 * result))
.thenAcceptAsync(System.out::println); // 8
Using thenApply() or thenApplyAsync()
CompletableFuture.supplyAsync(() -> calculateNAV())
.thenCompose(nav -> persistToDB(nav))
.thenAccept(System.out::println);
CompletableFuture.supplyAsync(() -> calculateNAV())
.thenComposeAsync(nav -> persistToDB(nav))
.thenAcceptAsync(System.out::println);
flatMapping Future
Using thenCompose() or thenComposeAsync()
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
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
Combining Results from
couple of Futures (and)
CompletableFuture<Integer> square(Integer x) {
return CompletableFuture.completedFuture(x * x);
}
CompletableFuture<Integer> cube(Integer x) {
try { Thread.sleep(1000); }
catch (InterruptedException e) { }
return CompletableFuture.completedFuture(x * x * x);
}
//Sum of square and cube
square(2)
.thenCombine(cube(3), (squared, cubed) -> squared + cubed)
.thenAccept(System.out::println); // 31
square(2)
.thenAcceptBoth(cube(3), (sqr, cub) ->
System.out.println(sqr + cub)); // 31
Accepting Either
Future Results (or)
CompletableFuture<Integer> slowPredictableSquare(Integer x) {
CompletableFuture<Integer> future = new CompletableFuture<>();
future.runAsync(() -> {
try { Thread.sleep(1000); }
catch (InterruptedException e) { }
System.out.println("slowButPredictableSquare");
future.complete(x * x);
});
return future;
}
CompletableFuture<Integer> fastUnpredictableSquare(Integer x) {
boolean canRespond = random.nextBoolean();
CompletableFuture<Integer> future = new CompletableFuture<>();
future.runAsync(() -> {
if (canRespond) {
System.out.println("fastButUnpredictableSquare");
future.complete(x * x);
} else {
System.out.println("fastButUnpredictableSquare without answer");
}
});
return future;
}
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
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.
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);
Essence
A functional program empowers the
developer with:
Reasoning with ease.
Testability.
Refactoring safely.
Composability.
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>
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
Thank-You!

More Related Content

PDF
Functional programming
PPTX
File Handling Python
PPT
Encapsulation
PPTX
Crafted Design - Sandro Mancuso
PPTX
Advanced Python : Decorators
PPTX
Clean Code
PDF
Strings in Python
Functional programming
File Handling Python
Encapsulation
Crafted Design - Sandro Mancuso
Advanced Python : Decorators
Clean Code
Strings in Python

What's hot (20)

PPT
Introduction to Design Patterns and Singleton
PDF
OOP and FP
PPTX
Interface in java
PPTX
Polymorphism in Python
PDF
Event-sourced architectures with Akka
PPTX
Object Oriented Programming ppt presentation
PDF
Files in C
PPTX
InfoGAN: Interpretable Representation Learning by Information Maximizing Gene...
PDF
From object oriented to functional domain modeling
PDF
Building Large-scale Real-world Recommender Systems - Recsys2012 tutorial
PDF
Java Serialization
PDF
Lesson 03 python statement, indentation and comments
PPTX
Dynamic memory allocation
PPTX
Deep Learning With Python | Deep Learning And Neural Networks | Deep Learning...
PPT
Scala Talk at FOSDEM 2009
PPTX
Template C++ OOP
PPTX
Deep learning lecture - part 1 (basics, CNN)
PDF
Polymorphism in oop
PPTX
Clean Code
PPTX
Introduction to OOP concepts
Introduction to Design Patterns and Singleton
OOP and FP
Interface in java
Polymorphism in Python
Event-sourced architectures with Akka
Object Oriented Programming ppt presentation
Files in C
InfoGAN: Interpretable Representation Learning by Information Maximizing Gene...
From object oriented to functional domain modeling
Building Large-scale Real-world Recommender Systems - Recsys2012 tutorial
Java Serialization
Lesson 03 python statement, indentation and comments
Dynamic memory allocation
Deep Learning With Python | Deep Learning And Neural Networks | Deep Learning...
Scala Talk at FOSDEM 2009
Template C++ OOP
Deep learning lecture - part 1 (basics, CNN)
Polymorphism in oop
Clean Code
Introduction to OOP concepts
Ad

Similar to Jumping-with-java8 (20)

PDF
Booting into functional programming
PPTX
Functional programming principles and Java 8
PDF
Fun with java 8
PPTX
(Fun)ctional go
PPTX
Functional programming
PPTX
Functional programming with Java 8
PDF
If You Think You Can Stay Away from Functional Programming, You Are Wrong
PDF
Functional Go
PDF
Pragmatic functional refactoring with java 8 (1)
PDF
FP in Java - Project Lambda and beyond
PDF
Introduction to new features in java 8
PDF
Pragmatic functional refactoring with java 8
PDF
Map, Flatmap and Reduce are Your New Best Friends: Simpler Collections, Concu...
PDF
Functional programming 101
PDF
Pragmatic Functional Refactoring with Java 8
PDF
Java 8 by example!
PPTX
Столпы функционального программирования для адептов ООП, Николай Мозговой
PDF
Map, flatmap and reduce are your new best friends (javaone, svcc)
PDF
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Booting into functional programming
Functional programming principles and Java 8
Fun with java 8
(Fun)ctional go
Functional programming
Functional programming with Java 8
If You Think You Can Stay Away from Functional Programming, You Are Wrong
Functional Go
Pragmatic functional refactoring with java 8 (1)
FP in Java - Project Lambda and beyond
Introduction to new features in java 8
Pragmatic functional refactoring with java 8
Map, Flatmap and Reduce are Your New Best Friends: Simpler Collections, Concu...
Functional programming 101
Pragmatic Functional Refactoring with Java 8
Java 8 by example!
Столпы функционального программирования для адептов ООП, Николай Мозговой
Map, flatmap and reduce are your new best friends (javaone, svcc)
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Ad

More from Dhaval Dalal (20)

PDF
Sri-Aurobindos-Integral-Education-Principles.pdf
PDF
Test Pyramid in Microservices Context
PDF
Code Retreat
PDF
Currying and Partial Function Application (PFA)
PDF
Creating Lazy stream in CSharp
PDF
Json Viewer Stories
PDF
Value Objects
PDF
Mars rover-extension
PDF
How Is Homeopathy Near To Yoga?
PDF
Approaching ATDD/BDD
PDF
Paradigms Code jugalbandi
PDF
Data Reconciliation
PDF
DRYing to Monad in Java8
PDF
CodeRetreat
PDF
4-Code-Jugalbandi-destructuring-patternmatching-healthycode#apr2015
PDF
3-CodeJugalbandi-currying-pfa-healthycodemagazine#mar2015
PDF
CodeJugalbandi-Sequencing-HealthyCode-Magazine-Feb-2015
PDF
CodeJugalbandi-Expression-Problem-HealthyCode-Magazine#Jan-2015-Issue
PDF
The tao-of-transformation-workshop
PDF
Grooming with Groovy
Sri-Aurobindos-Integral-Education-Principles.pdf
Test Pyramid in Microservices Context
Code Retreat
Currying and Partial Function Application (PFA)
Creating Lazy stream in CSharp
Json Viewer Stories
Value Objects
Mars rover-extension
How Is Homeopathy Near To Yoga?
Approaching ATDD/BDD
Paradigms Code jugalbandi
Data Reconciliation
DRYing to Monad in Java8
CodeRetreat
4-Code-Jugalbandi-destructuring-patternmatching-healthycode#apr2015
3-CodeJugalbandi-currying-pfa-healthycodemagazine#mar2015
CodeJugalbandi-Sequencing-HealthyCode-Magazine-Feb-2015
CodeJugalbandi-Expression-Problem-HealthyCode-Magazine#Jan-2015-Issue
The tao-of-transformation-workshop
Grooming with Groovy

Recently uploaded (20)

PPTX
OMC Textile Division Presentation 2021.pptx
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
August Patch Tuesday
PDF
NewMind AI Weekly Chronicles - August'25-Week II
PPTX
SOPHOS-XG Firewall Administrator PPT.pptx
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Assigned Numbers - 2025 - Bluetooth® Document
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Univ-Connecticut-ChatGPT-Presentaion.pdf
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Mushroom cultivation and it's methods.pdf
PPTX
TLE Review Electricity (Electricity).pptx
PDF
Getting Started with Data Integration: FME Form 101
PDF
A comparative study of natural language inference in Swahili using monolingua...
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Empathic Computing: Creating Shared Understanding
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
1. Introduction to Computer Programming.pptx
OMC Textile Division Presentation 2021.pptx
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Mobile App Security Testing_ A Comprehensive Guide.pdf
August Patch Tuesday
NewMind AI Weekly Chronicles - August'25-Week II
SOPHOS-XG Firewall Administrator PPT.pptx
Diabetes mellitus diagnosis method based random forest with bat algorithm
Assigned Numbers - 2025 - Bluetooth® Document
Accuracy of neural networks in brain wave diagnosis of schizophrenia
Digital-Transformation-Roadmap-for-Companies.pptx
Univ-Connecticut-ChatGPT-Presentaion.pdf
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Mushroom cultivation and it's methods.pdf
TLE Review Electricity (Electricity).pptx
Getting Started with Data Integration: FME Form 101
A comparative study of natural language inference in Swahili using monolingua...
Per capita expenditure prediction using model stacking based on satellite ima...
Empathic Computing: Creating Shared Understanding
Programs and apps: productivity, graphics, security and other tools
1. Introduction to Computer Programming.pptx

Jumping-with-java8

  • 1. Jumping With Java8 Come, Lambda Along! [email protected] @softwareartisan
  • 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; } }
  • 7. public String concat(String x, String y) { return x + y; } String a = "referential"; String b = " transparency "; String r1 = concat(a, b); // referential transparency String r2 = concat(a, b); // referential transparency Understanding Referential Transparency
  • 8. public String concat(StringBuilder x, String y) { return x.append(y).toString(); } StringBuilder a = new StringBuilder("referential"); String b = " transparency "; String r1 = concat(a, b); // referential transparency String r2 = concat(a, b); // referential transparency referential Understanding Referential Transparency
  • 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);
  • 38. A Mother-like Functional Interface @FunctionalInterface public interface Supplier<T> { T get(); } Things Emerge without asking for anything Supplier<String> mother = () -> "Love"; mother.get(); // Love
  • 39. Existing Suppliers public interface Callable<V> { public V call(); } // Can be written as: () -> 2;
  • 40. Mr.Spock-like Functional Interface @FunctionalInterface public interface Predicate<T> { boolean test(T t); … } Predicate<T> affirmative = x -> true; affirmative.test(1); // true Predicate<T> negative = x -> false; negative.test(1); // false Predicate<Integer> isEven = x -> x % 2 == 0; isEven.test(2); // true isEven.test(3); // false
  • 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.
  • 44. Encapsulate Self-Ref Anonymous Functions? Integer sum(List<Integer> ns) { BiFunction<Integer, List<Integer>, Integer> sum0 = (acc, xs) -> { if (xs.isEmpty()) return acc; else return sum0.apply(acc + xs.get(0), xs.subList(1, xs.size())); }; return sum0.apply(0, ns); } Variable ‘sum0’ might have not been initialized! Compilation Boom!
  • 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
  • 57. abstract class Logger { enum Level { INFO, WARN, ERROR, FATAL }; public void log(Level level, String message) { String logMessage = enrich(level, message); write(logMessage); } // Hook protected String enrich(Level level, String message) { return String.format("%s [%s] %s %s", new Date(), Thread.currentThread(), level, message); } //Mandate abstract void write(String message); } class DatabaseLogger extends Logger { DatabaseLogger(String url) { } void write(String message) { System.out.println("Database Logger writing => " + message); } } class ConsoleLogger extends Logger { void write(String message) { System.out.println("Console Logger writing => " + message); } } Can the design be improved? Download the Gist Logger Refactoring
  • 58. Subsumed Templateclass Logger { private final Consumer<String> destination; enum Level { INFO, WARN, ERROR, FATAL }; Logger(Consumer<String> destination) { this.destination = destination; } public void log(Level level, String message) { String logMessage = enrich(level, message); destination.accept(logMessage); } protected String enrich(Level level, String message) { return String.format("%s [%s] %s %s", new Date(), Thread.currentThread(), level, message); } } class DatabaseWriter { DatabaseWriter(String url) { } void write(String message) { System.out.println("Database Logger writing => " + message); } } public static void main(String[] args) throws Exception { Logger db = new Logger(new DatabaseWriter("url")::write); db.log(Logger.Level.INFO, "Hello"); Logger console = new Logger(System.out::println); console.log(Logger.Level.INFO, "Hello"); }
  • 59. Subsumed Proxy Memoization public<T, R> Function<T, R> memoize(Function<T, R> fn) { final Map<T, R> cache = new ConcurrentHashMap<>(); return t -> { if (!cache.containsKey(t)) { R r = fn.apply(t); cache.put(t, r); return r; } return cache.get(t); }; } Function<Double, Double> memoizedDoubler = memoize(x -> { System.out.println("Evaluating..."); return x * 2; }); memoizedDoubler.apply(2.0); // Evaluating… memoizedDoubler.apply(2.0); memoizedDoubler.apply(3.0); // Evaluating… memoizedDoubler.apply(3.0);
  • 60. Look Ma! Its Raining Lambdas Subsumed Aspect - AOP around style. public<T, R> Function<T, R> time(Function<T, R> fn) { return t -> { long startTime = System.currentTimeMillis(); R result = fn.apply(t); long timeTaken = System.currentTimeMillis() - startTime; System.out.println("Time: " + timeTaken + " ms"); return result; } }
  • 61. Subsumed Decorator Function<Integer, Integer> expensiveSquare = x -> { System.out.println("Now Squaring..."); try { Thread.sleep(2 * 1000); } catch (Exception e) { } return x * x; }; // Decorate with time and memoize Function<Integer, Integer> tmSquare = time(memoize(expensiveSquare)); System.out.println(tmSquare.apply(3)); System.out.println(tmSquare.apply(3)); With the ability to pass and return funcs, OO design patterns like Strategy, Proxy, Decorator etc… get subsumed in FP.
  • 62. Canthisbe improved? class Sql { static List<Employee> execute(String dburl, String sql) { Connection connection = null; Statement statement = null; ResultSet resultSet = null; List<Employee> employees = new ArrayList<Employee>(); try { connection = DriverManager.getConnection(dburl); statement = connection.createStatement(); statement.execute(sql); resultSet = statement.getResultSet(); while (resultSet.next()) { int empId = resultSet.getInt(0); String name = resultSet.getString(1); employees.add(new Employee(empId, name)); } } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null) { connection.close(); if (statement != null) { statement.close(); if (resultSet != null) resultSet.close(); } } } return employees; } } Download the Gist Smelly JDBC Code
  • 63. Loan My Resourceinterface ConsumerThrowsException<T, E extends Throwable> { public void accept(T t) throws E; } class Sql { static<T> void execute(String dburl, String sql, ConsumerThrowsException<ResultSet, SQLException> fn) throws SQLException { try (Connection connection = DriverManager.getConnection(dburl)) { try (Statement statement = connection.createStatement()) { statement.execute(sql); try (ResultSet resultSet = statement.getResultSet()) { fn.accept(resultSet); } } } catch (SQLException e) { throw e; } } } Sql.execute(dburl, "select * from events limit 1", resultSet -> { //loop through result set and map to List<Event> });
  • 64. Loan My Resource Acquire Loan resource for use Release
  • 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
  • 68. Refactored Code List<String> split(String sentence) { return Arrays.asList(sentence.split(" ")); } List<String> capitalize(List<String> words) { List<String> upperCased = new ArrayList<String>(); for (word : words) { upperCased.add(word.toUpperCase()); } return upperCased; } List<String> lessThan4(List<String> words) { List<String> filtered = new ArrayList<String>(); for (word : words) { if (word.length() < 4) { filtered.add(word); } } return filtered; }
  • 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();
  • 72. Sequencing Stream.of("Brahma", "Vishnu", "Mahesh") .map(String::toUpperCase) .forEach(System.out::println); // BRAHMA // VISHNU // MAHESH Chaining and applying operations one after the other. Moving data down the processing pipes. Operate on collection elements one at a time, rather than operate on all elements at once.
  • 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
  • 75. Parallel Stream Double expensiveSquare(Double n) { try { Thread.sleep(2 * 1000); // 2 secs return n * n; } catch (InterruptedException ie) { return Double.NaN; } } List<Double> timer(Stream<Double> numbers) { long startTime = System.currentTimeMillis(); List<Double> squares = numbers.map(n -> expensiveSquare(n)) .collect(Collectors.toList()); long timeTaken = System.currentTimeMillis() - startTime; System.out.println("Time: " + timeTaken + " ms"); return squares; } List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0); timer(numbers.stream()); // Time: 10078 ms timer(numbers.parallelStream()); // Time: 2013 ms
  • 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]]
  • 93. Subsumed Nested Loops List<List<?>> combinations(List<?> one, List<?> two) { return one.stream() .flatMap(o -> two.stream().map(t -> Arrays.asList(o, t))) .collect(Collectors.toList()); } List<Character> alphabets = Arrays.asList('a', 'b'); List<Integer> numbers = Arrays.asList(1, 2); List<List<?>> combo = combinations(alphabets, numbers); System.out.println(combo); // [[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
  • 95. Streamable ResultSet class ResultSetIterator<T> implements Iterator<T> { private ResultSet resultSet;
 private FunctionThrowsException<ResultSet, T, SQLException> mapper;
 
 ResultSetIterator(ResultSet resultSet, FunctionThrowsException<ResultSet, T, SQLException> mapper) {
 this.resultSet = resultSet;
 this.mapper = mapper;
 }
 @Override
 public boolean hasNext() {
 try { return resultSet.next(); } catch (SQLException e) { throw new RuntimeException(e); }
 }
 @Override
 public T next() {
 try { return mapper.apply(resultSet); } catch (SQLException e) { throw new RuntimeException(e); }
 }
 }
  • 96. interface FunctionThrowsException<T, R, E extends Throwable> { public R apply(T t) throws E; } class Sql { static void execute(String dburl, String sql, FunctionThrowsException<ResultSet, R, SQLException> mapper, Consumer<Stream<R>> consumer) throws SQLException { try (Connection connection = DriverManager.getConnection(dburl)) { try (Statement statement = connection.createStatement()) { statement.execute(sql); try (ResultSet resultSet = statement.getResultSet()) { Iterator rsi = new ResultSetIterator(resultSet, mapper); Stream<R> stream = StreamSupport.stream( Spliterators.spliteratorUnknownSize(rsi, Spliterator.ORDERED), false); consumer.accept(stream); } } } catch (SQLException e) { throw e; } } }
  • 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.
  • 99. Composition square.andThen(twice).apply(2); // 8 Java provides composition on Function. f ⨁ g Function composition is not commutative. f ⨁ g != g ⨁ f square.compose(twice).apply(2); // 16
  • 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.
  • 105. Outside World Side Effecting Functions Pure Functions Compose behaviours using pure Functions. Data flows through composed pipes. Interact with outside world using side- effecting functions or Monads. Circle of Purity Courtesy: Venkat
  • 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.
  • 111. But, how does that still make programming spicier?
  • 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
  • 132. Optional Operations Event diwali = new Event("Holiday"); diwali.type() .map(String::length) // Optional<Integer> .ifPresent(System.out::println); // 7 Transforming Optional - map Event diwali = new Event("Holiday"); diwali.type() .filter(t -> t.equalsIgnoreCase("holiday")) .ifPresent(System.out::println); // Holiday Filtering an Optional
  • 133. Gets rid of nested Optionals class EventRepository { private final Map<Integer, Event> events = new HashMap<>(); public EventRepository() { events.put(1, new Event("Personal")); events.put(2, new Event("Work")); } public Optional<Event> findById(final Integer id) { return Optional.ofNullable(events.get(id)); } } Optional flatMap EventRepository repository = new EventRepository(); repository.findById(1) .map(Event::type) // Optional<Optional<String>> .ifPresent(System.out::println); repository.findById(1) .flatMap(Event::type) // Optional<String> .ifPresent(System.out::println); repository.findById(1) .flatMap(Event::type) // Optional<String> .map(t -> String.format("{'type': '%s'}", t)) // Map to Json .orElse("{'error' : 'No Event Found'}"); // Default Json
  • 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
  • 144. Try Operations Transforming - map Filtering Try<String> success = Try.with(() -> "Diwali"); success.map(String::length) .forEach(System.out::println); // 6 String nothing = null; Try<Integer> failure = Try.with(() -> nothing.length()); failure.map(length -> length * 2) .forEach(System.out::println); // Prints Nothing Try<String> success = Try.with(() -> "Diwali"); success.filter(s -> s.length() >= 6) .forEach(System.out::println); // Diwali success.filter(s -> s.length() < 6) .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.
  • 152. The Happy Path Try.with(() -> 2 / 2) .recover((Throwable t) -> Double.NaN) .forEach(System.out::println); // 1.0 1 Try.with ( … ) recover ( … ) 1
  • 153. Try.with(() -> 2 / 0) // throws ArithmeticException .recover((Throwable t) -> Double.NaN) .forEach(System.out::println); // NaN Failure Recovery NaN Try.with ( … ) recover ( … )
  • 154. Failure Propagation Try.with(() -> Integer.valueOf(null)) .recover(t -> { if (t.getClass() == ArithmeticException.class) { return Double.NaN; } throw new RuntimeException(t); // Re-throw t }) .forEach(System.out::println); Try.with ( … ) recover ( … )
  • 155. Chain of Recovery Try<URL> nextURL = Try.with(() -> login(name, password)) .recover(ex -> { if (ex.getClass() == PasswordMismatchException.class) return forgotURL; // default throw new RuntimeException(ex); // Re-throw }) // 2 factor authentication returns dashboardURL .flatMap(user -> Try.with(() -> twoFactorAuth(user, _2FPasswd)) .recover(t -> loginURL); Try.with ( … ) recover ( … ) recover ( … ) flatMap ( … ) URL Unhandled Exceptions
  • 156. RecoverWith flatMap like Try<URL> startURL = Try.with(() -> login(name, password)) // google login returns user .recoverWith(ex -> Try.with(() -> googleLogin(gmail)); // 2 factor authentication returns dashboardURL .flatMap(user -> Try.with(() -> twoFactorAuth(user, _2FPasswd)) .recover(t -> loginURL); Try.with ( … ) recoverWith( … ) recover ( … ) flatMap ( … ) URL Unhandled Exceptions
  • 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
  • 163. Mapping Future CompletableFuture.supplyAsync(() -> expensiveSquare(2)) .thenApply(result -> 2 * result) .thenAccept(System.out::println); // 8 CompletableFuture.supplyAsync(() -> expensiveSquare(2)) .thenApplyAsync(result -> 2 * result)) .thenAcceptAsync(System.out::println); // 8 Using thenApply() or thenApplyAsync()
  • 164. CompletableFuture.supplyAsync(() -> calculateNAV()) .thenCompose(nav -> persistToDB(nav)) .thenAccept(System.out::println); CompletableFuture.supplyAsync(() -> calculateNAV()) .thenComposeAsync(nav -> persistToDB(nav)) .thenAcceptAsync(System.out::println); flatMapping Future Using thenCompose() or thenComposeAsync()
  • 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
  • 167. Combining Results from couple of Futures (and) CompletableFuture<Integer> square(Integer x) { return CompletableFuture.completedFuture(x * x); } CompletableFuture<Integer> cube(Integer x) { try { Thread.sleep(1000); } catch (InterruptedException e) { } return CompletableFuture.completedFuture(x * x * x); } //Sum of square and cube square(2) .thenCombine(cube(3), (squared, cubed) -> squared + cubed) .thenAccept(System.out::println); // 31 square(2) .thenAcceptBoth(cube(3), (sqr, cub) -> System.out.println(sqr + cub)); // 31
  • 168. Accepting Either Future Results (or) CompletableFuture<Integer> slowPredictableSquare(Integer x) { CompletableFuture<Integer> future = new CompletableFuture<>(); future.runAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("slowButPredictableSquare"); future.complete(x * x); }); return future; } CompletableFuture<Integer> fastUnpredictableSquare(Integer x) { boolean canRespond = random.nextBoolean(); CompletableFuture<Integer> future = new CompletableFuture<>(); future.runAsync(() -> { if (canRespond) { System.out.println("fastButUnpredictableSquare"); future.complete(x * x); } else { System.out.println("fastButUnpredictableSquare without answer"); } }); return future; }
  • 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