SlideShare a Scribd company logo
1 of 46
Download to read offline
trampolines, monoids 
& other functional 
amenities 
This is NOT your 
father's 
by Mario Fusco 
mario.fusco@gmail.com 
twitter: @mariofusco 
Laziness,
public static <T> void sort(List<T> list, 
Comparator<? super T> c) 
Essence of Functional Programming 
Data and behaviors are the same thing! 
Data 
Behaviors 
Collections.sort(persons, 
(p1, p2) -> p1.getAge() – p2.getAge())
Higher-order functions 
Are they so mind-blowing?
Higher-order functions 
Are they so mind-blowing? 
… but one of the most influent sw engineering 
book is almost completely dedicated to them
Command 
Template Method 
Functions are more general and higher level abstractions 
Factory 
Strategy
public interface Converter { 
double convert(double value); 
} 
public class AbstractConverter implements Converter { 
public double convert(double value) { 
return value * getConversionRate(); 
} 
public abstract double getConversionRate(); 
} 
public class Mi2KmConverter extends AbstractConverter { 
public double getConversionRate() { return 1.609; } 
} 
public class Ou2GrConverter extends AbstractConverter { 
public double getConversionRate() { return 28.345; } 
} 
A strategy pattern Converter
public List<Double> convertValues(List<Double> values, 
Converter converter) { 
List<Double> convertedValues = new ArrayList<Double>(); 
for (double value : values) { 
convertedValues.add(converter.convert(value)); 
} 
return convertedValues; 
} 
List<Double> values = Arrays.asList(10, 20, 50); 
List<Double> convertedDistances = 
convertValues(values, new Mi2KmConverter()); 
List<Double> convertedWeights = 
convertValues(values, new Ou2GrConverter()); 
Using the Converter
A functional Converter 
public class Converter implements 
ExtendedBiFunction<Double, Double, Double> { 
@Override 
public Double apply(Double conversionRate, Double value) { 
return conversionRate * value; 
} 
} 
@FunctionalInterface 
public interface ExtendedBiFunction<T, U, R> extends 
BiFunction<T, U, R> { 
default Function<U, R> curry1(T t) { 
return u -> apply(t, u); 
} 
default Function<T, R> curry2(U u) { 
return t -> apply(t, u); 
} 
}
Currying 
Converter converter = new Converter(); 
double tenMilesInKm = converter.apply(1.609, 10.0); 
Function<Double, Double> mi2kmConverter = converter.curry1(1.609); 
double tenMilesInKm = mi2kmConverter.apply(10.0); 
Converter 
value 
rate 
result 
Mi2km 
Converter 
value 
rate=1.609 
result 
List<Double> values = Stream.of(10, 20, 50) 
.map(new Converter().curry1(1.609)) 
.collect(toList())
Function Composition 
Celsius  Fahrenheit : F = C * 9/5 + 32 
Converter 
value 
rate=9/5
Function Composition 
Celsius  Fahrenheit : F = C * 9/5 + 32 
Converter 
value 
rate=9/5 
andThen 
n -> n+32 
result
Function Composition 
Celsius  Fahrenheit : F = C * 9/5 + 32 
Converter 
value 
rate=9/5 
andThen 
n -> n+32 
result 
Celsius2FarenheitConverter 
Function<Double, Double> c2fConverter = 
new Converter().curry1(9.0/5) 
.andThen(n -> n + 32);
More Function Composition 
@FunctionalInterface 
public interface ExtendedBiFunction<T, U, R> extends 
BiFunction<T, U, R> { 
default <V> ExtendedBiFunction<V, U, R> 
compose1(Function<? super V, ? extends T> before) { 
return (v, u) -> apply(before.apply(v), u); 
} 
default <V> ExtendedBiFunction<T, V, R> 
compose2(Function<? super V, ? extends U> before) { 
return (t, v) -> apply(t, before.apply(v)); 
} 
} 
default <V> Function<V, R> 
compose(Function<? super V, ? extends T> before) { 
return (V v) -> apply(before.apply(v)); 
}
More Function Composition 
Fahrenheit  Celsius : C = (F - 32) * 5/9 
Converter 
rate=5/9 
result
More Function Composition 
Fahrenheit  Celsius : C = (F - 32) * 5/9 
Converter 
rate=5/9 
value 
n -> n-32 
result 
compose2
More Function Composition 
Fahrenheit  Celsius : C = (F - 32) * 5/9 
Converter 
rate=5/9 
value 
n -> n-32 
result 
Farenheit2CelsiusConverter 
Function<Double, Double> f2cConverter = 
new Converter().compose2((Double n) -> n - 32) 
.curry1(5.0/9); 
Functions are building blocks to create other functions 
compose2
Monoids 
A monoid is a triple (T, ∗, z) such that ∗ is an associative binary operation on T, and z ∈ T has the property that for all 
x ∈ T it holds that x∗z = z∗x = x. 
interface Monoid<T> { 
T append(T a, T b); 
T zero(); 
} 
class Appender implements Monoid<String> { 
public String append(String a, String b) { return a + b; } 
public String zero() { return ""; } 
} 
class Multiplier implements Monoid<Integer> { 
public Integer append(Integer a, Integer b) { return a * b; } 
public Integer zero() { return 1; } 
}
Endomorphisms & Monoids 
interface Endomorphism<A> extends Function<A, A> { } 
interface EndoMonoid<A> extends Monoid<Endomorphism<A>> { 
@Override 
default Endomorphism<A> append(Endomorphism<A> f1, 
Endomorphism<A> f2) { 
return ??? 
} 
@Override 
default Endomorphism<A> zero() { 
return ??? 
} 
}
Endomorphisms & Monoids 
interface Endomorphism<A> extends Function<A, A> { } 
interface EndoMonoid<A> extends Monoid<Endomorphism<A>> { 
@Override 
default Endomorphism<A> append(Endomorphism<A> f1, 
Endomorphism<A> f2) { 
return ??? 
} 
@Override 
default Endomorphism<A> zero() { 
return ??? 
} 
} 
f1.andThen(f2); 
Function.identity();
public class SalaryCalculator { 
// B = basic + 20% 
public double plusAllowance(double d) { return d * 1.2; } 
// C = B + 10% 
public double plusBonus(double d) { return d * 1.1; } 
// D = C - 30% 
public double plusTax(double d) { return d * 0.7; } 
// E = D - 10% 
public double plusSurcharge(double d) { return d * 0.9; } 
public double calculate(double basic, boolean[] flags) { 
double salary = basic; 
if (flags[0]) salary = plusAllowance(salary); 
if (flags[1]) salary = plusBonus(salary); 
if (flags[2]) salary = plusTax(salary); 
if (flags[3]) salary = plusSurcharge(salary); 
return salary; 
} 
} 
SalaryCalculator
public class FluentEndoMonoid<A> implements EndoMonoid<A> { 
private final Endomorphism<A> endo; 
public FluentEndoMonoid(Endomorphism<A> endo) { this.endo = endo; } 
public FluentEndoMonoid(Endomorphism<A> endo, boolean b) { 
this.endo = b ? endo : zero(); 
} 
public FluentEndoMonoid<A> add(Endomorphism<A> other) { 
return new FluentEndoMonoid<A>(append(endo, other)); 
} 
public FluentEndoMonoid<A> add(Endomorphism<A> other, boolean b) { 
return add(b ? other : zero()); 
} 
public Endomorphism<A> get() { return endo; } 
public static <A> FluentEndoMonoid<A> endo(Endomorphism<A> f, boolean b) { 
return new FluentEndoMonoid<A>(f, b); 
} 
} 
FluentEndoMonoid
public class SalaryCalculator { 
public double calculate(double basic, boolean [] flags) { 
return getCalculator(bs).apply(basic); 
} 
public Endomorphism<Double> getCalculator(boolean[] flags) { 
return endo(this::plusAllowance, flags[0]) 
.add(this::plusBonus, flags[1]) 
.add(this::plusTax, flags[2]) 
.add(this::plusSurcharge, flags[3]) 
.get(); 
} 
} 
Endomorphism<Double> f = salaryCalc.getCalculator(true, false, false, true); 
double aliceNet = f.apply(alice.getIncome()); 
double brianNet = f.apply(brian.getIncome()); 
Functional SalaryCalculator 
You can calculate a single salary … 
… but also obtain a calculator for a given combination of flags (Factory)
Lazy Evaluation 
Lazy evaluation (or call-by-name) is an evaluation strategy which delays the evaluation of an expression until its value is needed 
I know what to do. Wake me up when you really need it
Creating a Stream of prime numbers 
public static IntStream primes(int n) { 
return IntStream.iterate(2, i -> i + 1) 
.filter(n –> isPrime(n)) 
.limit(n); 
} 
public static boolean isPrime(int candidate) { 
int candidateRoot = (int) Math.sqrt((double) candidate); 
return IntStream.rangeClosed(2, candidateRoot) 
.noneMatch(i -> candidate % i == 0); 
}
Creating a Stream of prime numbers 
public static IntStream primes(int n) { 
return IntStream.iterate(2, i -> i + 1) 
.filter(n –> isPrime(n)) 
.limit(n); 
} 
public static boolean isPrime(int candidate) { 
int candidateRoot = (int) Math.sqrt((double) candidate); 
return IntStream.rangeClosed(2, candidateRoot) 
.noneMatch(i -> candidate % i == 0); 
} 
It iterates through every number every time to see if it can be exactly divided by a candidate number, but it would be enough to only test numbers that have been already classified as prime
Recursively creating a Stream of primes 
static Intstream numbers() { 
return IntStream.iterate(2, n -> n + 1); 
} 
static int head(IntStream numbers) { 
return numbers.findFirst().getAsInt(); 
} 
static IntStream tail(IntStream numbers) { 
return numbers.skip(1); 
} 
static IntStream primes(IntStream numbers) { 
int head = head(numbers); 
return IntStream.concat( 
IntStream.of(head), 
primes(tail(numbers).filter(n -> n % head != 0)) 
); 
}
Recursively creating a Stream of primes 
static Intstream numbers() { 
return IntStream.iterate(2, n -> n + 1); 
} 
static int head(IntStream numbers) { 
return numbers.findFirst().getAsInt(); 
} 
static IntStream tail(IntStream numbers) { 
return numbers.skip(1); 
} 
static IntStream primes(IntStream numbers) { 
int head = head(numbers); 
return IntStream.concat( 
IntStream.of(head), 
primes(tail(numbers).filter(n -> n % head != 0)) 
); 
} 
Cannot invoke 2 terminal operations on the same Streams 
Problems? 
No lazy evaluation in Java leads to an endless recursion
Lazy evaluation in Scala 
def numbers(n: Int): Stream[Int] = n #:: numbers(n+1) 
def primes(numbers: Stream[Int]): Stream[Int] = 
numbers.head #:: 
primes(numbers.tail filter (n -> n % numbers.head != 0)) 
lazy concatenation 
In Scala the #:: method (lazy concatenation) returns immediately and the elements are evaluated only when needed
interface HeadTailList<T> { 
T head(); 
LazyList<T> tail(); 
default boolean isEmpty() { return true; } 
} 
Implementing a lazy list in Java 
class LazyList<T> implements HeadTailList<T> { 
private final T head; 
private final Supplier<MyList<T>> tail; 
public LazyList(T head, 
Supplier<HeadTailList<T>> tail) { 
this.head = head; 
this.tail = tail; 
} 
public T head() { return head; } 
public HeadTailList<T> tail() { return tail.get(); } 
public boolean isEmpty() { return false; } 
}
… and its lazy filter 
public HeadTailList<T> filter(Predicate<T> p) { 
return isEmpty() ? 
this : 
p.test(head()) ? 
new LazyList<>(head(), () -> tail().filter(p)) : 
tail().filter(p); 
} 
2 
3 
4 
5 
6 
7 
8 
9 
2 
3 
5 
7
Back to generating primes 
static HeadTailList<Integer> primes(HeadTailList<Integer> numbers) { 
return new LazyList<>( 
numbers.head(), 
() -> primes(numbers.tail() 
.filter(n -> n % numbers.head() != 0))); 
} 
static LazyList<Integer> from(int n) { 
return new LazyList<Integer>(n, () -> from(n+1)); 
} 
LazyList<Integer> numbers = from(2); 
int two = primes(numbers).head(); 
int three = primes(numbers).tail().head(); 
int five = primes(numbers).tail().tail().head();
Printing primes 
static <T> void printAll(HeadTailList<T> list) { 
while (!list.isEmpty()){ 
System.out.println(list.head()); 
list = list.tail(); 
} 
} 
printAll(primes(from(2))); 
static <T> void printAll(HeadTailList<T> list) { 
if (list.isEmpty()) return; 
System.out.println(list.head()); 
printAll(list.tail()); 
} 
printAll(primes(from(2)));
Iteration vs. Recursion 
External Iteration public int sumAll(int n) { int result = 0; for (int i = 0; i <= n; i++) { result += i; } return result; } 
Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); } 
Internal Iteration public static int sumAll(int n) { return IntStream.rangeClosed(0, n).sum(); }
public class PalindromePredicate implements Predicate<String> { 
@Override public boolean test(String s) { 
return isPalindrome(s, 0, s.length()-1); 
} 
private boolean isPalindrome(String s, int start, int end) { 
while (start < end && !isLetter(s.charAt(start))) start++; 
while (start < end && !isLetter(s.charAt(end))) end--; 
if (start >= end) return true; 
if (toLowerCase(s.charAt(start)) != 
toLowerCase(s.charAt(end))) return false; 
return isPalindrome(s, start+1, end-1); 
} 
} 
Another Recursive Example 
Tail Rescursive Call
What's the problem? 
List<String> sentences = asList( "Dammit, I’m mad!", 
"Rise to vote, sir!", 
"Never odd or even", 
"Never odd and even", 
"Was it a car or a cat I saw?", 
"Was it a car or a dog I saw?", 
VERY_LONG_PALINDROME ); 
sentences.stream() 
.filter(new PalindromePredicate()) 
.forEach(System.out::println); 
Exception in thread "main" java.lang.StackOverflowError at java.lang.Character.getType(Character.java:6924) at java.lang.Character.isLetter(Character.java:5798) at java.lang.Character.isLetter(Character.java:5761) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:17) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) ……..
Tail Call Optimization 
int func_a(int data) { data = do_this(data); return do_that(data); } 
... | executing inside func_a() 
push EIP | push current instruction pointer on stack 
push data | push variable 'data' on the stack 
jmp do_this | call do_this() by jumping to its address 
... | executing inside do_this() 
push EIP | push current instruction pointer on stack 
push data | push variable 'data' on the stack 
jmp do_that | call do_that() by jumping to its address 
... | executing inside do_that() 
pop data | prepare to return value of 'data' 
pop EIP | return to do_this() 
pop data | prepare to return value of 'data' 
pop EIP | return to func_a() 
pop data | prepare to return value of 'data' 
pop EIP | return to func_a() caller 
...
Tail Call Optimization 
int func_a(int data) { 
data = do_this(data); 
return do_that(data); 
} 
... | executing inside func_a() 
push EIP | push current instruction pointer on stack 
push data | push variable 'data' on the stack 
jmp do_this | call do_this() by jumping to its address 
... | executing inside do_this() 
push EIP | push current instruction pointer on stack 
push data | push variable 'data' on the stack 
jmp do_that | call do_that() by jumping to its address 
... | executing inside do_that() 
pop data | prepare to return value of 'data' 
pop EIP | return to do_this() 
pop data | prepare to return value of 'data' 
pop EIP | return to func_a() 
pop data | prepare to return value of 'data' 
pop EIP | return to func_a() caller 
... 
caller 
avoid putting instruction on stack
from Recursion to Tail Recursion 
Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); } 
Tail Recursion public int sumAll(int n) { return sumAll(n, 0); } private int sumAll(int n, int acc) { return n == 0 ? acc : sumAll(n – 1, acc + n); }
Tail Recursion in Scala 
def isPalindrome(s: String): Boolean = isPalindrome(s, 0, s.length-1) 
@tailrec 
def isPalindrome(s: String, start: Int, end: Int): Boolean = { 
val pos1 = nextLetter(s, start, end) 
val pos2 = prevLetter(s, start, end) 
if (pos1 >= pos2) return true 
if (toLowerCase(s.charAt(pos1)) != 
toLowerCase(s.charAt(pos2))) return false 
isPalindrome(s, pos1+1, pos2-1) 
} 
def nextLetter(s: String, start: Int, end: Int): Int = 
if (start > end || isLetter(s.charAt(start))) start 
else nextLetter(s, start+1, end) 
def prevLetter(s: String, start: Int, end: Int): Int = 
if (start > end || isLetter(s.charAt(end))) end 
else prevLetter(s, start, end-1)
Tail Recursion in Java? 
Scala (and many other functional languages) automatically perform tail call optimization at compile time 
@tailrec annotation ensures the compiler will optimize a tail recursive function (i.e. you will get a compilation failure if you use it on a function that is not really tail recursive) 
Java compiler doesn't perform any tail call optimization (and very likely won't do it in a near future) 
How can we overcome this limitation and have StackOverflowError-free functions also in Java tail recursive methods?
Trampolines to the rescue 
A trampoline is an iteration applying a list of functions. Each function returns the next function for the loop to run. 
Func1 
return 
apply 
Func2 
return 
apply 
Func3 
return 
apply 
FuncN 
apply 
… 
result 
return
Implementing the TailCall … 
@FunctionalInterface public interface TailCall<T> { TailCall<T> apply(); default boolean isComplete() { return false; } default T result() { throw new UnsupportedOperationException(); } default T invoke() { return Stream.iterate(this, TailCall::apply) .filter(TailCall::isComplete) .findFirst() .get() .result(); } // ... missing terminal TailCall }
… and the terminal TailCall 
public static <T> TailCall<T> done(final T value) { 
return new TailCall<T>() { 
@Override 
public boolean isComplete() { return true; } 
@Override 
public T result() { return value; } 
@Override 
public TailCall<T> apply() { 
throw new UnsupportedOperationException(); 
} 
}; 
}
Using the Trampoline 
public class PalindromePredicate implements Predicate<String> { 
@Override public boolean test(String s) { 
return isPalindrome(s, 0, s.length()-1).invoke(); 
} 
private TailCall<Boolean> isPalindrome(String s, int start, 
int end) { 
while (start < end && !isLetter(s.charAt(start))) start++; 
while (end > start && !isLetter(s.charAt(end))) end--; 
if (start >= end) return done(true); 
if (toLowerCase(s.charAt(start)) != 
toLowerCase(s.charAt(end))) return done(false); 
int newStart = start + 1; 
int newEnd = end - 1; 
return () -> isPalindrome(s, newStart, newEnd); 
} 
}
Mario Fusco Red Hat – Senior Software Engineer 
mario.fusco@gmail.com twitter: @mariofusco 
Q 
A 
Thanks … Questions?

More Related Content

What's hot

Algorithmes d'approximation
Algorithmes d'approximationAlgorithmes d'approximation
Algorithmes d'approximation
mohamed_SAYARI
 

What's hot (20)

Clean code slide
Clean code slideClean code slide
Clean code slide
 
Algorithmes d'approximation
Algorithmes d'approximationAlgorithmes d'approximation
Algorithmes d'approximation
 
The lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of testsThe lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of tests
 
Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)Functional Programming Patterns (BuildStuff '14)
Functional Programming Patterns (BuildStuff '14)
 
The Power Of Composition (DotNext 2019)
The Power Of Composition (DotNext 2019)The Power Of Composition (DotNext 2019)
The Power Of Composition (DotNext 2019)
 
Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)
 
Design functional solutions in Java, a practical example
Design functional solutions in Java, a practical exampleDesign functional solutions in Java, a practical example
Design functional solutions in Java, a practical example
 
The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)
 
