54
Pragmatic Functional Refactoring with Java 8 Raoul-Gabriel Urma Richard Warburton

Pragmatic functional refactoring with java 8

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

Page 1: Pragmatic functional refactoring with java 8

Pragmatic Functional Refactoring with Java 8Raoul-Gabriel UrmaRichard Warburton

Page 2: Pragmatic functional refactoring with java 8
Page 3: Pragmatic functional refactoring with java 8
Page 4: Pragmatic functional refactoring with java 8

First-class Functions

Currying

Immutability

Optional Data Types

Conclusions

Page 5: Pragmatic functional refactoring with java 8

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;

}

Page 6: Pragmatic functional refactoring with java 8

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;

}

Page 7: Pragmatic functional refactoring with java 8

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;

}

Page 8: Pragmatic functional refactoring with java 8

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;

}

Page 9: Pragmatic functional refactoring with java 8

Step 4a: modeling the filtering criterion

public Predicate<T> {

boolean test(T t);

}

Invoice boolean

Predicate<Invoice>

Page 10: Pragmatic functional refactoring with java 8

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;

}

Page 11: Pragmatic functional refactoring with java 8

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");

}

}

Page 12: Pragmatic functional refactoring with java 8

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

Page 13: Pragmatic functional refactoring with java 8

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")));

Page 14: Pragmatic functional refactoring with java 8

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

Page 15: Pragmatic functional refactoring with java 8

Composing functions

Functions Composing functions

Page 16: Pragmatic functional refactoring with java 8

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

Page 17: Pragmatic functional refactoring with java 8

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

Page 18: Pragmatic functional refactoring with java 8

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");

}

}

Page 19: Pragmatic functional refactoring with java 8

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

Page 20: Pragmatic functional refactoring with java 8

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

Page 21: Pragmatic functional refactoring with java 8

First-class Functions

Currying

Immutability

Optional Data Types

Conclusions

Page 22: Pragmatic functional refactoring with java 8
Page 23: Pragmatic functional refactoring with java 8
Page 24: Pragmatic functional refactoring with java 8

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);

Page 25: Pragmatic functional refactoring with java 8

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);

Page 26: Pragmatic functional refactoring with java 8

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);

Page 27: Pragmatic functional refactoring with java 8

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);

Page 28: Pragmatic functional refactoring with java 8

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);

}

Page 29: Pragmatic functional refactoring with java 8

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.

Page 30: Pragmatic functional refactoring with java 8

First-class Functions

Currying

Immutability

Optional Data Types

Conclusions

Page 31: Pragmatic functional refactoring with java 8

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; }}

Page 32: Pragmatic functional refactoring with java 8

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); }}

Page 33: Pragmatic functional refactoring with java 8

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;

}

Page 34: Pragmatic functional refactoring with java 8
Page 35: Pragmatic functional refactoring with java 8

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);

}

}

Page 36: Pragmatic functional refactoring with java 8

java.lang.StackOverflowError

Page 37: Pragmatic functional refactoring with java 8

Immutable approach

public TrainJourneyImmutable append(TrainJourneyImmutable tj1,

TrainJourneyImmutable tj2) {

return tj1 == null ? tj2

: new TrainJourneyImmutable(tj1.getPrice(),

append(tj1.getOnward(), tj2)

);

}

Page 38: Pragmatic functional refactoring with java 8
Page 39: Pragmatic functional refactoring with java 8

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

Page 40: Pragmatic functional refactoring with java 8

Immutable objects reduce the scope for bugs.

Page 41: Pragmatic functional refactoring with java 8

First-class Functions

Currying

Immutability

Optional Data Types

Conclusions

Page 42: Pragmatic functional refactoring with java 8

Don’t we all love it?

Exception in thread "main" java.lang.NullPointerException

Page 43: Pragmatic functional refactoring with java 8

public String getCarInsuranceName(Person person) {

return person.getCar().getInsurance().getName();

}

Where’s the NPE?

Page 44: Pragmatic functional refactoring with java 8

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";

}

Page 45: Pragmatic functional refactoring with java 8

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

Page 46: Pragmatic functional refactoring with java 8

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; }

}

Page 47: Pragmatic functional refactoring with java 8

Refactoring our example

public String getCarInsuranceName(Person person) {

return Optional.ofNullable(person)

.flatMap(Person::getCar)

.flatMap(Car::getInsurance)

.map(Insurance::getName)

.orElse("No Insurance");

}

Page 48: Pragmatic functional refactoring with java 8

Consistent use of Optional replaces the use of null

Page 49: Pragmatic functional refactoring with java 8

First-class Functions

Currying

Immutability

Optional Data Types

Conclusions

Page 50: Pragmatic functional refactoring with java 8

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

Page 51: Pragmatic functional refactoring with java 8
Page 52: Pragmatic functional refactoring with java 8

Training Website: http://java8training.com

http://manning.com/urma http://tinyurl.com/java8lambdas

Page 53: Pragmatic functional refactoring with java 8

Any Questions?Richard Warburton@richardwarburto

Raoul-Gabriel Urma@raoulUK

Page 54: Pragmatic functional refactoring with java 8

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.