[Tutor] recursion and trampolines

Danny Yoo dyoo at hkn.eecs.berkeley.edu
Thu Aug 26 05:36:27 CEST 2004



> >Are there anything else?
>
> A few things to note:
>
> - I'm not too fond of the recursive structure of SpcfName. This
> structure has cropped up a few times on this list recently - maybe we
> have a few converts from LISP.


[advanced; skip if you haven't seen Scheme/Lisp code or recursion.]


We also have to bring up that Python doesn't implement "tail call
elimination". Python's recursion depth is limited, so certain
constructions that work in Scheme/Lisp don't work so well in Python, at
least, not without some adjustments.


As a concrete example, a Python function like:

###
def broken_fact(n, k = 1):
    if n == 0:
        return k
    return broken_fact(n-1, n * k)
###


is broken for any "n" that's greater than the system's recursion limit.


###
>>> sys.getrecursionlimit()
1000
>>> broken_fact(2)
2
>>> broken_fact(3)
6
>>> broken_fact(1000)
>>> broken_fact(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 4, in broken_fact
  File "<stdin>", line 4, in broken_fact

[... a LOT of lines cut.  *grin*]

  File "<stdin>", line 4, in broken_fact
  File "<stdin>", line 4, in broken_fact
RuntimeError: maximum recursion depth exceeded
###


One place we might see this kind of RuntimeError, in practice, is with
nongreedy regular expressions.  Python's implementation of nongreedy
regexes uses a recursive algorithm that dies badly on certain inputs.

###
>>> s = 'Begin ' + 1000*'a very long string ' + 'end'
>>> re.match('Begin (\w| )*? end', s).end()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/usr/lib/python2.3/sre.py", line 132, in match
    return _compile(pattern, flags).match(string)
RuntimeError: maximum recursion limit exceeded
###

(this example is taken from: http://www.python.org/doc/lib/node109.html)

So although this thing is mostly esoteric, it can effect us.


That being said, there is a technique to work around the lack of tail
recursion, called "trampolined style".

    http://www.cs.indiana.edu/hyplan/sganz/publications/icfp99/paper.pdf

It allows us to write recursive programs that avoids growing the call
stack, even in languages that don't natively support tail call
elimination.


Here's a concrete example of a trampolined version of that factorial
function, using the trampolining technique:

###
def trampolined_fact(n, k=1):
    if n == 0:
        return land(k)
    return bounce(trampolined_fact, n-1, k*n)


def fact(n):
    """Returns the factorial of n."""
    return trampoline(trampolined_fact, n)


def trampoline(function, *args):
    """Bounces a function over and over, until we "land" off the
    trampoline."""
    bouncer = bounce(function, *args)
    while True:
        bouncer = bouncer[1](*bouncer[2])
        if bouncer[0] == 'land':
            return bouncer[1]


def bounce(function, *args):
    """Bounce back onto the trampoline, with an upcoming function call."""
    return ["bounce", function, args]


def land(value):
    """Jump off the trampoline, and land with a value."""
    return ["land", value]
###



And now we're in business:

###
>>> fact(1000)
4023872600770937735437024339230039857193748642107146325
4379991042993851239862902059204420848696940480047998861
0197196058631666872994808558901323829669944590997424504
0870737599188236277271887325197795059509952761208749754
6249704360141827809464649629105639388743788648733711918
1045825783647849977012476632889835955735432513185323958
4630755574091142624174743493475534286465766116677973966
6882029120737914385371958824980812686783837455973174613
6085379534524221586593201928090878297308431392844403281
23155861103697680135730421616874760... [text cut]
###


Anyway, I thought this was really cool.  *grin* Hope this helps!



More information about the Tutor mailing list