A new module for performing tail-call elimination
Terry Reedy
tjreedy at udel.edu
Fri Jul 17 20:40:53 EDT 2015
On 7/17/2015 4:55 PM, Marko Rauhamaa wrote:
> Terry Reedy <tjreedy at udel.edu>:
>
>> On 7/16/2015 3:45 PM, Marko Rauhamaa wrote:
>>> Nobody seemed to notice that I just posted a fairly typical tail call
>>> function:
>>
>> Because non-recursive tail calls are completely normal.
>
> I don't know how recursion makes a difference
There are two extremely important differences:
1. Direct recursion calls the function that contains the call. This is
what allows replacement of tail recursion with a loop within the same
function.
2. Almost all problems with stack overflow are due to recursion, whether
at the tail or within the body of a code block. Such problems are nearly
always with linear recursion (one recursive call per call until the base
case). Problems with branching recursion (multiple recursive calls per
call) are rare except for very deep trees and graphs.
> but that one did happen to be recursive.
Not directly, as needed for loop conversion.
class X: # omitted from original but needed below
def setvalue(self, keyseq, value, offset=0):
...
child.setvalue(keyseq, value, offset + 1)
child.setvalue is a new callable bound method object, call it 'bm', that
is different from the setvalue function. This tail call is not a tail
recursive call. The standard conversion of adding a while loop and
replacing the tail call with the assignment in the call, in this case
'offset = offset+1' will not work.
> It could easily have been replaced with a while loop
Given the equality stated below, then yes.
child.setvalue(*args) == bm(*args) calls (in 3.x)
bm.__func__(bm.__self__, *args)
If type(child) == type(self) == X, then bm.func == X.setvalue
and the indirect call is the same as
X.setvalue(child, keyseq, value, offset + 1)
making the bound method call indirectly recursive.
If we replace the bound method call in setvalue with the call to
setvalue itself, then we have a tail recursive call and can do the
standard replacement to get:
class X
def setvalue(self, keyseq, value, offset=0):
while True:
...
# child.setvalue(keyseq, value, offset + 1)
offset += 1
self = child
If children are not always instances of type(self), as when a tree has
separate Node and Leaf classes, then recursive calls to Node instances
must be separated from non-recursive Leaf calls before replacing the
recursive calls.
Having a good reason to rebind the first parameter within methods, as
above, is a good reason to not hide it (as some have proposed).
> but there were good aesthetic reasons to leave it recursive.
Once one learns that looping and recursive calls are two ways of doing
the same thing -- repeatedly executing a block of code with altered
inputs, then the aesthetic reasons are purely aesthetic.
I personally would probably initially write and test setvalue with
recursion. If I were releasing it for others to use in production code
(where aesthetics no longer break ties between equivalent correct
implementations), I would do the conversion to avoid possible stack
overflows. For cases like this, where only some parameters are rebound,
I might leave the original tail call in a comment as documentation, as I
did above.
--
Terry Jan Reedy
More information about the Python-list
mailing list