Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Hitchhiker’s Guide to
Functional Programming
Sergey Shishkin
1
@sshishkin
shishkin
2
The Challenge
Compute the sum of squares of the first even natural
numbers up to 10
2*2 + 4*4 + 6*6 + 8*8 + 10*10 = 220
What if max number should be 1000?
3
The “Real World” Program
4
int sumOfSquaresOfEvenNumbersUpTo(int max) {
int sum = 0;
for (int x = 1; x <= max; x++) {
if (x % 2 == 0)
sum += x * x;
}
return sum;
}



// sumOfSquaresOfEvenNumbersUpTo(10) => 220
Sum
Iterate
Filter
Square
Sum
5
6
for (int i = 0; i < items.length; i++) {
if (!items[i].name.equals("Aged Brie")
&& !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].quality > 0) {
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].quality = items[i].quality - 1;
}
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].sellIn < 11) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
if (items[i].sellIn < 6) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
}
}
}
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].sellIn = items[i].sellIn - 1;
}
if (items[i].sellIn < 0) {
if (!items[i].name.equals("Aged Brie")) {
if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].quality > 0) {
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].quality = items[i].quality - 1;
}
}
} else {
items[i].quality = items[i].quality - items[i].quality;
}
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
}
}
}
}
Source: Gilded Rose Refactoring Kata
7
} else {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
if (items[i].sellIn < 11) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
i++;
}
}
if (items[i].sellIn < 6) {
if (items[i].quality < 50) {
items[i].quality = items[i].quality + 1;
i = items[i].quality;
}
}
}
}
}
if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) {
items[i].sellIn = items[i].sellIn - 1;
}
if (items[i].sellIn < 0) {
if (!items[i].name.equals("Aged Brie")) {
if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) {
😈
🤯
int sumOfSquaresOfEvenNumbersUpTo(int max) {
int sum = 0;
for (int x = 1; x <= max; x++) {
if (x % 2 == 0)
sum += x * x;
}
return sum;
}



8
Statements
Mutation
😱 Violation of Single Responsibility Principle
👻 Mutable Variables
🤯 Statement-based Programming
9
Decomplecting* Functions
* Rich Hickey’s “Simple Made Easy”
10
List<Integer> iterate(int max) {
var result = new ArrayList<Integer>();
for (int x = 1; x <= max; x++) {
result.add(x);
}
return result;
} // iterate(3) => [1,2,3]
List<Integer> filterEven(List<Integer> xs) {
var result = new ArrayList<Integer>();
for (Integer x : xs) {
if (x % 2 == 0) {
result.add(x);
}
}
return result;
} // filterEven([1,2,3]) => [2]
List<Integer> square(List<Integer> xs) {
var result = new ArrayList<Integer>();
for (Integer x : xs) {
result.add(x * x);
}
return result;
} // square([1,2,3]) => [1,4,9]
Integer sum(List<Integer> xs) {
var result = 0;
for (Integer x : xs) {
result += x;
}
return result;
} // sum([1,2,3]) => 6
11
int sumOfSquaresOfEvenNumbersUpTo(int max) {
return sum(square(filterEven(iterate(max))));
}



// sumOfSquareOfEvenNumbersUpTo(10) => 220
12
👍 Expression-based Programming
👌 Composable Functions
👏 Testable in Isolation
💪 Higher Reuse
13
Pure function returns same value for same arguments and
has no side effects
Referentially transparent expression can be replaced with
its resulting value without changing program’s behavior
14
👌 Single Responsibility Principle
🤔 Statement-based Programming
👻 Mutable Variables
15
Recursion
16
17
<A> List<A> prepend(A x, List<A> xs);

// prepend(1, [2,3]) => [1,2,3]
<A> List<A> append(List<A> xs, A x);

// append([2,3], 1) => [2,3,1]
<A> List<A> tail(List<A> xs);
// tail([1,2,3]) => [2,3]

<A> A head(List<A> xs);

// head([1,2,3]) => 1
<A> List<A> concat(List<A> a, List<A> b);

// concat([1,2], [2,3]) => [1,2,2,3]
List<Integer> iterate(int max) {
var result = new ArrayList<Integer>();
for (int x = 1; x <= max; x++) {
result.add(x);
}
return result;
}
List<Integer> iterate(int max) {
if (max == 0) return List.of();
else return append(iterate(max - 1), max);
}
18
List<Integer> filterEven(List<Integer> xs) {
var result = new ArrayList<Integer>();
for (Integer x : xs) {
if (x % 2 == 0) {
result.add(x);
}
}
return result;
}
List<Integer> filterEven(List<Integer> xs) {
if (xs.isEmpty()) return List.of();
else return head(xs) % 2 == 0 ?
prepend(head(xs), filterEven(tail(xs)))

: filterEven(tail(xs));
}
19
List<Integer> square(List<Integer> xs) {
var result = new ArrayList<Integer>();
for (Integer x : xs) {
result.add(x * x);
}
return result;
}
List<Integer> square(List<Integer> xs) {
if (xs.isEmpty()) return List.of();
else return prepend(head(xs) * head(xs),
square(tail(xs)));
}
20
int sum(List<Integer> xs) {
var result = 0;
for (Integer x : xs) {
result += x;
}
return result;
}
int sum(List<Integer> xs) {
if (xs.isEmpty()) return 0;
else return head(xs) + sum(tail(xs));
}
21
Did You See Boilerplate?
22
List<Integer> filterEven(List<Integer> xs) {
if (xs.isEmpty()) return List.of();
else return head(xs) % 2 == 0 ?
prepend(head(xs), filterEven(tail(xs))) :
filterEven(tail(xs));
}
List<Integer> square(List<Integer> xs) {
if (xs.isEmpty()) return List.of();
else return prepend(head(xs) * head(xs),
square(tail(xs)));
}
23
List<Integer> filterEven(List<Integer> xs) {
if (xs.isEmpty()) return List.of();
else return concat(head(xs) % 2 == 0 ?

List.of(head(xs)) : List.of(),
filterEven(tail(xs)));
}
List<Integer> square(List<Integer> xs) {
if (xs.isEmpty()) return List.of();
else return concat(List.of(head(xs) * head(xs)),
square(tail(xs)));
}
24
Higher-Order Functions
Functions that return functions or take functions as arguments
25
List<Integer> flatMap(
List<Integer> xs,
Function<Integer, List<Integer>> f) {
if (xs.isEmpty()) return List.of();
else return concat(f.apply(head(xs)),
flatMap(tail(xs), f));
}
List<Integer> filterEven(List<Integer> xs) {
return flatMap(xs,
x -> x % 2 == 0 ? List.of(x) : List.of());
}
List<Integer> square(List<Integer> xs) {
return flatMap(xs, x -> List.of(x * x));
}
26
Int → List<Int>
Java 8 Lambda
<A, R> R fold(
List<A> xs,
R zero,
BiFunction<R, A, R> combine) {
if (xs.isEmpty()) return zero;
else return combine.apply(
fold(tail(xs), zero, combine),
head(xs));
}
int sum(List<Integer> xs) {
return fold(xs, 0, (r, x) -> r + x);
}
27
(R, A) → R
<A> List<A> flatMap(
List<A> xs,
Function<A, List<A>> f) {
return fold(xs, List.of(),
(acc, x) -> concat(f.apply(x), acc));
}
28
flatMap as fold
Combinators FTW!!1
29
<A> List<A> filter(List<A> xs, Predicate<A> f) {
return flatMap(xs,
x -> f.test(x) ? List.of(x) : List.of());
}
List<Integer> filterEven(List<Integer> xs) {
return filter(xs, x -> x % 2 == 0);
}
30
<A> List<A> map(List<A> xs, Function<A, A> f) {
return flatMap(xs, x -> List.of(f.apply(x)));
}
List<Integer> square(List<Integer> xs) {
return map(xs, x -> x * x);
}
31
Beware Recursion
Recursion is theoretical foundation that enables
referential transparency
Java doesn’t optimize recursive calls
Deep recursion leads to stack overflows
Use combinators and HoF’s instead
32
Stream API
33
int sumOfSquaresOfEvenNumbersUpTo(int max) {
IntPredicate isEven = x -> x % 2 == 0;
IntUnaryOperator square = x -> x * x;
return IntStream.rangeClosed(1, max)
.filter(isEven)
.map(square)
.sum();
}
34
Java 8 Stream API
java.util.stream.Stream<T> and Co.
35
Recap
36
int sum = 0;
for (int x = 1; x <= max; x++)
{
if (x % 2 == 0)
sum += x * x;
}
return sum;
return IntStream
.rangeClosed(1, max)
.filter(isEven)
.map(square)
.sum();
Multiple statements Single expression
Mutable state Pure functions
Entangled code Reusable functions
Harder to test Trivial to test
37
On Performance…
Java compiler doesn’t perform tail
call optimization
Combinators can still use loops to
optimize for performance
Data structures can still use mutable
state to optimize for performance
As long as they stay practically pure
38
On Testing…
Small pure functions are easy to test
No mocks needed
Referential transparency allows for
formal proofs
See Martin Odersky’s Functional Programming
in Scala course
39
40
Sergey Shishkin
@sshishkin
shishkin
40
https://github.com/shishkin/java-fp

More Related Content

Hitchhiker's Guide to Functional Programming

  • 1. Hitchhiker’s Guide to Functional Programming Sergey Shishkin 1 @sshishkin shishkin
  • 2. 2
  • 3. The Challenge Compute the sum of squares of the first even natural numbers up to 10 2*2 + 4*4 + 6*6 + 8*8 + 10*10 = 220 What if max number should be 1000? 3
  • 5. int sumOfSquaresOfEvenNumbersUpTo(int max) { int sum = 0; for (int x = 1; x <= max; x++) { if (x % 2 == 0) sum += x * x; } return sum; }
 
 // sumOfSquaresOfEvenNumbersUpTo(10) => 220 Sum Iterate Filter Square Sum 5
  • 6. 6 for (int i = 0; i < items.length; i++) { if (!items[i].name.equals("Aged Brie") && !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].quality > 0) { if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].quality = items[i].quality - 1; } } } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].sellIn < 11) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; } } if (items[i].sellIn < 6) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; } } } } } if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].sellIn = items[i].sellIn - 1; } if (items[i].sellIn < 0) { if (!items[i].name.equals("Aged Brie")) { if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].quality > 0) { if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].quality = items[i].quality - 1; } } } else { items[i].quality = items[i].quality - items[i].quality; } } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; } } } } Source: Gilded Rose Refactoring Kata
  • 7. 7 } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].sellIn < 11) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; i++; } } if (items[i].sellIn < 6) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; i = items[i].quality; } } } } } if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].sellIn = items[i].sellIn - 1; } if (items[i].sellIn < 0) { if (!items[i].name.equals("Aged Brie")) { if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { 😈 🤯
  • 8. int sumOfSquaresOfEvenNumbersUpTo(int max) { int sum = 0; for (int x = 1; x <= max; x++) { if (x % 2 == 0) sum += x * x; } return sum; }
 
 8 Statements Mutation
  • 9. 😱 Violation of Single Responsibility Principle 👻 Mutable Variables 🤯 Statement-based Programming 9
  • 10. Decomplecting* Functions * Rich Hickey’s “Simple Made Easy” 10
  • 11. List<Integer> iterate(int max) { var result = new ArrayList<Integer>(); for (int x = 1; x <= max; x++) { result.add(x); } return result; } // iterate(3) => [1,2,3] List<Integer> filterEven(List<Integer> xs) { var result = new ArrayList<Integer>(); for (Integer x : xs) { if (x % 2 == 0) { result.add(x); } } return result; } // filterEven([1,2,3]) => [2] List<Integer> square(List<Integer> xs) { var result = new ArrayList<Integer>(); for (Integer x : xs) { result.add(x * x); } return result; } // square([1,2,3]) => [1,4,9] Integer sum(List<Integer> xs) { var result = 0; for (Integer x : xs) { result += x; } return result; } // sum([1,2,3]) => 6 11
  • 12. int sumOfSquaresOfEvenNumbersUpTo(int max) { return sum(square(filterEven(iterate(max)))); }
 
 // sumOfSquareOfEvenNumbersUpTo(10) => 220 12
  • 13. 👍 Expression-based Programming 👌 Composable Functions 👏 Testable in Isolation 💪 Higher Reuse 13
  • 14. Pure function returns same value for same arguments and has no side effects Referentially transparent expression can be replaced with its resulting value without changing program’s behavior 14
  • 15. 👌 Single Responsibility Principle 🤔 Statement-based Programming 👻 Mutable Variables 15
  • 17. 17 <A> List<A> prepend(A x, List<A> xs);
 // prepend(1, [2,3]) => [1,2,3] <A> List<A> append(List<A> xs, A x);
 // append([2,3], 1) => [2,3,1] <A> List<A> tail(List<A> xs); // tail([1,2,3]) => [2,3]
 <A> A head(List<A> xs);
 // head([1,2,3]) => 1 <A> List<A> concat(List<A> a, List<A> b);
 // concat([1,2], [2,3]) => [1,2,2,3]
  • 18. List<Integer> iterate(int max) { var result = new ArrayList<Integer>(); for (int x = 1; x <= max; x++) { result.add(x); } return result; } List<Integer> iterate(int max) { if (max == 0) return List.of(); else return append(iterate(max - 1), max); } 18
  • 19. List<Integer> filterEven(List<Integer> xs) { var result = new ArrayList<Integer>(); for (Integer x : xs) { if (x % 2 == 0) { result.add(x); } } return result; } List<Integer> filterEven(List<Integer> xs) { if (xs.isEmpty()) return List.of(); else return head(xs) % 2 == 0 ? prepend(head(xs), filterEven(tail(xs)))
 : filterEven(tail(xs)); } 19
  • 20. List<Integer> square(List<Integer> xs) { var result = new ArrayList<Integer>(); for (Integer x : xs) { result.add(x * x); } return result; } List<Integer> square(List<Integer> xs) { if (xs.isEmpty()) return List.of(); else return prepend(head(xs) * head(xs), square(tail(xs))); } 20
  • 21. int sum(List<Integer> xs) { var result = 0; for (Integer x : xs) { result += x; } return result; } int sum(List<Integer> xs) { if (xs.isEmpty()) return 0; else return head(xs) + sum(tail(xs)); } 21
  • 22. Did You See Boilerplate? 22
  • 23. List<Integer> filterEven(List<Integer> xs) { if (xs.isEmpty()) return List.of(); else return head(xs) % 2 == 0 ? prepend(head(xs), filterEven(tail(xs))) : filterEven(tail(xs)); } List<Integer> square(List<Integer> xs) { if (xs.isEmpty()) return List.of(); else return prepend(head(xs) * head(xs), square(tail(xs))); } 23
  • 24. List<Integer> filterEven(List<Integer> xs) { if (xs.isEmpty()) return List.of(); else return concat(head(xs) % 2 == 0 ?
 List.of(head(xs)) : List.of(), filterEven(tail(xs))); } List<Integer> square(List<Integer> xs) { if (xs.isEmpty()) return List.of(); else return concat(List.of(head(xs) * head(xs)), square(tail(xs))); } 24
  • 25. Higher-Order Functions Functions that return functions or take functions as arguments 25
  • 26. List<Integer> flatMap( List<Integer> xs, Function<Integer, List<Integer>> f) { if (xs.isEmpty()) return List.of(); else return concat(f.apply(head(xs)), flatMap(tail(xs), f)); } List<Integer> filterEven(List<Integer> xs) { return flatMap(xs, x -> x % 2 == 0 ? List.of(x) : List.of()); } List<Integer> square(List<Integer> xs) { return flatMap(xs, x -> List.of(x * x)); } 26 Int → List<Int> Java 8 Lambda
  • 27. <A, R> R fold( List<A> xs, R zero, BiFunction<R, A, R> combine) { if (xs.isEmpty()) return zero; else return combine.apply( fold(tail(xs), zero, combine), head(xs)); } int sum(List<Integer> xs) { return fold(xs, 0, (r, x) -> r + x); } 27 (R, A) → R
  • 28. <A> List<A> flatMap( List<A> xs, Function<A, List<A>> f) { return fold(xs, List.of(), (acc, x) -> concat(f.apply(x), acc)); } 28 flatMap as fold
  • 30. <A> List<A> filter(List<A> xs, Predicate<A> f) { return flatMap(xs, x -> f.test(x) ? List.of(x) : List.of()); } List<Integer> filterEven(List<Integer> xs) { return filter(xs, x -> x % 2 == 0); } 30
  • 31. <A> List<A> map(List<A> xs, Function<A, A> f) { return flatMap(xs, x -> List.of(f.apply(x))); } List<Integer> square(List<Integer> xs) { return map(xs, x -> x * x); } 31
  • 32. Beware Recursion Recursion is theoretical foundation that enables referential transparency Java doesn’t optimize recursive calls Deep recursion leads to stack overflows Use combinators and HoF’s instead 32
  • 34. int sumOfSquaresOfEvenNumbersUpTo(int max) { IntPredicate isEven = x -> x % 2 == 0; IntUnaryOperator square = x -> x * x; return IntStream.rangeClosed(1, max) .filter(isEven) .map(square) .sum(); } 34
  • 35. Java 8 Stream API java.util.stream.Stream<T> and Co. 35
  • 37. int sum = 0; for (int x = 1; x <= max; x++) { if (x % 2 == 0) sum += x * x; } return sum; return IntStream .rangeClosed(1, max) .filter(isEven) .map(square) .sum(); Multiple statements Single expression Mutable state Pure functions Entangled code Reusable functions Harder to test Trivial to test 37
  • 38. On Performance… Java compiler doesn’t perform tail call optimization Combinators can still use loops to optimize for performance Data structures can still use mutable state to optimize for performance As long as they stay practically pure 38
  • 39. On Testing… Small pure functions are easy to test No mocks needed Referential transparency allows for formal proofs See Martin Odersky’s Functional Programming in Scala course 39