It is true, but not useful, to observe that any collection of mutually recursive functions can be turned into a tail-recursive function. This observation is on par with the old chestnut of the 1960s, that control flow designs can be eliminated, because each program can be written as a loop with a case embedded in it.
It is useful to know that many functions that are not explicitly tail-recursive can be converted to tail-recursive form by adding cumulative parameters. (The latest version of this conversion is a transition to the Continuing Pass (CPS) style, but most programmers find the result of the CPS conversion difficult to read.)
Here is an example of a function that is โrecursiveโ (in fact, it just repeats itself), but is not tail recursive:
factorial n = if n == 0 then 1 else n * factorial (n-1)
In this case, the multiplication occurs after a recursive call. We can create a tail recursive version by putting the product in the cumulative parameter:
factorial n = fn 1 where fn product = if n == 0 then product else f (n-1) (n * product)
The inner function f is tail recursive and compiles into a closed loop.
I find the following differences useful:
In an iterative or recursive program, you solve a problem of size n by first solving one subproblem of size n-1 . The calculation of the factor function falls into this category, and this can be done either iteratively or recursively. (This idea generalizes, for example, the Fibonacci function, where you need both n-1 and n-2 to solve n .)
In a recursive program, you solve a problem of size n by first resolving two sub-tasks of size n/2 . Or, more generally, you solve a problem of size n first resolving a subtask of size k and one of sizes nk , where 1 < k < n . Quicksort and mergesort are two examples of these kinds of problems that can be easily programmed recursively, but the program is not so simple iteratively or using only tail recursion. (You should essentially mimic recursion using an explicit stack.)
In dynamic programming, you solve a problem of size n by first resolving all subtasks of all sizes k , where k<n . Finding the shortest route from one point to another on the London Underground is an example of this kind of problem. (The London Underground is a multiply connected graph, and you solve the problem by first finding all the points for which the shortest path is 1 stop, then for which the shortest path is 2 stops, etc. etc.)
Only the first type of program has a simple conversion to the tail-recursive form.
Norman Ramsey Dec 12 '09 at 1:07 2009-12-12 01:07
source share