Groovy Functional Programming - Tail Call Optimization

Let us create a function that computes the factorial of a number using recursion.

def factorial(def number) {
    if(number == 0){
        1
    }
    else{
        number * factorial(number - 1)
    }
}

println factorial(1) // 1
println factorial(3) // 6
println factorial(10) // 3628800

What would happen if we invoke factorial function with 17 as argument?
The program prints -288522240 as factorial of 17, which is incorrect.

What caused this incorrect result?
The program assumes Integer type for the parameter number. Sine the value overflows, we get a negative result.

We could try using Long, but that will not take us far from here. Instead BigInteger is the recommended choice as it will not cause value overflow.

What would be factorial(10000)?
Now we hit a different limit. We get a java.lang.StackOverflowError. Now you would be thinking that iterative approach is better than recursive. But wait, tail call optimization is here to rescue.

@TailRecursive
def factorial(int number, BigInteger fact = 1) {
    number == 0 ? fact : factorial(number - 1, number * fact)
}

Note that now for a non-zero value of number, the function factorial just has to call itself (this is called tail call). Consider factorial(3, 1).
This can be substituted as follows

= factorial(2, 3 * 1)

= factorial(1, 2 * 3 * 1)

= factorial(0, 1 * 2 * 3 * 1)

= 1 * 2 * 3 * 1

= 6

Hence factorial computation requires just a single stack frame. By specifying @TailRecursive, we ask the Groovy compiler leverage tail call optimization.

TailRecursive AST transformation was introduced in Groovy 2.3. Prior to this we had to rely upon closures as follows.

def factorial
factorial = { int number, BigInteger fact = 1 ->
    number == 0 ? fact : factorial.trampoline(number - 1, number * fact)
}.trampoline()
println factorial(10000)

If you are not familiar with closures, this article provides the necessary details.

You may want to take a look at the slides from my FunctionalConf talk for more examples.

Show Comments