Arnaud learns Scala - part 2
One of the basics of functional programming: tail recursion – or how to write an iterative code that looks recursive.
Tail-recursive factorial
Scala By Example describes tail recursion and asks the reader to design a tail-recursive version of the factorial function.
That is easy with the help of a nested function which acts as an accumulator:
import scala.annotation.tailrec
def factorial(n: Int): Int = {
@tailrec def fact(f: Int, m: Int): Int = if (m <= 1) f else fact(m * f, m - 1)
fact(n, n - 1)
}
println(factorial(5));
Note: @tailrec
only ensures the nested function is recognized as a tail-recursive function but
it isn’t required.
Performance figures
In order to show the runtime difference let me compare the performance between the simply-recursive version and the tail-recursive version, the code is:
import scala.annotation.tailrec
def factorial1(n: Int): Int = if (n == 0) 1 else n * factorial1(n - 1)
def factorial2(n: Int): Int = {
@tailrec def fact(f: Int, m: Int): Int = if (m <= 1) f else fact(m * f, m - 1)
fact(n, n - 1)
}
def test(fact: (Int) => Int) = {
val t1 = System.currentTimeMillis()
for (x <- 1 to 1000 * 1000 * 1000) {
val b = fact(16)
}
val t2 = System.currentTimeMillis()
println(t2 - t1)
}
if (args.length > 0 && args(0) == "tailrec") {
for (x <- 1 to 10) test(factorial2)
} else {
for (x <- 1 to 10) test(factorial1)
}
The output for factorial1 is:
143978
146334
146322
146454
146180
146301
146540
146427
146097
146343
The output for factorial2 is:
3688
2445
1814
1821
1818
1815
1818
1815
1815
1816
Convinced or need to see the bytecode?
Bytecode of the tail-recursive version
The decompiled java code of the tail-recursive version looks like this:
public int factorial(int n) {
return this.fact$1(n, n - 1);
}
private final int fact$1(int f, int m) {
while (m > 1) {
int n = m * f;
--m;
f = n;
}
return f;
}
That shows the actual effect of the tail-recursive design.