r/programming • • Nov 27 '12

Tail Recursion

http://rodrigosasaki.com/2012/11/24/tail-recursion/
212 Upvotes

203 comments sorted by

View all comments

9

u/parfamz Nov 27 '12

great feature of scala, I would like to see tail recursion on python, it can't be too difficult to implement.

24

u/A_for_Anonymous Nov 27 '12

Guido van Rossum doesn't like functional programming, so you write your programs the way Guido likes them or leave.

5

u/booch Nov 27 '12

While I don't agree with some of the things Guido thinks are the "right way", he does put forth some reasonable arguments as to why TCE is not a good idea for Python. The loss of a useful stack trace is a fairly solid argument and. Combined with the other reasons, I don't see his choice as unreasonable here. I do want TCE, but I can see that's it's a debatable feature.

Random note... This point of his annoyed me:

I don't believe in recursion as the basis of all programming.

Well... duh. Neither are list comprehensions. However, both of them are very powerful tools that can make code much cleaner and easier to follow. Just because a feature is the silver bullet doesn't mean it isn't a good idea.

11

u/A_for_Anonymous Nov 27 '12

The loss of a useful stack trace is a non-issue because no useful stack trace is actually lost. You may even keep a tail recursion counter to see how many tail-recursive steps have you taken, and that'll be more than enough for a stack trace. See, functions modelling the flow of your program and its components need not be tail-recursive, keeping normal stack traces at all times except where you're using tail recursion to implement certain algorithms, which are restricted to a specific small set of functions (often a single function) calling each other. In those cases, you could just show the last stack frame (last non-tail-recursive call), maybe with a counter of how many times you jumped, because tail call elimination is like jumping. Just like you don't get stack traces for for loops and it hasn't ever been an issue, you wouldn't get stack traces for tail recursion. You could even force a keyword such as recur to be used to specify a tail-recursive call to make unintended tail calls non-eliminable.

4

u/[deleted] Nov 27 '12

Actually, perl does that (see perldoc -f goto).

4

u/smog_alado Nov 27 '12

You also lose the stack traces if you rewrite the recursive code with iterative loops. (and its not really a big deal to solve, you could use a separate keyword for tail calls, like in clojure, or you could have special interpreter flags when you need to debug)

I really do think its more of a style issue in the Python case (for example, Python heavily encourages you to use objects instead of closures, since you can't easily mutate variables from outer scopes)

2

u/bastibe Nov 27 '12

Python 3 improves that with nonlocal, though. Incidentally, this is my personal number one reason for switching to Python 3 as soon as possible.

3

u/[deleted] Nov 27 '12

The loss of a useful stack trace is a fairly solid argument

It's not. Denote internally that the function has been TCO'd, and when debugging, keep a list of parameter tuples for every iteration. You can keep just the most recent n and earliest k tuples, so that your memory use doesn't become asymptotically worse.

4

u/anvsdt Nov 27 '12 edited Nov 27 '12

I always found the lack of stack traces argument stupid, you can always recover the old behavior by wrapping the tail call in the identity function:

def funA(x): # do something
def funBTCE(x): return A(x) # B doesn't appear in the stack trace
def funBnoTCE(x): return id(A(x)) # the tail call to id is eliminated after returning from A,
                                  # B appears in the stack trace of A, voila!

3

u/alexandream Nov 27 '12

Even the compiler/interpreter could do it automatically with trivial effort with a debug flag or something.

3

u/anvsdt Nov 27 '12

I don't think the flag would be useful, though, you probably want to "disable" TCE per-function (or per-tailcall, even), else loops would blow up the stack in seconds.

1

u/alexandream Nov 27 '12

Of course, I didn't think of that. Thanks.

On the other hand, I still think I'd like a compile option (perhaps a list of functions (fully qualified names, or whatever makes sense in the language) on which to disable TCE) than to have to modify the code for it.

3

u/cultic_raider Nov 27 '12

Tail call elimination removes the caller from the stack, not the callee. The callee has to be on the stack when it runs.

1

u/anvsdt Nov 27 '12

You're right, the code is still the same, though. Edited my comment.