Upload
dhaval-dalal
View
1.307
Download
5
Tags:
Embed Size (px)
Citation preview
A Pure FunctionUses 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 FunctionModifies or interacts with things outside of its scope, and may also return a value.
Outside of its scope
Side-effecting FunctionChanges 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 FunctionGives 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 transparencyString 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 transparencyString 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); // 6c.add(2, 3); // 5 c.add(2, 4); // 6
Referential TransparencyCalculator 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); // 2c.memoryPlus(3); // 5c.memoryPlus(2); // 7c.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 memoryPlusReferential Transparent?
Ref. TransparentmemoryPlus
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.
ReflectionsReferential 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 & FPWe encapsulate data because we think
it will protect us from inadvertent changes and build trust.
Chai Chat: OO & FPWe 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 & FPWe 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 & FPWe 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.
fChai 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 AnonymouslyAn anonymous function - Lambda
Hard to name thingspublic 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
LambdaCompiled 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@FunctionalInterfacepublic interface Function<T, R> { R apply(T t); … }Function<Float, Float> twice = x -> 2 * x;twice.apply(3); // 6
// A function returns its argument unchangedFunction<Float, Float> identity = x -> x@FunctionalInterfacepublic 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
ExistingFunctional 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
@FunctionalInterfacepublic interface Consumer<T> { void accept(T t); …}
@FunctionalInterfacepublic 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 asevent -> System.out.println(event);
A Mother-like Functional Interface
@FunctionalInterfacepublic 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
@FunctionalInterfacepublic 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); // trueisEven.test(3); // false
Functional Interface
Unimplemented Method
Static Method
Default Method
1
0..*
0..*
Default & Static MethodsDefault 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!
CompilationBoom!
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 functionSubsumed Factory Method.
Another Exampleclass 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 Exampleclass 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; } }
//mainTransactions 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; } }
//mainTransactions 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 Strategyclass 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; } }//mainTransactions 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 ProxyMemoization
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 DecoratorFunction<Integer, Integer> expensiveSquare = x -> { System.out.println("Now Squaring..."); try { Thread.sleep(2 * 1000); } catch (Exception e) { } return x * x;};
// Decorate with time and memoizeFunction<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.
Can
this
be
impr
oved
?
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 CodeList<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 CodeString 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 ReadabilityWhat about memory utilisation and performance?
Enter StreamsCollection 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 OperationsIterate 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 StreamDouble 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 mstimer(numbers.parallelStream()); // Time: 2013 ms
Parallel Stream andSide-Effects
String sentence = "all mimsy were the borogoves and the mome raths";
// Prints in the order the stream encounteredsentence.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 encounteredsentence.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);
StatefulEffectively 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 OpsMutable 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 EvaluationJava 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 EvaluationLazy - 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 StreamsStream<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 generateStream<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 LazinessWith 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 InfiniteList<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 SolutionList<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 LoopsList<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 SpliteratorSpliterators, 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 ResultSetclass 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.
ReflectionsFunction 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
ReflectionsComposition 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 WorldSide
EffectingFunctions
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 asx -> y -> (z -> x + y + z);
// is same asx -> (y -> (z -> x + y + z));
CurryingFunction 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 repositoryclass 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!
Reject
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
Accept
Solution 3Making 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 3Making 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 3Making our own curry
Accept
ObservationsWe 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 DIinterface 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); } }//mainApprovalStrategy singleMakerChecker = new SingleMakerChecker();Clearing clearing = new Clearing(singleMakerChecker);clearing.approve(ts);
Curried DIinterface Transaction { }interface ApprovalStrategy { boolean approve(List<Transaction> ts); //…}class Clearing {
public Function<ApprovalStrategy, Boolean> approve(List<Transaction> ts) { return aps -> aps.approve(ts); } }//mainClearing 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());
Refl
ecti
ons
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 DefaultAvoid get() - it throws Exception for absence of value
Instead use orElse, orElseGet, orElseThrowEvent shopping = new Event(null);
System.out.println(shopping.type() .orElse("Other")); // OtherSystem.out.println(shopping.type() .orElseGet(() -> "Other"));// Othershopping.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
@FunctionalInterfaceinterface 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 ArithmeticExceptionTry<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 ExceptionTry<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-effectsAvoid 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 TryFunctionThrowsException<String, Connection, SQLException> getConnection = DriverManager::getConnection;String url = "jdbc:oracle:oci8:scott/tiger@myhost";//Try getting a connection firstTry<Connection> connection = Try.with(getConnection, url);
flatMapping a TryFunctionThrowsException<String, Connection, SQLException> getConnection = DriverManager::getConnection;String url = "jdbc:oracle:oci8:scott/tiger@myhost";//Try getting a connection firstTry<Connection> connection = Try.with(getConnection, url);
FunctionThrowsException<Connection, Statement, SQLException> createStatement = c -> c.createStatement();//Try creating a connection from statementTry<Try<Statement>> statement = connection.map(c -> Try.with(createStatement, c));
flatMapping a TryFunctionThrowsException<String, Connection, SQLException> getConnection = DriverManager::getConnection;String url = "jdbc:oracle:oci8:scott/tiger@myhost";//Try getting a connection firstTry<Connection> connection = Try.with(getConnection, url);
FunctionThrowsException<Connection, Statement, SQLException> createStatement = c -> c.createStatement();//Try creating a connection from statementTry<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 statementTry<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 setTry<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 ResultSetTry<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 TryThe 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 HandlingSo 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 PathTry.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 PropagationTry.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 RecoveryTry<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 TryTry<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 FutureCompletableFuture.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 FutureCompletableFuture<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 Resultget() 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-effectsAvoid 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 FutureUsing 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 cubesquare(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 FuturesUsing 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.
EssenceImmutability 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>
Referenceshttp://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_languagehttp://www.nurkiewicz.com/2013/05/java-8-definitive-guide-to.html
Thank-You!