Last call modulo constructor
One common situation in declarative languages is where there is almost a tail call, in the sense that there are no computations or calls (or ABI infrastructure, such as exception-handling code) between the call and the return.
An especially common occurrence is where the only thing remaining is a constructor. Consider, for example, list append
in ML:
fun append [] l2 = l2
| append (h::t) l2 = h :: append t l2;
The only thing that happens after the recursive call to append
is the construction of the cons cell. So this could be implemented as a tail call or loop where the constructor occurs before the call, and a reference or pointer is passed to the tail call.
You may need to implement this as a worker/wrapper, with the wrapper satisfying the language's ABI. In a hypothetical C-like backend:
ml_object*
ml_append(ml_object* l1, ml_object* l2)
{
ml_object* return_value = nullptr;
ml_append_worker(l1, l2, &return_value);
return return_value;
}
void
ml_append_worker(ml_object* l1, ml_object* l2, ml_object** return_value)
{
if (is_nil(l1)) {
*return_value = l2;
}
else {
*return_value = make_cons();
return_value->car = l1->car;
return_value->cdr = nullptr; /* Garbage collection needs to know about this! */
/* The following is now a tail call. */
ml_append_worker(l1->cdr, l2, &return_value->cdr);
}
}
As with all worker/wrapper arrangements, the wrapper is an excellent candidate for inlining.
Middle recursion
A second option is what Mercury calls middle recursion. Using Mercury's terminology, this pattern:
a_function()
{
if (is_base_case()) {
base_case();
}
else {
down();
a_function();
up();
}
}
...can be transformed into this, using an explicit stack.
a_function()
{
while (!is_base_case()) {
down();
push_local_variables_onto_stack();
}
base_case();
while (stack_has_elements) {
pop_local_variables_off_stack();
up();
}
}
The stack can sometimes be replaced by a counter if the data stored across the recursive call is trivial or nonexistent.
A common hand-implementation of quick sort essentially does this, using an explicit stack for one recursive call and a loop for the other.