Path: csiph.com!usenet.pasdenom.info!news.redatomik.org!newsfeed.xs4all.nl!newsfeed8.news.xs4all.nl!newsgate.cistron.nl!newsgate.news.xs4all.nl!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.001 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'else:': 0.03; 'compiler': 0.05; 'removes': 0.05; 'exception.': 0.07; 'overflow': 0.07; 'cc:addr:python-list': 0.09; 'level:': 0.09; "they've": 0.09; 'python': 0.10; 'python.': 0.11; 'stack': 0.13; 'def': 0.13; 'interpreter': 0.15; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'looping': 0.16; 'transforming': 0.16; 'trivially': 0.16; 'try/except': 0.16; 'wrote:': 0.16; 'implementing': 0.18; 'instance,': 0.18; 'tree': 0.18; 'runs': 0.18; '2015': 0.20; 'cc:2**0': 0.20; 'cc:addr:python.org': 0.20; '(by': 0.22; 'converted': 0.22; 'decorator': 0.22; 'parameter': 0.22; 'am,': 0.23; 'this:': 0.23; 'implemented': 0.24; 'header:In- Reply-To:1': 0.24; "i've": 0.25; "doesn't": 0.26; 'message- id:@mail.gmail.com': 0.27; 'function': 0.28; '**kwargs)': 0.29; 'tail': 0.29; 'convert': 0.29; 'subject:/': 0.30; 'code': 0.30; "can't": 0.32; 'implement': 0.32; 'problem': 0.33; 'source': 0.33; 'usually': 0.33; 'similar': 0.33; 'received:google.com': 0.35; 'ones': 0.35; 'could': 0.35; 'returning': 0.35; 'something': 0.35; 'should': 0.36; 'created': 0.36; 'cases': 0.36; 'subject:: ': 0.37; 'really': 0.37; 'method': 0.37; '12,': 0.37; 'means': 0.39; 'why': 0.39; 'does': 0.39; 'where': 0.40; 'still': 0.40; 'space': 0.40; 'your': 0.60; 'behavior': 0.61; 'show': 0.62; 'making': 0.62; 'information': 0.63; 'worth': 0.67; 'jul': 0.72; 'overcome': 0.72; 'calls,': 0.84; 'chrisa': 0.84; 'func):': 0.84; 'transmitting': 0.84; 'utilized': 0.84; 'to:none': 0.91 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:cc :content-type:content-transfer-encoding; bh=qGVkMlduhjr+ArxRF/5yP8P16W+xVW4XJts6TcmqzwU=; b=AKSTrJ9tclZUOZdDdc6crgppD9b22OXkWBx94mqCCSz2rZgXEHvhX2Qiy5R+AjL2D+ WrYKuRmxnpGSoMNu15YacgckJ4yBi5xMzqLnvoRK0Vz1nPJ/Nkry8abUejr/XfqrwCtG O8THC6P61ko10Tt7X05avzlB0UrPozRmPYujm2h/D+n3hRGAUpgyXoNrrKcY3A3HL4Px UdWlw2E+VMPeBg6P90h5SVjRXA77lk0e83/+TH6+VkTRqgayamjyJu5xOMf51ervU/JW UIQ/381bModN7Bn6bun+5JioFKLuXykE09cBwwFkwvuFRINcWOeMGJmk3OREfbMSu6o7 wz2w== MIME-Version: 1.0 X-Received: by 10.50.128.169 with SMTP id np9mr12288247igb.37.1436786895276; Mon, 13 Jul 2015 04:28:15 -0700 (PDT) In-Reply-To: References: Date: Mon, 13 Jul 2015 21:28:15 +1000 Subject: Re: Possibly Pythonic Tail Call Optimization (TCO/TRE) From: Chris Angelico Cc: "python-list@python.org" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.20+ 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: 64 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1436786904 news.xs4all.nl 2859 [2001:888:2000:d::a6]:39525 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:93752 On Sun, Jul 12, 2015 at 8:20 AM, Ian Burnette wrot= e: > > I've recently been playing around with Clojure, and I really like the way= they've overcome the JVM not supporting TRE. The way Clojure solves this p= roblem is to have a built in "recur" function that signals to the Clojure c= ompiler to convert the code to a loop in the JVM. In python we could do som= ething similar by having a recur(*args, **kwargs) function that removes its= parent from the stack and then runs the parent function again with the giv= en arguments. It would look something like this: > > def factorial(n, acc=3D1): > if n =3D=3D 0: > return acc > else: > return recur(n-1, acc=3D(acc*n)) #signals to the interpreter to t= rigger TRE > # Doesn't overflow the stack! > > factorial(30000) > > > I've also created a plugin that mocks this behavior using the try/except = looping method that others have utilized to implement TCO into python. Of c= ourse, my plugin still requires a decorator and the overhead of transmittin= g information through an exception. Implementing recur with interpreter sup= port should lead to much better performance. > When a function is purely tail-recursive like this, it's trivial to convert it at the source code level: def factorial(n): acc =3D 1 while n > 0: acc *=3D n n -=3D 1 return acc The cases that _can't_ be converted this trivially are the ones that usually can't be converted automatically either - the best case I can think of is tree traversal, where one of the calls could be tail-call optimized: def traverse(node, func): if node.left: traverse(node.left, func) func(node) if node.right: return recur(node.right, func) (By the way, what happens if you call recur() without returning its result?= ) It still needs stack space for all the left calls, so it's not really benefiting that much from TCO. Where does the advantage show up? Why is it worth writing your code recursively, only to have it be implemented iteratively? Warping your code around a recursive solution to make it into a perfect tail call usually means making it look a lot less impressive; for instance, the 'acc' parameter in your above code has no meaning outside of transforming the recursion into tail recursion. https://xkcd.com/1270/ ChrisA