So, that difference in the rewriting rules actually translates directly to a
difference in the execution, in the actual, execution on a computer.
In fact, it turns out that if you have a recursive function that calls itself as
its last action, then you can reuse the stack frame of that function.
This is called tail recursion. And by applying that trick, it means that
a tail recursive function can execute in constant stuck space, so it's really just
another formulation of an iterative process.
Could say a tail recursive function is the functional form of a loop, and it executes
just as efficiently as a loop. So if we go back to GCD, we see that in
the else part, GCD calls itself as its last action.
And that translate its, to a rewriting sequence that was essentially constant
size, and that will, in the actual execution on a computer, translate into a
tail recursive call that can execute in constant space.
On the other hand, if you look at factorial again, then you'll see that
after the call to factorial n minus one, there was still work to be done, namely,
we had to multiply the result of that call with the number N.
So that call here is not a tail recursive call, and it becomes evident in the
reduction sequence, where you see that actually there's a buildup of intermediate
results that we all have to keep until we can compute the final value.
So that factorial would not be a tail recursive function.
Both factorial and GCD only call itself
but in general, of course, a function could call other functions.
So the generalization of tail recursion is that, if the last action of a
function consists of calling another function, maybe the same, maybe some other
function. The stack frame could be reused for both
functions. Such calls are called tail calls.
After having gone through the exercise, you might ask yourself, should every
function be tail recursive? Well, not really.
The interest of tail recursion is mostly to avoid very deep recursive chains.
Most implementations of, the JBM, for instance, limit the maximal depth of
recursion to a couple of thousand stack frames.
So if your, the input data is such that these deep recursive chains could happen,
then yes it's a good idea to reformulate your function to be tail recursive, to run
in constant stack frame, so as to avoid stack overflow exceptions.
On the other hand, if your input data are not,
susceptible to deep precausive chains then
clarity trumps efficiency evert time, so write your function the clearest way you
can. Which often is not terricosive.
And don't worry about the steck frames that are spent.
As Donald Knuth has said, premature optimization is the source of all evil.
And that's the model that's very valuable to follow.