views:

500

answers:

5

Tail recursion is an important performance optimisation stragegy in functional languages because it allows recursive calls to consume constant stack (rather than O(n)).

Are there any problems that simply cannot be written in a tail-recursive style, or is it always possible to convert a naively-recursive function into a tail-recursive one?

If so, one day might functional compilers and interpreters be intelligent enough to perform the conversion automatically?

+8  A: 

Any recursive algorithm can be rewritten as an iterative algorithm (perhaps requiring a stack or list) and iterative algorithms can always be rewritten as tail-recursive algorithms, so I think it's true that any recursive solution can somehow be converted to a tail-recursive solution.

(In comments, Pascal Cuoq points out that any algorithm can be converted to continuation-passing style.)

Note that just because something is tail-recursive doesn't mean that its memory usage is constant. It just means that the call-return stack doesn't grow.

Kristopher Johnson
-1: Not all recursive methods can be made tail-recursive. See my answer for an example.
Ben S
You can write an iterative tree-traversal algorithm by using a stack.
Kristopher Johnson
I meant consume constant stack. Thanks for pointing out my mistake.
ctford
@Ben S: "in CPS [...] every call is a tail call." See it for yourself in context: http://en.wikipedia.org/wiki/Continuation-passing_style
Pascal Cuoq
A recursive algorithm can use tail calls if and only if the stack values are never written or read again, after a recursive call, meaning if the previous values can be discarded. If not, tail calls can't be used.
Pop Catalin
+1 to cancel out Ben's -1, which i think is not correct.
Peter Recore
@Ben S: sorry, but you're answer is wrong. Tail-recursive tree traversal and insertion is easy to write using continuation passing. See the following urls: http://stackoverflow.com/questions/833180/handy-f-snippets/1532265#1532265 and http://fortysix-and-two.blogspot.com/2009/06/fun-with-data-structures-continuations.html
Juliet
+6  A: 

You can't do everything in O(1) space (space hierarchy theorem). If you insist on using tail recursion, then you can store the call stack as one of the arguments. Obviously this doesn't change anything; somewhere internally, there is a call stack, you're simply making it explicitly visible.

If so, one day might functional compilers and interpreters be intelligent enough to perform the conversion automatically?

Such conversion will not decrease space complexity.

As Pascal Cuoq commented, another way is to use CPS; all calls are tail recursive then.

sdcvvc
A: 

I don't think something like tak could be implemented using only tail calls. (not allowing continuations)

recursive
+12  A: 
Jason Orendorff
I don't know if this was the best answer to the question, but it was by far the most informative answer. +1
Imagist
It's easier to think of transforming a language with goto and no procedure calls into one with only tail-recursive calls: i.e. each goto, besides the first, becomes a procedure invocation followed by an end.It's the transformation that lies behind Dijkstra's GOTO considered harmful.
Charles Stewart
+4  A: 

It's 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 a par with the old chestnut fro the 1960s that control-flow constructs could be eliminated because every program could be written as a loop with a case statement nested inside.

What's useful to know is that many functions which are not obviously tail-recursive can be converted to tail-recursive form by the addition of accumulating parameters. (An extreme version of this transformation is the transformation to continuation-passing style (CPS), but most programmers find the output of the CPS transform difficult to read.)

Here's an example of a function that is "recursive" (actually it's just iterating) but not tail-recursive:

factorial n = if n == 0 then 1 else n * factorial (n-1)

In this case the multiply happens after the recursive call. We can create a version that is tail recursive by putting the product in an accumulating parameter:

factorial n = f n 1
   where f n product = if n == 0 then product else f (n-1) (n * product)

The inner function f is tail-recursive and compiles into a tight loop.


I find the following distinctions useful:

  • In an iterative or recursive program, you solve a problem of size n by first solving one subproblem of size n-1. Computing the factorial function falls into this category, and it can be done either iteratively or recursively. (This idea generalizes, e.g., to 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 solving two subproblems of size n/2. Or, more generally, you solve a problem of size n by first solving a subproblem of size k and one of size n-k, where 1 < k < n. Quicksort and mergesort are two examples of this kind of problem, which can easily be programmed recursively, but is not so easy to program iteratively or using only tail recursion. (You essentially have to simulate recursion using an explicit stack.)

  • In dynamic programming, you solve a problem of size n by first solving all subproblems 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 points for which the shortest path is 1 stop, then for which the shortest path is 2 stops, etc etc.)

Only the first kind of program has a simple transformation into tail-recursive form.

Norman Ramsey