I realise the answer to this question could be different for different languages, and the language I am most interested in is C++. If the tag needs be changed because this can’t be answered in a language-agnostic manner, feel free.
Is it possible to have a function be partially tail-recursive and still get any advantage that being tail-recursive would get you?
As I understand it, tail-recursion is where instead of doing a full function call, the compiler will optimise the function to just change the arguments in place to the new arguments and jump to the beginning of the function.
If you have a function like this:
def example(arg):
if arg == 0:
return 0 # base case
if arg % 2 == 0:
return example(arg - 1) # should be tail recursive
return 3 + example(arg - 1) # isn't tail recursive because 3 is added to the result
When an optimiser encounters something like that (where the function is tail-recursive in some cases and not in others) will it turn the one into a jump and the other into a call, or will some fact of optimisation reality (if I knew it I wouldn’t be asking) make it have to turn everything into a call and lose all the efficiency you would have had if the function were tail-recursive?
In Scheme, the first language that comes to mind when I think of tail calls, the second case is guaranteed to be a tail call by the language specification. (Terminology note: it is preferred to refer to such function calls as ‘tail calls’.)
The Scheme specification defines exactly what tail calls are in Scheme and mandates that compilers support them specially. You can see the definition in 11.20. Tail calls and tail contexts of R6RS (source).
Note that in Scheme, the specification says nothing about optimization of tail calls. Rather, it says that an implementation must support an unbounded number of active tail calls — a semantic property of the language runtime. They can be implemented as normal calls, but usually aren’t.
Example, in C:
Take a C version of your example.
Compile it using gcc’s usual optimization settings (
-O2) for i386:Note that there are no function calls in the assembly code. GCC has not only optimized your tail call into a jump, it optimized the non-tail call into a jump as well.