The document discusses functional programming techniques for refactoring an imperative program that calculates the sum of squares of even numbers up to a given maximum. It shows how to decompose the program into pure, testable functions using recursion and higher-order functions. This makes the program more reusable, composable and easier to test compared to the original mutable, statement-based implementation.
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")) {
😈
🤯
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
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
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
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