Clean Code: Chapter 3 Function
Clean Code: Chapter 3 FunctionClean Code: Chapter 3 Function
Clean Code: Chapter 3 Function
 
Idiomatic Kotlin
Idiomatic KotlinIdiomatic Kotlin
Idiomatic Kotlin
 
Optional in Java 8
Optional in Java 8Optional in Java 8
Optional in Java 8
 
Clean code
Clean codeClean code
Clean code
 
Java 8 lambda expressions
Java 8 lambda expressionsJava 8 lambda expressions
Java 8 lambda expressions
 
Advanced JavaScript
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScript
 
Clean coding-practices
Clean coding-practicesClean coding-practices
Clean coding-practices
 
Threading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
Threading Made Easy! A Busy Developer’s Guide to Kotlin CoroutinesThreading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
Threading Made Easy! A Busy Developer’s Guide to Kotlin Coroutines
 
Recursiviteeeeeeeeee
RecursiviteeeeeeeeeeRecursiviteeeeeeeeee
Recursiviteeeeeeeeee
 
Why TypeScript?
Why TypeScript?Why TypeScript?
Why TypeScript?
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
 
Solid C++ by Example
Solid C++ by ExampleSolid C++ by Example
Solid C++ by Example
 

Viewers also liked

Scala - where objects and functions meet
Scala - where objects and functions meetScala - where objects and functions meet
Scala - where objects and functions meet
Mario Fusco
 
