The document discusses pragmatic functional refactoring in Java 8, covering concepts such as first-class functions, currying, immutability, and optional data types. It provides step-by-step examples for filtering invoices and creating function pipelines while emphasizing benefits like reduced bugs and improved code maintainability. Additionally, it highlights using Java's Optional class to handle potential null values more gracefully.
Introduction to Pragmatic Functional Refactoring in Java 8; Key concepts include first-class functions, currying, immutability, and optional data types.
Introduction to Pragmatic Functional Refactoring in Java 8; Key concepts include first-class functions, currying, immutability, and optional data types.
Steps for filtering invoices: initial filtering from Oracle, customer abstraction, and using method references with predicates.
Introduction to lambda expressions; allows functions to be treated as first-class values in code.
Concept of composing functions using predicates and chaining filters for more complex operations.
Building and executing pipelines of transformations on methods using Java functions.
Currying allows splitting function arguments; partial application creates new functions with some pre-defined arguments.
Comparison of mutable and immutable objects; immutability reduces bugs and is seen in Java 8 improvements.
Handling NullPointerExceptions through defensive checks; introducing Java 8's Optional type to manage optional values.
Summary of benefits: first-class functions, currying, immutability, and optionals reduce bugs and improve code.
Training resources mentioned; opportunity for audience questions to clarify concepts presented.
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: 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()
creating more complex
functions from building blocks
.filter( isFacebookInvoice.or(this::isGoogleInvoice))
.collect(toList());
17.
Composing functions: whydoes 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
18.
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");
}
}
19.
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);
andThen andThen
composing
functions
addHeader checkSpelling addFooter
Letter
{message="Java
8 FTW!"}
Letter{message='From
her majesty:
Java 8 for the win!
Kind regards'}
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);
28.
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);
}
29.
Summary
● Curryingis 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.
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
Defensive checking
publicString 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";
}
45.
Optional
● Java8 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
46.
Updating model
publicclass 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; }
}
47.
Refactoring our example
public String getCarInsuranceName(Person person) {
return Optional.ofNullable(person)
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("No Insurance");
}
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
Example currying usecases
● 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.