Path: csiph.com!usenet.pasdenom.info!aioe.org!news.stack.nl!newsfeed.xs4all.nl!newsfeed2.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'example:': 0.03; 'else:': 0.03; 'parameters': 0.04; 'assignment': 0.07; 'source.': 0.07; 'arguments': 0.09; 'bindings': 0.09; 'bug.': 0.09; 'closest': 0.09; 'override': 0.09; 'parameter': 0.09; 'received:80.91': 0.09; 'received:80.91.229': 0.09; 'received:gmane.org': 0.09; 'received:list': 0.09; 'statements': 0.09; 'subject:while': 0.09; 'python': 0.11; 'def': 0.12; 'jan': 0.12; 'assume': 0.14; "'''": 0.16; 'check.': 0.16; 'correctness': 0.16; 'efficiency.': 0.16; 'expression,': 0.16; 'iteration': 0.16; 'optional': 0.16; 'received:80.91.229.3': 0.16; 'received:plane.gmane.org': 0.16; 'reedy': 0.16; 'subject:recursion': 0.16; 'targets': 0.16; 'else,': 0.19; 'first.': 0.19; 'input': 0.22; 'tests': 0.22; 'header:User-Agent:1': 0.23; 'body,': 0.24; 'logical': 0.24; 'replace': 0.24; 'equivalent': 0.26; 'nearly': 0.26; 'header:X -Complaints-To:1': 0.27; 'function': 0.29; '(this': 0.29; 'raise': 0.29; 'label': 0.30; 'statement': 0.30; 'especially': 0.30; 'usually': 0.31; 'argue': 0.31; 'work:': 0.31; 'but': 0.35; 'add': 0.35; 'done,': 0.36; 'done': 0.36; 'turn': 0.37; 'two': 0.37; 'expected': 0.38; 'to:addr:python-list': 0.38; 'rather': 0.38; 'does': 0.39; 'to:addr:python.org': 0.39; 'received:org': 0.40; 'users': 0.40; 'even': 0.60; 'remove': 0.60; 'easy': 0.60; 'is.': 0.60; 'conversion': 0.61; 'new': 0.61; 'received:173': 0.61; 'real': 0.63; 'our': 0.64; 'more': 0.64; 'here': 0.66; 'reverse': 0.68; 'statement,': 0.68; 'secret': 0.74; "'we": 0.84; 'condition.': 0.84; 'goto': 0.84; 'received:fios.verizon.net': 0.84; 'situations,': 0.84; 'top.': 0.84; 'care,': 0.91; 'steps.': 0.91; 'contrary': 0.95 X-Injected-Via-Gmane: http://gmane.org/ To: python-list@python.org From: Terry Reedy Subject: Tail recursion to while iteration in 2 easy steps Date: Tue, 01 Oct 2013 17:30:41 -0400 Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Gmane-NNTP-Posting-Host: pool-173-75-251-66.phlapa.fios.verizon.net User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Thunderbird/24.0 X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 89 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1380663065 news.xs4all.nl 15988 [2001:888:2000:d::a6]:51789 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:55239 Part of the reason that Python does not do tail call optimization is that turning tail recursion into while iteration is almost trivial, once you know the secret of the two easy steps. Here it is. Assume that you have already done the work of turning a body recursive ('not tail recursive') form like def fact(n): return 1 if n <= 1 else n * fact(n-1) into a tail recursion like def fact(n, _fac=1): '''Return factorial for any count n. Users are not expected to override private parameter _fac. ''' if n <= 1: return _fac else: # optional return fact(n-1, n * _fac) (This conversion nearly requires adding an accumulator parameter, as done here. Turn this into while iteration with two easy steps. 1. If not already done, make if-else a statement, rather than an expression, with the recursive call in the if branch. If nothing else, just use 'not condition' to invert the condition. def fact(n, _fac=1): if n > 1: # not n <= 1 return fact(n-1, n * _fac) else: # optional return _fac While contrary to what is often published, this order makes logical sense. The recursive call means 'go to the top of this function with new bindings for the parameters'. So put it closest to the top. The base case means 'we are done, time to leave'. So put it at the bottom. 2. This order also makes the follow substeps work: 2a. Replace 'if' with 'while'. 2b. Replace the recursive call with multiple assignment, using the parameters as targets and the arguments as source. For our example: def fact(n, _fac=1): while n > 1: n, _fac = n-1, n * _fac else: return _fac The proof of correctness for this conversion might argue that the recursive form is equivalent to the following pseudo-Python: def fact(n, _fac=1): label top if n > 1: n, _fac = n-1, n * _fac goto top else: return _fac and that this is equivalent to the real Python with while. At this point, you call pull the initialization of the private parameter into the body, remove the else, and even add a value check. def fact(n): if n < 0 or n != int(n): raise ValueError('fact input {} is not a count'.format(n)) fac = 1 while n > 1: n, fac = n-1, n * fac return fac With care, the multiple-assignment statement can usually be turned into multiple assignment statements for greater efficiency. fac *= n n -= 1 But note that the reverse order would be a bug. So I would not do this, especially in more complicated situations, without having tests first. -- Terry Jan Reedy