Slide 1

Slide 1 text

trampolines, monoids & other functional amenities This is NOT your father's by Mario Fusco mario.fusco@gmail.com twitter: @mariofusco Laziness,

Slide 2

Slide 2 text

public static void sort(List 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())

Slide 3

Slide 3 text

Higher-order functions Are they so mind-blowing?

Slide 4

Slide 4 text

Higher-order functions Are they so mind-blowing? … but one of the most influent sw engineering book is almost completely dedicated to them

Slide 5

Slide 5 text

Command Template Method Functions are more general and higher level abstractions Factory Strategy

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

public List convertValues(List values, Converter converter) { List convertedValues = new ArrayList(); for (double value : values) { convertedValues.add(converter.convert(value)); } return convertedValues; } List values = Arrays.asList(10, 20, 50); List convertedDistances = convertValues(values, new Mi2KmConverter()); List convertedWeights = convertValues(values, new Ou2GrConverter()); Using the Converter

Slide 8

Slide 8 text

A functional Converter public class Converter implements ExtendedBiFunction { @Override public Double apply(Double conversionRate, Double value) { return conversionRate * value; } } @FunctionalInterface public interface ExtendedBiFunction extends BiFunction { default Function curry1(T t) { return u -> apply(t, u); } default Function curry2(U u) { return t -> apply(t, u); } }

Slide 9

Slide 9 text

Currying Converter converter = new Converter(); double tenMilesInKm = converter.apply(1.609, 10.0); Function mi2kmConverter = converter.curry1(1.609); double tenMilesInKm = mi2kmConverter.apply(10.0); Converter value rate result Mi2km Converter value rate=1.609 result curry1 List values = Stream.of(10, 20, 50) .map(new Converter().curry1(1.609)) .collect(toList())

Slide 10

Slide 10 text

Function Composition Celsius  Fahrenheit : F = C * 9/5 + 32 Converter value rate=9/5

Slide 11

Slide 11 text

Function Composition Celsius  Fahrenheit : F = C * 9/5 + 32 Converter value rate=9/5 andThen n -> n+32 result

Slide 12

Slide 12 text

Function Composition Celsius  Fahrenheit : F = C * 9/5 + 32 Converter value rate=9/5 andThen n -> n+32 result Celsius2FarenheitConverter Function c2fConverter = new Converter().curry1(9.0/5) .andThen(n -> n + 32);

Slide 13

Slide 13 text

More Function Composition @FunctionalInterface public interface ExtendedBiFunction extends BiFunction { default ExtendedBiFunction compose1(Function super V, ? extends T> before) { return (v, u) -> apply(before.apply(v), u); } default ExtendedBiFunction compose2(Function super V, ? extends U> before) { return (t, v) -> apply(t, before.apply(v)); } } default Function compose(Function super V, ? extends T> before) { return (V v) -> apply(before.apply(v)); }

Slide 14

Slide 14 text

More Function Composition Fahrenheit  Celsius : C = (F - 32) * 5/9 Converter rate=5/9 result

Slide 15

Slide 15 text

More Function Composition Fahrenheit  Celsius : C = (F - 32) * 5/9 Converter rate=5/9 value n -> n-32 result compose2

Slide 16

Slide 16 text

More Function Composition Fahrenheit  Celsius : C = (F - 32) * 5/9 Converter rate=5/9 value n -> n-32 result Farenheit2CelsiusConverter Function f2cConverter = new Converter().compose2((Double n) -> n - 32) .curry1(5.0/9); Functions are building blocks to create other functions compose2

Slide 17

Slide 17 text

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 append(T a, T b); T zero(); } class Appender implements Monoid { public String append(String a, String b) { return a + b; } public String zero() { return ""; } } class Multiplier implements Monoid { public Integer append(Integer a, Integer b) { return a * b; } public Integer zero() { return 1; } }

Slide 20

Slide 20 text

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

Slide 22

Slide 22 text

public class SalaryCalculator { public double calculate(double basic, boolean [] flags) { return getCalculator(bs).apply(basic); } public Endomorphism 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 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)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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 Inefficient

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

interface HeadTailList { T head(); LazyList tail(); default boolean isEmpty() { return true; } } Implementing a lazy list in Java class LazyList implements HeadTailList { private final T head; private final Supplier> tail; public LazyList(T head, Supplier> tail) { this.head = head; this.tail = tail; } public T head() { return head; } public HeadTailList tail() { return tail.get(); } public boolean isEmpty() { return false; } }

Slide 30

Slide 30 text

… and its lazy filter public HeadTailList filter(Predicate 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

Slide 31

Slide 31 text

Back to generating primes static HeadTailList primes(HeadTailList numbers) { return new LazyList<>( numbers.head(), () -> primes(numbers.tail() .filter(n -> n % numbers.head() != 0))); } static LazyList from(int n) { return new LazyList(n, () -> from(n+1)); } LazyList numbers = from(2); int two = primes(numbers).head(); int three = primes(numbers).tail().head(); int five = primes(numbers).tail().tail().head();

Slide 32

Slide 32 text

Printing primes static void printAll(HeadTailList list) { while (!list.isEmpty()){ System.out.println(list.head()); list = list.tail(); } } printAll(primes(from(2))); static void printAll(HeadTailList list) { if (list.isEmpty()) return; System.out.println(list.head()); printAll(list.tail()); } printAll(primes(from(2))); iteratively recursively

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

public class PalindromePredicate implements Predicate { @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

Slide 35

Slide 35 text

What's the problem? List 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) ……..

Slide 36

Slide 36 text

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 ...

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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)

Slide 40

Slide 40 text

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?

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Implementing the TailCall … @FunctionalInterface public interface TailCall { TailCall 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 }

Slide 43

Slide 43 text

… and the terminal TailCall public static TailCall done(final T value) { return new TailCall() { @Override public boolean isComplete() { return true; } @Override public T result() { return value; } @Override public TailCall apply() { throw new UnsupportedOperationException(); } }; }

Slide 44

Slide 44 text

Using the Trampoline public class PalindromePredicate implements Predicate { @Override public boolean test(String s) { return isPalindrome(s, 0, s.length()-1).invoke(); } private TailCall 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); } }

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Mario Fusco Red Hat – Senior Software Engineer mario.fusco@gmail.com twitter: @mariofusco Q A Thanks … Questions?