In computer science, tail recursion (or tail-end recursion) is a special case of recursion that can be easily transformed into an iteration. Such a transformation is possible if the recursive call is the last thing that happens in a function. Replacing recursion with iteration, either manually or automatically, can drastically decrease the amount of stack space used and improve efficiency. This technique is commonly used with functional programming languages where the declarative approach and explicit handling of state promote the use of recursive functions that rapidly fill the call stack.
For normal, non-recursive function calls, this is usually a micro-optimization that saves little time and space, since there are not that many different functions available to call. When dealing with recursive or mutually recursive functions, however, the stack space and the number of returns saved can grow to huge numbers, since a function can call itself, directly or indirectly, a huge number of times. In fact, it often asymptotically reduces stack requirements from linear, or O(n) stack space, to constant, or O(1) stack space.
If several functions are mutually recursive, meaning they each call one another, and each call they make to one another in an execution sequence uses a tail call, then tail call optimization will give a properly tail recursive implementation that does not consume stack space; this is a requirement in, for example, the standard definition of Scheme.
The notion of tail position in Scheme can be defined as follows:
(define (factorial n) (define (iterate n acc) (if (= n 0) acc (iterate (- n 1) (* acc n)))) (if (< n 0) (display "Wrong argument!") (iterate n 1)))
As you can see, the inner procedure iterate calls itself last in the control flow. This allows an interpreter or compiler to reorganize the execution which would ordinarily look like this:
call factorial (3) call iterate (3 1) call iterate (2 3) call iterate (1 6) return 6 return 6 return 6 return 6
into the more space- (and time-) efficient variant:
call factorial (3) replace arguments with (3 1), jump into "iterate" replace arguments with (2 3), re-iterate replace arguments with (1 6), re-iterate return 6
This reorganization saves space because no state except for the calling function's address needs to be saved, neither on the stack nor on the heap. This also means that the programmer need not worry about running out of stack or heap space for extremely deep recursions.
Some programmers working in functional languages will rewrite recursive code to be tail recursive so they can take advantage of this feature.
This often requires addition of an "accumulator" (acc in the above implementation of factorial) as an argument to a function.
In some cases (such as filtering lists) and in some languages, full tail recursion may require a function that was previously purely functional to be written such that it mutates references stored in other variables.
Since having a complete call graph is a daunting task for compilers, a mere tail call is usually referred to as being tail recursive.
Besides space and execution efficiency, the tail recursion is important to allowing a common idiom in functional programming, continuation passing style (CPS), without quickly running out of stack space.
For example, consider a function that accepts a linked list and returns a list of ones with the same length, described here in C:
list *f(list *input)
{
list *head;
if(input == NULL) {
head = NULL;
} else {
head = malloc(sizeof(list));
head->value = 1;
head->next = f(input->next);
}
return head;
}
In this form the function is not tail-recursive, because control returns to the caller after the recursive call to set the value of head→next. But on resumption, the caller merely prepends a value to the result from the callee. So the function is tail-recursive, save for a "cons" action, that is, tail recursive modulo cons. Warren's method gives the following purely tail-recursive implementation:
list *f(list *input)
{
list *head;
fprime(input, &head);
return head;
}
void fprime(list *input, list **p)
{
if(input == NULL) {
*p = NULL;
} else {
*p = malloc(sizeof(list));
(*p)->value = 1;
fprime(input->next, &(*p)->next);
}
}
Note how the callee now appends to the end of the list, rather than have the caller prepend to the beginning.
This implementation can be converted to iterative form.
list *f(list *input)
{
list *head;
list **p;
p = &head;
while(input != NULL) {
*p = malloc(sizeof(list));
(*p)->value = 1;
input = input->next;
p = &(*p)->next;
}
*p = NULL;
return head;
}
Since many Scheme compilers use C as an intermediate target code, the problem comes down to coding tail recursion in C without growing the stack. Many implementations achieve this by using a device known as a trampoline, a piece of code that repeatedly calls functions. All functions are entered via the trampoline. When a function has to call another, instead of calling it directly it returns the address of the function to be called, the arguments to be used, and so on, to the trampoline. This ensures that the C stack does not grow and iteration can continue indefinitely.
Using a trampoline for all function calls is rather more expensive than the normal C function call, so at least one Scheme compiler, Chicken, uses a technique first described by Henry Baker from an unpublished suggestion by Andrew Appel, in which normal C calls are used but the stack size is checked before every call. When the stack reaches its maximum permitted size, objects on the stack are garbage-collected using the Cheney algorithm by moving all live data into a separate heap. Following this, the stack is unwound ("popped") and the program resumes from the state saved just before the garbage collection. Baker says "Appel's method avoids making a large number of small trampoline bounces by occasionally jumping off the Empire State Building." Indefinite levels of tail recursion are permitted, as required by the Scheme standard.
Endrekursion | Récursion terminale | Uodeginė rekursija | Staartrecursie | 末尾再帰 | Rekursja ogonowa | Kuyruk özyineleme
This article is licensed under the GNU Free Documentation License.
It uses material from the
"Tail recursion".
Home Page • arts • business • computers • games • health • hospitals • home • kids & teens • news • physicians • recreation• reference • regional • science • shopping • society • sports • world