FP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyondFP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyond
Mario Fusco
 
Why we cannot ignore Functional Programming
Why we cannot ignore Functional ProgrammingWhy we cannot ignore Functional Programming
Why we cannot ignore Functional Programming
Mario Fusco
 
Real world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same languageReal world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same language
Mario Fusco
 
Pratiquer DDD en un éclair (devoxx france 2012)
Pratiquer DDD en un éclair (devoxx france 2012)Pratiquer DDD en un éclair (devoxx france 2012)
Pratiquer DDD en un éclair (devoxx france 2012)
Ulrich VACHON
 
Gr8conf - The Groovy Ecosystem Revisited
Gr8conf - The Groovy Ecosystem RevisitedGr8conf - The Groovy Ecosystem Revisited
Gr8conf - The Groovy Ecosystem Revisited
Andres Almiray
 

Viewers also liked (20)

OOP and FP - Become a Better Programmer
OOP and FP - Become a Better ProgrammerOOP and FP - Become a Better Programmer
OOP and FP - Become a Better Programmer
 
Scala - where objects and functions meet
Scala - where objects and functions meetScala - where objects and functions meet
Scala - where objects and functions meet
 
FP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyondFP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyond
 
Hammurabi
HammurabiHammurabi
Hammurabi
 
Reactive Programming for a demanding world: building event-driven and respons...
Reactive Programming for a demanding world: building event-driven and respons...Reactive Programming for a demanding world: building event-driven and respons...
Reactive Programming for a demanding world: building event-driven and respons...
 
