In this post we will write some Scala helper code that allows you to evaluate non-tail recursive functions as if they were tail recursive. This is done behind the scenes using an explicit stack. The benefit of this is that stack overflow is then much less likely to occur for “large” inputs.

Evaluating Recursive Functions

Suppose we define the function $f: \mathbb{N} \rightarrow \mathbb{N}$ as follows:

$$ f(n) = \left\{ \begin{array}{ll} 0 & \text{ if } n = 0 \text{ ,} \\ n - f(n - 1) & \text { otherwise. } \end{array} \right. $$

It turns out that for any $n$, we have $f(n) = \lceil n / 2 \rceil$ but this is not immediately obvious. This function is not very interesting in itself but we will use it to illustrate some practical aspects of recursion. Here is a straightforward implementation of $f$ in Scala:

def f(n: Int): Int = n match { case 0 => 0 case _ => n - f(n - 1) }

What happens when we evaluate $f(2)$? Tracing all the steps on paper we get

$$ \begin{array}{rcl} f(2) & = & 2 - f(1) \\ & = & 2 - (1 - f(0)) \\ & = & 2 - (1 - 0) \\ & = & 2 - 1 \\ & = & 1 \text{ .} \end{array} $$

On the other hand, a computer evaluates f(2) essentially as follows.

“Enter” the body of f with parameter n set to the argument 2 . The second case applies, so we need to call f recursively and subtract the result from n . Push a frame on to the call stack. This frame contains a mapping from n to 2 , among other things. Let’s denote this frame by $F(2)$. (Re)enter the body of f , this time with n set to 1 . The second case applies again. Push a frame $F(1)$ on to the call stack which maps n to 1 . (Re)enter the body of f , this time with n set to 0 . The first case applies — return 0. At this point the call stack consists of $F(1)$ and $F(2)$. Pop the top frame $F(1)$ off the stack. This frame maps n to 1 and also contains a pointer to the place in the body of f from where the recursive call was made, namely at n - f(n - 1) . We can calculate this now: n = 1 and f(n - 1) is the returned value 0 . Therefore return 1 . Pop the top frame $F(2)$ off the stack. Now n maps to 2 and we have just determined that f(n - 1) is 1 . Therefore return 2 - 1 = 1 . The stack is now empty, so evaluation has finished and 1 is the final result returned by f .

This is very much a simplification of what actually happens but the important point is that the stack grows each time a recursive call is made. We need to push frames on to the stack in order to keep track of the value of n at the time that f calls itself. This is because we later need to subtract the value returned by the recursive invocation from n .

There is a limit on how large the call stack can grow. If this limit is exceeded, stack overflow occurs. When exactly this limit is reached depends on various factors, but on my machine I already get a stack overflow error when $n = 50,000$:

scala> def f(n: Int): Int = n match { | case 0 => 0 | case _ => n - f(n - 1) | } f: (n: Int)Int scala> f(2000) res0: Int = 1000 scala> f(50000) java.lang.StackOverflowError at .f(<console>:9)

Preventing Stack Overflow

As we have just seen, if we need to do anything with the result of a recursive call then we need to push a new frame on to the call stack. If we don’t need to do anything (other than simply returning the result) then there is no need for a new stack frame. This is known as tail call optimisation and it is supported by the Scala compiler. A tail call is a recursive call that does not do any additional operations with its result. A tail-recursive function is a recursive function that uses only tail calls.

Below is a tail-recursive implementation of the factorial function:

def factorial(n: Int): Int = { def factorial(n: Int, accumulator: Int): Int = n match { case 0 => accumulator case _ => factorial(n - 1, accumulator * n) } factorial(n, 1) }

Compare this to the direct, recursive implementation:

def factorial(n: Int): Int = n match { case 0 => 1 case _ => n * factorial(n - 1) }

The tail recursive version uses an accumulator which is a standard trick for making recursive functions tail-recursive.

As pointed out in converting recursion to iteration, this trick does not always work. In the case of factorial, for say $n = 3$ the tail-recursive implementation computes $2 \cdot (3 \cdot 1)$ whereas the standard recursive implementation computes $3 \cdot (2 \cdot 1)$. [UPDATE: As $0$ is the base case this should in fact read $1 \cdot (2 \cdot (3 \cdot 1))$ and $3 \cdot (2 \cdot (1 \cdot 1))$.] The end result happens to be the same because multiplication is commutative and associative, but obviously not every operation has those properties. In particular, subtraction is neither commutative nor associative. Moreover, rewriting a function to use an accumulator is usually not straightforward if our original function is multiply recursive instead of singly recursive — that is, if it calls itself more than once. A typical example of this is the Fibonacci function.

A more generally applicable method is to use an explicit stack instead of an accumulator. For example, we can use Scala’s built in Stack class (or some other appropriate data structure) and then mimic the way the call stack works when evaluating our function. The rest of this post shows how we can implement this approach.

A Datatype for Recursive Functions

Wikipedia’s page on recursion has the following to say about explicit stacks:

Multiply recursive problems are inherently recursive, because of prior state they need to track. One example is tree traversal as in depth-first search; contrast with list traversal and linear search in a list, which is singly recursive and thus naturally iterative. Other examples include divide-and-conquer algorithms such as Quicksort, and functions such as the Ackermann function. All of these algorithms can be implemented iteratively with the help of an explicit stack, but the programmer effort involved in managing the stack, and the complexity of the resulting program, arguably outweigh any advantages of the iterative solution.

This seems a bit pessimistic. Maybe we can write a small Scala library that exposes a simple syntax (DSL?) for specifying recursive functions, and implements an explicit stack behind the scenes for evaluating them. First, here is a datatype EvalSpec for expressing the body of a recursive function.

sealed trait EvalSpec[S, T] case class Constant[S, T](t: T) extends EvalSpec[S, T] case class Recurse[S, T](s: S, f: T => EvalSpec[S, T]) extends EvalSpec[S, T]

Here S is the input type of the recursive function and T is its result type. A recursive function either returns a constant t , or it makes a recursive call with an argument s and a function f that does something with the result of this recursive call. We also add a method evaluate that takes a function producing an EvalSpec and applies it to an argument. The implementation of evaluate is left for later, but here is its signature:

def evaluate[S, T](f: S => EvalSpec[S, T])(s: S): T

Now, for example, to re-implement the function $f$ given at the start of this post we write the following (calling the new method g for convenience):

def g: Int => Int = evaluate[Int, Int] { case 0 => Constant(0) case n => Recurse(n - 1, x => Constant(n - x)) }

This says that if the input to g is 0 then 0 should be returned. Otherwise, for an integer n (which is assumed to be positive) we must make a recursive call with n - 1 and then subtract the result x from n .

Evaluating With an Explicit Stack

Here is the implementation of evaluate , along with some auxiliary definitions:

private sealed trait EvalStack[S, T] private case class ConstantFrame[S, T](constant: Constant[S, T], tail: Option[ContinueFrame[S, T]]) extends EvalStack[S, T] private case class RecurseFrame[S, T](recurse: Recurse[S, T], tail: Option[ContinueFrame[S, T]]) extends EvalStack[S, T] private case class ContinueFrame[S, T](f: T => EvalSpec[S, T], tail: Option[ContinueFrame[S, T]]) extends EvalStack[S, T] def evaluate[S, T](f: S => EvalSpec[S, T])(s: S) = { @scala.annotation.tailrec def process(stack: EvalStack[S, T]): T = { stack match { case ConstantFrame(Constant(c), None) => c case ConstantFrame(Constant(c), Some(ContinueFrame(g, tail))) => process(makeStack(g(c), tail)) case RecurseFrame(Recurse(arg, g), tail) => process(makeStack(f(arg), Some(ContinueFrame(g, tail)))) } } process(makeStack(f(s), None)) } private def makeStack[S, T](top: EvalSpec[S, T], tail: Option[ContinueFrame[S, T]]): EvalStack[S, T] = top match { case constant@Constant(_) => ConstantFrame(constant, tail) case recurse@Recurse(_, _) => RecurseFrame(recurse, tail) }

Instead of using Scala’s built-in Stack or List datatype we define our own, called EvalStack . This is essentially a linked list whose head can be a Constant , a Recurse , or the “continuation” part of a Recurse . The helper function makeStack simply takes a frame and makes it the new head of given stack tail (whose head is a ContinueFrame ).

Calling the function g above with argument 2 results in the following stack progression:

iteration 1: RecurseFrame(Recurse(1, x => 2 - x), // the top of the stack None) iteration 2: RecurseFrame(Recurse(0, x => 1 - x), // the new top of the stack Some(ContinueFrame(x => 2 - x, None))) iteration 3: ConstantFrame(Constant(0), Some(ContinueFrame(x => 1 - x, Some(ContinueFrame(x => 2 - x, None))))) iteration 4: ConstantFrame(Constant(1), // 1 - 0 Some(ContinueFrame(x => 2 - x, None))) iteration 5: ConstantFrame(Constant(1), // 2 - (1 - 0) None)

Moreover, evaluating g(50000) produces 25000 as expected and does not fail due to stack overflow.

In what follows, let’s look at a few more examples of what EvalSpec can be used for. We first add an implicit definition that saves us from having to wrap constants in Constant :

implicit def valToConst[S, T](t: T): Constant[S, T] = Constant[S, T](t)

Example: Factorial

Below are recursive and EvalSpec -based implementations of the factorial function:

def factorialRecursive(n: Int): Int = if (n < 2) 1 else n * factorialRecursive(n - 1) def factorial: Int => Int = evaluate[Int, Int] { n => if (n < 2) 1 else Recurse(n - 1, x => n * x) }

In contrast to the tail-recursive version given earlier that used an accumulator, this time the result is calculated in the exact same order as by the normal recursive version. Here is what happens for factorial(4) :

iteration 1: RecurseFrame(Recurse(3, x => 4 * x), None) iteration 2: RecurseFrame(Recurse(2, x => 3 * x), Some(ContinueFrame(x => 4 * x, None))) iteration 3: RecurseFrame(Recurse(1, x => 2 * x), Some(ContinueFrame(x => 3 * x, Some(ContinueFrame(x => 4 * x, None))))) iteration 4: ConstantFrame(Constant(1), Some(ContinueFrame(x => 2 * x, Some(ContinueFrame(x => 3 * x, Some(ContinueFrame(x => 4 * x, None))))))) iteration 5: ConstantFrame(Constant(2), // 2 * 1 Some(ContinueFrame(x => 3 * x, Some(ContinueFrame(x => 4 * x, None))))) iteration 6: ConstantFrame(Constant(6), // 3 * (2 * 1) Some(ContinueFrame(x => 4 * x, None))) iteration 7: ConstantFrame(Constant(24), // 4 * (3 * (2 * 1)) None)

Example: Fibonacci

Below are implementations of the Fibonacci function (which uses multiple recursion):

def fibonacciRecursive(n: Int): Int = if (n < 2) n else fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2) def fibonacci: Int => Int = evaluate[Int, Int] { n => if (n < 2) n else Recurse(n - 1, x => Recurse(n - 2, y => x + y)) }

Example: Binary Trees

In this example we define a datatype for binary trees. We then define a function that takes a binary tree whose nodes are labelled by integers, and sums the labels of all the nodes in this tree.

trait BinaryTree[+T] case class Node[+T](value: T, left: BinaryTree[T], right: BinaryTree[T]) extends BinaryTree[T] case object EmptyTree extends BinaryTree[Nothing] def sumRecursive(tree: BinaryTree[Int]): Int = tree match { case EmptyTree => 0 case Node(value, left, right) => value + sumRecursive(left) + sumRecursive(right) } def sum: BinaryTree[Int] => Int = evaluate[BinaryTree[Int], Int] { case EmptyTree => 0 case Node(value, left, right) => Recurse(left, x => Recurse(right, y => value + x + y)) }

Trampolines and Continuations

The helper code above shares some similarities with Rich Dougherty’s implementation of trampolines but is more generally applicable. It also seems similar to continuations but I haven’t quite understood the relationship yet.

Conclusion

The EvalSpec -based definitions of recursive functions are arguably concise and easy to read. They look (almost) like their non-tail recursive counterparts but get evaluated in a tail-recursive way behind the scenes. This code is just a simple working prototype, if you know of a better way to do this then please let me know (surely I have poorly reinvented some existing technique?). Please see the simulated-call-stack project on GitHub for the full implementation as well as more examples.

UPDATE: Boyd Smith has kindly pointed out that Stackless Scala With Free Monads is far more general than what I have done here. So please consider this blog post as a small step in the right direction :-).

By Hilverd Reker