Upload
richardwarburton
View
586
Download
1
Tags:
Embed Size (px)
DESCRIPTION
You may be hearing a lot of buzz around functional programming. For example, Java 8 recently introduced new features (lambda expressions and method references) and APIs (Streams, Optional and CompletableFutures) inspired from functional ideas such as first-class functions, composition and immutability. However, what does this mean for my existing codebase? In this talk we show how you can refactor your traditional object-oriented Java to using FP features and APIs from Java 8 in a beneficial manner. We will discuss: - How to adapt to requirement changes using first-class functions - How you can enhance code reusability using currying - How you can make your code more robust by favouring immutability over mutability - How you can design better APIs and reduce unintended null pointer exceptions using an optional data type"
Citation preview
Pragmatic Functional Refactoring with Java 8Raoul-Gabriel UrmaRichard Warburton
First-class Functions
Currying
Immutability
Optional Data Types
Conclusions
Step 1: filtering invoices from Oracle
public List<Invoice> filterInvoicesFromOracle(List<Invoice> invoices){
List<Invoice> result = new ArrayList<>();
for(Invoice invoice: invoices){
if(invoice.getCustomer() == Customer.ORACLE){
result.add(invoice);
}
}
return result;
}
Step 2a: abstracting the customer
public List<Invoice> filterInvoicesFromCustomer(List<Invoice> invoices,
Customer customer){
List<Invoice> result = new ArrayList<>();
for(Invoice invoice: invoices){
if(invoice.getCustomer() == customer){
result.add(invoice);
}
}
return result;
}
Step 2b: abstracting the name
public List<Invoice> filterInvoicesEndingWith(List<Invoice> invoices,
String suffix){
List<Invoice> result = new ArrayList<>();
for(Invoice invoice: invoices){
if(invoice.getName().endsWith(suffix)){
result.add(invoice);
}
}
return result;
}
Step 3: messy code-reuse!
private List<Invoice> filterInvoicesBad(List<Invoice> invoices,
Customer customer,
String suffix,
boolean flag){
List<Invoice> result = new ArrayList<>();
for(Invoice invoice: invoices){
if((flag && invoice.getCustomer() == customer)
|| (!flag && invoice.getName().endsWith(suffix))){
result.add(invoice);
}
}
return result;
}
Step 4a: modeling the filtering criterion
public Predicate<T> {
boolean test(T t);
}
Invoice boolean
Predicate<Invoice>
Step 4b: using different criterion with objects
private List<Invoice> filterInvoices(List<Invoice> invoices,
Predicate<Invoice> p){
List<Invoice> result = new ArrayList<>();
for(Invoice invoice: invoices){
if(p.test(invoice)){
result.add(invoice);
}
}
return result;
}
Step 4b: using different criterion with objects
List<Invoice> specificInvoices =
filterInvoices(invoices, new FacebookTraining());
private class FacebookTraining implements Predicate<Invoice> {
@Override
public boolean test(Invoice invoice) {
return invoice.getCustomer() == Customer.FACEBOOK
&& invoice.getName().endsWith("Training");
}
}
Step 5: method references
public boolean isOracleInvoice(Invoice invoice){
return invoice.getCustomer() == Customer.ORACLE;
}
public boolean isTrainingInvoice(Invoice invoice){
return invoice.getName().endsWith("Training");
}
List<Invoice> oracleInvoices =
filterInvoices(invoices, this::isOracleInvoice);
List<Invoice> trainingInvoices =
filterInvoices(invoices, this::isTrainingInvoice);
method references
Step 6: lambdas
List<Invoice> oracleInvoices =
filterInvoices(invoices,
(Invoice invoice) -> invoice.getCustomer() == Customer.ORACLE);
List<Invoice> trainingInvoices =
filterInvoices(invoices,
(Invoice invoice) -> invoice.getName().endsWith(("Training")));
First-class functions
● Scary functional-programming terminology for a common object-oriented pattern (strategy, function object…)
● All it means is the ability to use a function (method reference, lambda, object representing a function) just like a regular value○ Pass it as argument to a method○ Store it in a variable
● Lets you cope with requirement changes by representing and passing the new requirement as a function
Composing functions
Functions Composing functions
Composing functions: example
import java.util.function.Predicate;
Predicate<Invoice> isFacebookInvoice = this::isFacebookInvoice;
List<Invoice> facebookAndTraining =
invoices.stream()
.filter( isFacebookInvoice.and(this::isTrainingInvoice))
.collect(toList());
List<Invoice> facebookOrGoogle =
invoices.stream()
.filter( isFacebookInvoice.or(this::isGoogleInvoice))
.collect(toList());
creating more complex functions from building blocks
Composing functions: why does it work?
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
}
returns a new function that is the result of composing two functions
Creating function pipelines (1)
public class Letter {
private final String message;
public Letter(String message) {
this.message = message;
}
public Letter addDefaultHeader(){
return new Letter("From her majesty:\n" + message);
}
public Letter checkSpelling(){
return new Letter(message.replaceAll("FTW", "for the win"));
}
public Letter addDefaultFooter(){
return new Letter(message + "\nKind regards");
}
}
Creating function pipelines (2)
import java.util.function.Function;
Function<Letter, Letter> addHeader = Letter::addDefaultHeader;
Function<Letter, Letter> processingPipeline =
addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addDefaultFooter);
addHeader checkSpelling addFooterandThen andThen
Letter{message="Java 8 FTW!"}
Letter{message='From her majesty:Java 8 for the win!Kind regards'}
composing functions
Creating function pipelines (3)
import java.util.function.Function;
Function<Letter, Letter> addHeader = Letter::addDefaultHeader;
Function<Letter, Letter> processingPipeline =
addHeader.andThen(Letter::addDefaultFooter);
addHeader addFooterandThen
Letter{message="Java 8 FTW!"}
Letter{message='From her majesty:Java 8 FTW!Kind regards'}
composing functions
First-class Functions
Currying
Immutability
Optional Data Types
Conclusions
A conversion function
// (double, double, double) -> double
double convert(double amount, double factor, double base) {
return amount * factor + base;
}
// Usage
double result = convert(10, 1.8, 32);
assertEquals(result, 50, 0.0);
Currying the conversion function
// (double, double) -> (double -> double)
DoubleUnaryOperator convert(double factor, double base) {
return amount -> amount * factor + base;
}
// Partial Application
DoubleUnaryOperator convertCtoF = convert(1.8, 32);
// Usage
double result = convertCtoF.applyAsDouble(10);
assertEquals(result, 50, 0.0);
More partial application
// Temperatures
DoubleUnaryOperator convertCtoF = convert(1.8, 32);
// Currencies
DoubleUnaryOperator convert$to£ = convert(0.6, 0);
// Distance
DoubleUnaryOperator convertKmToMi = convert(0.62137, 0);
A curry function
// int -> (int -> int)
IntFunction<IntUnaryOperator>
curry(IntBinaryOperator biFunction) {
return f -> s -> biFunction.applyAsInt(f, s);
}
// Usage:
IntFunction<IntUnaryOperator> add = curry((f, s) -> f + s);
int result = add.apply(1)
.applyAsInt(2);
assertEquals(3, result);
Generalised curry function
// F -> (S -> R)
<F, S, R> Function<F, Function<S, R>>
curry(BiFunction<F, S, R> biFunction) {
return f -> s -> biFunction.apply(f, s);
}
Summary
● Currying is about splitting up the arguments of a function.
● Partial Application is a function “eating” some of its arguments and returning a new function
● You can write your functions in curried form from the beginning or use a function to curry them.
First-class Functions
Currying
Immutability
Optional Data Types
Conclusions
Mutable objectspublic class TrainJourney {
private int price; private TrainJourney onward;
public TrainJourney(int p, TrainJourney t) { price = p; onward = t; }
public int getPrice() { return price; }
public TrainJourney getOnward() { return onward; }
public void setPrice(int price) { this.price = price; }
public void setOnward(TrainJourney onward) { this.onward = onward; }}
Immutable objectspublic class TrainJourneyImmutable {
private final int price; private final TrainJourneyImmutable onward;
public TrainJourneyImmutable(int p, TrainJourneyImmutable t) { price = p; onward = t; }
public int getPrice() { return price; }
public TrainJourneyImmutable getOnward() { return onward; }
public TrainJourneyImmutable setPrice(int price) { return new TrainJourneyImmutable(price, getOnward()); }
public TrainJourneyImmutable setOnward(TrainJourneyImmutable onward) { return new TrainJourneyImmutable(getPrice(), onward); }}
Mutable approach
public TrainJourney link(TrainJourney tj1, TrainJourney tj2) {
if (tj1 == null) {
return tj2;
}
TrainJourney t = tj1;
while (t.getOnward() != null) {
t = t.getOnward();
}
t.setOnward(tj2);
return tj1;
}
Mutable approach: problem
TrainJourney linked1 = link(tj1, tj2);
TrainJourney linked2 = link(tj1, tj2);
visit(linked2, tj -> { System.out.print(tj.price + " - "); });
static void visit(TrainJourney journey, Consumer<TrainJourney> c) {
if (journey != null) {
c.accept(journey);
visit(journey.onward, c);
}
}
java.lang.StackOverflowError
Immutable approach
public TrainJourneyImmutable append(TrainJourneyImmutable tj1,
TrainJourneyImmutable tj2) {
return tj1 == null ? tj2
: new TrainJourneyImmutable(tj1.getPrice(),
append(tj1.getOnward(), tj2)
);
}
Related topics
● Domain Driven Design○ Value Classes are Immutable
● Core Java Improvements○ New date & time library in Java 8 has many Immutable Objects○ Current Value Types proposal is immutable
● Tooling○ final keyword only bans reassignment○ JSR 308 - improved annotation opportunities○ Mutability Detector○ Findbugs
Immutable objects reduce the scope for bugs.
First-class Functions
Currying
Immutability
Optional Data Types
Conclusions
Don’t we all love it?
Exception in thread "main" java.lang.NullPointerException
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
Where’s the NPE?
Defensive checking
public String getCarInsuranceName(Person person) {
if (person != null) {
Car car = person.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "No Insurance";
}
Optional
● Java 8 introduces a new class java.util.Optional<T>○ a single-value container
● Explicit modelling○ Immediately clear that its an optional value○ better maintainability
● You need to actively unwrap an Optional○ force users to think about the absence case○ fewer errors
Updating model
public class Person {
private Optional<Car> car;
public Optional<Car> getCar() { return car; }
}
public class Car {
private Optional<Insurance> insurance;
public Optional<Insurance> getInsurance() { return insurance; }
}
public class Insurance {
private String name;
public String getName() { return name; }
}
Refactoring our example
public String getCarInsuranceName(Person person) {
return Optional.ofNullable(person)
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("No Insurance");
}
Consistent use of Optional replaces the use of null
First-class Functions
Currying
Immutability
Optional Data Types
Conclusions
Summary of benefits
● First-class functions let you cope for requirement changes
● Currying lets you re-use code logic
● Immutability reduces the scope for bugs
● Optional data types lets you reduce null checking boilerplate and prevent bugs
Training Website: http://java8training.com
http://manning.com/urma http://tinyurl.com/java8lambdas
Any Questions?Richard Warburton@richardwarburto
Raoul-Gabriel Urma@raoulUK
Example currying use cases
● Large factory methods○ Partially apply some of the arguments and then pass this factory
object around.
● Parser Combinators○ many(‘a’) - function which parses a, aa, aaa, etc.