Comparing different concurrency models on the JVM
Comparing different concurrency models on the JVMComparing different concurrency models on the JVM
Comparing different concurrency models on the JVM
 
Drools 6 deep dive
Drools 6 deep diveDrools 6 deep dive
Drools 6 deep dive
 
Why we cannot ignore Functional Programming
Why we cannot ignore Functional ProgrammingWhy we cannot ignore Functional Programming
Why we cannot ignore Functional Programming
 
Real world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same languageReal world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same language
 
Pratiquer DDD en un éclair (devoxx france 2012)
Pratiquer DDD en un éclair (devoxx france 2012)Pratiquer DDD en un éclair (devoxx france 2012)
Pratiquer DDD en un éclair (devoxx france 2012)
 
J'ai mis du DDD et du BDD dans ma marmite.
J'ai mis du DDD et du BDD dans ma marmite.J'ai mis du DDD et du BDD dans ma marmite.
J'ai mis du DDD et du BDD dans ma marmite.
 
Griffon: what's new and what's coming
Griffon: what's new and what's comingGriffon: what's new and what's coming
Griffon: what's new and what's coming
 
Gradle Glam: Plugis Galore
Gradle Glam: Plugis GaloreGradle Glam: Plugis Galore
Gradle Glam: Plugis Galore
 
Making the Most of Your Gradle Build
Making the Most of Your Gradle BuildMaking the Most of Your Gradle Build
Making the Most of Your Gradle Build
 
Gradle Glam: Plugis Strike Back
Gradle Glam: Plugis Strike BackGradle Glam: Plugis Strike Back
Gradle Glam: Plugis Strike Back
 
Greach - The Groovy Ecosystem
Greach - The Groovy EcosystemGreach - The Groovy Ecosystem
Greach - The Groovy Ecosystem
 
Making the Most of Your Gradle Build
Making the Most of Your Gradle BuildMaking the Most of Your Gradle Build
Making the Most of Your Gradle Build
 
Gr8conf - The Groovy Ecosystem Revisited
Gr8conf - The Groovy Ecosystem RevisitedGr8conf - The Groovy Ecosystem Revisited
Gr8conf - The Groovy Ecosystem Revisited
 
Asciidoctor, because documentation does not have to suck
Asciidoctor, because documentation does not have to suckAsciidoctor, because documentation does not have to suck
Asciidoctor, because documentation does not have to suck
 
Machine Learning for Developers
Machine Learning for DevelopersMachine Learning for Developers
Machine Learning for Developers
 

Similar to Laziness, trampolines, monoids and other functional amenities: this is not your father's Java

C++ lectures all chapters in one slide.pptx
C++ lectures all chapters in one slide.pptxC++ lectures all chapters in one slide.pptx
C++ lectures all chapters in one slide.pptx
ssuser3cbb4c
 
Cs1123 8 functions
Cs1123 8 functionsCs1123 8 functions
Cs1123 8 functions
TAlha MAlik
 

Similar to Laziness, trampolines, monoids and other functional amenities: this is not your father's Java (20)

Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
 
functions
functionsfunctions
functions
 
Advanced C - Part 2
Advanced C - Part 2Advanced C - Part 2
Advanced C - Part 2
 
UNIT3.pptx
UNIT3.pptxUNIT3.pptx
UNIT3.pptx
 
function_v1.ppt
function_v1.pptfunction_v1.ppt
function_v1.ppt
 
function_v1.ppt
function_v1.pptfunction_v1.ppt
function_v1.ppt
 
From object oriented to functional domain modeling
From object oriented to functional domain modelingFrom object oriented to functional domain modeling
From object oriented to functional domain modeling
 
L25-L26-Parameter passing techniques.pptx
L25-L26-Parameter passing techniques.pptxL25-L26-Parameter passing techniques.pptx
L25-L26-Parameter passing techniques.pptx
 
Array Cont
Array ContArray Cont
Array Cont
 
Embedded C - Day 2
Embedded C - Day 2Embedded C - Day 2
Embedded C - Day 2
 
6. function
6. function6. function
6. function
 
12
1212
12
 
C++ lectures all chapters in one slide.pptx
C++ lectures all chapters in one slide.pptxC++ lectures all chapters in one slide.pptx
C++ lectures all chapters in one slide.pptx
 
Functions
FunctionsFunctions
Functions
 
Let Us Learn Lambda Using C# 3.0
Let Us Learn Lambda Using C# 3.0Let Us Learn Lambda Using C# 3.0
Let Us Learn Lambda Using C# 3.0
 
Chapter 7 functions (c)
Chapter 7 functions (c)Chapter 7 functions (c)
Chapter 7 functions (c)
 
Cs1123 8 functions
Cs1123 8 functionsCs1123 8 functions
Cs1123 8 functions
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
 
Unit 4 (1)
Unit 4 (1)Unit 4 (1)
Unit 4 (1)
 
functions
functionsfunctions
functions
 

More from Mario Fusco (8)

Kogito: cloud native business automation
Kogito: cloud native business automationKogito: cloud native business automation
Kogito: cloud native business automation
 
How and why I turned my old Java projects into a first-class serverless compo...
How and why I turned my old Java projects into a first-class serverless compo...How and why I turned my old Java projects into a first-class serverless compo...
How and why I turned my old Java projects into a first-class serverless compo...
 
OOP and FP
OOP and FPOOP and FP
OOP and FP
 
Introducing Drools
Introducing DroolsIntroducing Drools
Introducing Drools
 
Java 7, 8 & 9 - Moving the language forward
Java 7, 8 & 9 - Moving the language forwardJava 7, 8 & 9 - Moving the language forward
Java 7, 8 & 9 - Moving the language forward
 
Swiss army knife Spring
Swiss army knife SpringSwiss army knife Spring
Swiss army knife Spring
 
No more loops with lambdaj
No more loops with lambdajNo more loops with lambdaj
No more loops with lambdaj
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
 

Recently uploaded

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
Enterprise Knowledge
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 

Recently uploaded (20)

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Evaluating the top large language models.pdf
Evaluating the top large language models.pdfEvaluating the top large language models.pdf
Evaluating the top large language models.pdf
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 

Laziness, trampolines, monoids and other functional amenities: this is not your father's Java

  • 1. trampolines, monoids & other functional amenities This is NOT your father's by Mario Fusco mario.fusco@gmail.com twitter: @mariofusco Laziness,
  • 2. public static <T> void sort(List<T> list, Comparator<? super T> c) Essence of Functional Programming Data and behaviors are the same thing! Data Behaviors Collections.sort(persons, (p1, p2) -> p1.getAge() – p2.getAge())
  • 3. Higher-order functions Are they so mind-blowing?
  • 4. Higher-order functions Are they so mind-blowing? … but one of the most influent sw engineering book is almost completely dedicated to them
  • 5. Command Template Method Functions are more general and higher level abstractions Factory Strategy
  • 6. public interface Converter { double convert(double value); } public class AbstractConverter implements Converter { public double convert(double value) { return value * getConversionRate(); } public abstract double getConversionRate(); } public class Mi2KmConverter extends AbstractConverter { public double getConversionRate() { return 1.609; } } public class Ou2GrConverter extends AbstractConverter { public double getConversionRate() { return 28.345; } } A strategy pattern Converter
  • 7. public List<Double> convertValues(List<Double> values, Converter converter) { List<Double> convertedValues = new ArrayList<Double>(); for (double value : values) { convertedValues.add(converter.convert(value)); } return convertedValues; } List<Double> values = Arrays.asList(10, 20, 50); List<Double> convertedDistances = convertValues(values, new Mi2KmConverter()); List<Double> convertedWeights = convertValues(values, new Ou2GrConverter()); Using the Converter
  • 8. A functional Converter public class Converter implements ExtendedBiFunction<Double, Double, Double> { @Override public Double apply(Double conversionRate, Double value) { return conversionRate * value; } } @FunctionalInterface public interface ExtendedBiFunction<T, U, R> extends BiFunction<T, U, R> { default Function<U, R> curry1(T t) { return u -> apply(t, u); } default Function<T, R> curry2(U u) { return t -> apply(t, u); } }
  • 9. Currying Converter converter = new Converter(); double tenMilesInKm = converter.apply(1.609, 10.0); Function<Double, Double> mi2kmConverter = converter.curry1(1.609); double tenMilesInKm = mi2kmConverter.apply(10.0); Converter value rate result Mi2km Converter value rate=1.609 result List<Double> values = Stream.of(10, 20, 50) .map(new Converter().curry1(1.609)) .collect(toList())
  • 10. Function Composition Celsius  Fahrenheit : F = C * 9/5 + 32 Converter value rate=9/5
  • 11. Function Composition Celsius  Fahrenheit : F = C * 9/5 + 32 Converter value rate=9/5 andThen n -> n+32 result
  • 12. Function Composition Celsius  Fahrenheit : F = C * 9/5 + 32 Converter value rate=9/5 andThen n -> n+32 result Celsius2FarenheitConverter Function<Double, Double> c2fConverter = new Converter().curry1(9.0/5) .andThen(n -> n + 32);
  • 13. More Function Composition @FunctionalInterface public interface ExtendedBiFunction<T, U, R> extends BiFunction<T, U, R> { default <V> ExtendedBiFunction<V, U, R> compose1(Function<? super V, ? extends T> before) { return (v, u) -> apply(before.apply(v), u); } default <V> ExtendedBiFunction<T, V, R> compose2(Function<? super V, ? extends U> before) { return (t, v) -> apply(t, before.apply(v)); } } default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { return (V v) -> apply(before.apply(v)); }
  • 14. More Function Composition Fahrenheit  Celsius : C = (F - 32) * 5/9 Converter rate=5/9 result
  • 15. More Function Composition Fahrenheit  Celsius : C = (F - 32) * 5/9 Converter rate=5/9 value n -> n-32 result compose2
  • 16. More Function Composition Fahrenheit  Celsius : C = (F - 32) * 5/9 Converter rate=5/9 value n -> n-32 result Farenheit2CelsiusConverter Function<Double, Double> f2cConverter = new Converter().compose2((Double n) -> n - 32) .curry1(5.0/9); Functions are building blocks to create other functions compose2
  • 17. Monoids A monoid is a triple (T, ∗, z) such that ∗ is an associative binary operation on T, and z ∈ T has the property that for all x ∈ T it holds that x∗z = z∗x = x. interface Monoid<T> { T append(T a, T b); T zero(); } class Appender implements Monoid<String> { public String append(String a, String b) { return a + b; } public String zero() { return ""; } } class Multiplier implements Monoid<Integer> { public Integer append(Integer a, Integer b) { return a * b; } public Integer zero() { return 1; } }
  • 18. Endomorphisms & Monoids interface Endomorphism<A> extends Function<A, A> { } interface EndoMonoid<A> extends Monoid<Endomorphism<A>> { @Override default Endomorphism<A> append(Endomorphism<A> f1, Endomorphism<A> f2) { return ??? } @Override default Endomorphism<A> zero() { return ??? } }
  • 19. Endomorphisms & Monoids interface Endomorphism<A> extends Function<A, A> { } interface EndoMonoid<A> extends Monoid<Endomorphism<A>> { @Override default Endomorphism<A> append(Endomorphism<A> f1, Endomorphism<A> f2) { return ??? } @Override default Endomorphism<A> zero() { return ??? } } f1.andThen(f2); Function.identity();
  • 20. public class SalaryCalculator { // B = basic + 20% public double plusAllowance(double d) { return d * 1.2; } // C = B + 10% public double plusBonus(double d) { return d * 1.1; } // D = C - 30% public double plusTax(double d) { return d * 0.7; } // E = D - 10% public double plusSurcharge(double d) { return d * 0.9; } public double calculate(double basic, boolean[] flags) { double salary = basic; if (flags[0]) salary = plusAllowance(salary); if (flags[1]) salary = plusBonus(salary); if (flags[2]) salary = plusTax(salary); if (flags[3]) salary = plusSurcharge(salary); return salary; } } SalaryCalculator
  • 21. public class FluentEndoMonoid<A> implements EndoMonoid<A> { private final Endomorphism<A> endo; public FluentEndoMonoid(Endomorphism<A> endo) { this.endo = endo; } public FluentEndoMonoid(Endomorphism<A> endo, boolean b) { this.endo = b ? endo : zero(); } public FluentEndoMonoid<A> add(Endomorphism<A> other) { return new FluentEndoMonoid<A>(append(endo, other)); } public FluentEndoMonoid<A> add(Endomorphism<A> other, boolean b) { return add(b ? other : zero()); } public Endomorphism<A> get() { return endo; } public static <A> FluentEndoMonoid<A> endo(Endomorphism<A> f, boolean b) { return new FluentEndoMonoid<A>(f, b); } } FluentEndoMonoid
  • 22. public class SalaryCalculator { public double calculate(double basic, boolean [] flags) { return getCalculator(bs).apply(basic); } public Endomorphism<Double> getCalculator(boolean[] flags) { return endo(this::plusAllowance, flags[0]) .add(this::plusBonus, flags[1]) .add(this::plusTax, flags[2]) .add(this::plusSurcharge, flags[3]) .get(); } } Endomorphism<Double> f = salaryCalc.getCalculator(true, false, false, true); double aliceNet = f.apply(alice.getIncome()); double brianNet = f.apply(brian.getIncome()); Functional SalaryCalculator You can calculate a single salary … … but also obtain a calculator for a given combination of flags (Factory)
  • 23. Lazy Evaluation Lazy evaluation (or call-by-name) is an evaluation strategy which delays the evaluation of an expression until its value is needed I know what to do. Wake me up when you really need it
  • 24. Creating a Stream of prime numbers public static IntStream primes(int n) { return IntStream.iterate(2, i -> i + 1) .filter(n –> isPrime(n)) .limit(n); } public static boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot) .noneMatch(i -> candidate % i == 0); }
  • 25. Creating a Stream of prime numbers public static IntStream primes(int n) { return IntStream.iterate(2, i -> i + 1) .filter(n –> isPrime(n)) .limit(n); } public static boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot) .noneMatch(i -> candidate % i == 0); } It iterates through every number every time to see if it can be exactly divided by a candidate number, but it would be enough to only test numbers that have been already classified as prime
  • 26. Recursively creating a Stream of primes static Intstream numbers() { return IntStream.iterate(2, n -> n + 1); } static int head(IntStream numbers) { return numbers.findFirst().getAsInt(); } static IntStream tail(IntStream numbers) { return numbers.skip(1); } static IntStream primes(IntStream numbers) { int head = head(numbers); return IntStream.concat( IntStream.of(head), primes(tail(numbers).filter(n -> n % head != 0)) ); }
  • 27. Recursively creating a Stream of primes static Intstream numbers() { return IntStream.iterate(2, n -> n + 1); } static int head(IntStream numbers) { return numbers.findFirst().getAsInt(); } static IntStream tail(IntStream numbers) { return numbers.skip(1); } static IntStream primes(IntStream numbers) { int head = head(numbers); return IntStream.concat( IntStream.of(head), primes(tail(numbers).filter(n -> n % head != 0)) ); } Cannot invoke 2 terminal operations on the same Streams Problems? No lazy evaluation in Java leads to an endless recursion
  • 28. Lazy evaluation in Scala def numbers(n: Int): Stream[Int] = n #:: numbers(n+1) def primes(numbers: Stream[Int]): Stream[Int] = numbers.head #:: primes(numbers.tail filter (n -> n % numbers.head != 0)) lazy concatenation In Scala the #:: method (lazy concatenation) returns immediately and the elements are evaluated only when needed
  • 29. interface HeadTailList<T> { T head(); LazyList<T> tail(); default boolean isEmpty() { return true; } } Implementing a lazy list in Java class LazyList<T> implements HeadTailList<T> { private final T head; private final Supplier<MyList<T>> tail; public LazyList(T head, Supplier<HeadTailList<T>> tail) { this.head = head; this.tail = tail; } public T head() { return head; } public HeadTailList<T> tail() { return tail.get(); } public boolean isEmpty() { return false; } }
  • 30. … and its lazy filter public HeadTailList<T> filter(Predicate<T> p) { return isEmpty() ? this : p.test(head()) ? new LazyList<>(head(), () -> tail().filter(p)) : tail().filter(p); } 2 3 4 5 6 7 8 9 2 3 5 7
  • 31. Back to generating primes static HeadTailList<Integer> primes(HeadTailList<Integer> numbers) { return new LazyList<>( numbers.head(), () -> primes(numbers.tail() .filter(n -> n % numbers.head() != 0))); } static LazyList<Integer> from(int n) { return new LazyList<Integer>(n, () -> from(n+1)); } LazyList<Integer> numbers = from(2); int two = primes(numbers).head(); int three = primes(numbers).tail().head(); int five = primes(numbers).tail().tail().head();
  • 32. Printing primes static <T> void printAll(HeadTailList<T> list) { while (!list.isEmpty()){ System.out.println(list.head()); list = list.tail(); } } printAll(primes(from(2))); static <T> void printAll(HeadTailList<T> list) { if (list.isEmpty()) return; System.out.println(list.head()); printAll(list.tail()); } printAll(primes(from(2)));
  • 33. Iteration vs. Recursion External Iteration public int sumAll(int n) { int result = 0; for (int i = 0; i <= n; i++) { result += i; } return result; } Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); } Internal Iteration public static int sumAll(int n) { return IntStream.rangeClosed(0, n).sum(); }
  • 34. public class PalindromePredicate implements Predicate<String> { @Override public boolean test(String s) { return isPalindrome(s, 0, s.length()-1); } private boolean isPalindrome(String s, int start, int end) { while (start < end && !isLetter(s.charAt(start))) start++; while (start < end && !isLetter(s.charAt(end))) end--; if (start >= end) return true; if (toLowerCase(s.charAt(start)) != toLowerCase(s.charAt(end))) return false; return isPalindrome(s, start+1, end-1); } } Another Recursive Example Tail Rescursive Call
  • 35. What's the problem? List<String> sentences = asList( "Dammit, I’m mad!", "Rise to vote, sir!", "Never odd or even", "Never odd and even", "Was it a car or a cat I saw?", "Was it a car or a dog I saw?", VERY_LONG_PALINDROME ); sentences.stream() .filter(new PalindromePredicate()) .forEach(System.out::println); Exception in thread "main" java.lang.StackOverflowError at java.lang.Character.getType(Character.java:6924) at java.lang.Character.isLetter(Character.java:5798) at java.lang.Character.isLetter(Character.java:5761) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:17) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) ……..
  • 36. Tail Call Optimization int func_a(int data) { data = do_this(data); return do_that(data); } ... | executing inside func_a() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_this | call do_this() by jumping to its address ... | executing inside do_this() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_that | call do_that() by jumping to its address ... | executing inside do_that() pop data | prepare to return value of 'data' pop EIP | return to do_this() pop data | prepare to return value of 'data' pop EIP | return to func_a() pop data | prepare to return value of 'data' pop EIP | return to func_a() caller ...
  • 37. Tail Call Optimization int func_a(int data) { data = do_this(data); return do_that(data); } ... | executing inside func_a() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_this | call do_this() by jumping to its address ... | executing inside do_this() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_that | call do_that() by jumping to its address ... | executing inside do_that() pop data | prepare to return value of 'data' pop EIP | return to do_this() pop data | prepare to return value of 'data' pop EIP | return to func_a() pop data | prepare to return value of 'data' pop EIP | return to func_a() caller ... caller avoid putting instruction on stack
  • 38. from Recursion to Tail Recursion Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); } Tail Recursion public int sumAll(int n) { return sumAll(n, 0); } private int sumAll(int n, int acc) { return n == 0 ? acc : sumAll(n – 1, acc + n); }
  • 39. Tail Recursion in Scala def isPalindrome(s: String): Boolean = isPalindrome(s, 0, s.length-1) @tailrec def isPalindrome(s: String, start: Int, end: Int): Boolean = { val pos1 = nextLetter(s, start, end) val pos2 = prevLetter(s, start, end) if (pos1 >= pos2) return true if (toLowerCase(s.charAt(pos1)) != toLowerCase(s.charAt(pos2))) return false isPalindrome(s, pos1+1, pos2-1) } def nextLetter(s: String, start: Int, end: Int): Int = if (start > end || isLetter(s.charAt(start))) start else nextLetter(s, start+1, end) def prevLetter(s: String, start: Int, end: Int): Int = if (start > end || isLetter(s.charAt(end))) end else prevLetter(s, start, end-1)
  • 40. Tail Recursion in Java? Scala (and many other functional languages) automatically perform tail call optimization at compile time @tailrec annotation ensures the compiler will optimize a tail recursive function (i.e. you will get a compilation failure if you use it on a function that is not really tail recursive) Java compiler doesn't perform any tail call optimization (and very likely won't do it in a near future) How can we overcome this limitation and have StackOverflowError-free functions also in Java tail recursive methods?
  • 41. Trampolines to the rescue A trampoline is an iteration applying a list of functions. Each function returns the next function for the loop to run. Func1 return apply Func2 return apply Func3 return apply FuncN apply … result return
  • 42. Implementing the TailCall … @FunctionalInterface public interface TailCall<T> { TailCall<T> apply(); default boolean isComplete() { return false; } default T result() { throw new UnsupportedOperationException(); } default T invoke() { return Stream.iterate(this, TailCall::apply) .filter(TailCall::isComplete) .findFirst() .get() .result(); } // ... missing terminal TailCall }
  • 43. … and the terminal TailCall public static <T> TailCall<T> done(final T value) { return new TailCall<T>() { @Override public boolean isComplete() { return true; } @Override public T result() { return value; } @Override public TailCall<T> apply() { throw new UnsupportedOperationException(); } }; }
  • 44. Using the Trampoline public class PalindromePredicate implements Predicate<String> { @Override public boolean test(String s) { return isPalindrome(s, 0, s.length()-1).invoke(); } private TailCall<Boolean> isPalindrome(String s, int start, int end) { while (start < end && !isLetter(s.charAt(start))) start++; while (end > start && !isLetter(s.charAt(end))) end--; if (start >= end) return done(true); if (toLowerCase(s.charAt(start)) != toLowerCase(s.charAt(end))) return done(false); int newStart = start + 1; int newEnd = end - 1; return () -> isPalindrome(s, newStart, newEnd); } }
  • 45.
  • 46. Mario Fusco Red Hat – Senior Software Engineer mario.fusco@gmail.com twitter: @mariofusco Q A Thanks … Questions?