Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #93763 > unrolled thread

A new module for performing tail-call elimination

Started by"Th. Baruchel" <baruchel@no.spam.gmx.dot.com>
First post2015-07-13 15:44 +0000
Last post2015-07-17 10:06 +0200
Articles 19 on this page of 39 — 16 participants

Back to article view | Back to comp.lang.python


Contents

  A new module for performing tail-call elimination "Th. Baruchel" <baruchel@no.spam.gmx.dot.com> - 2015-07-13 15:44 +0000
    Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-15 11:29 +0200
      Re: A new module for performing tail-call elimination Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2015-07-16 18:07 +1000
        Re: A new module for performing tail-call elimination Robin Becker <robin@reportlab.com> - 2015-07-16 10:13 +0100
        Re: A new module for performing tail-call elimination Robin Becker <robin@reportlab.com> - 2015-07-16 10:28 +0100
          Re: A new module for performing tail-call elimination Marko Rauhamaa <marko@pacujo.net> - 2015-07-16 12:56 +0300
        Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-16 12:41 +0200
          Re: A new module for performing tail-call elimination Steven D'Aprano <steve@pearwood.info> - 2015-07-17 04:58 +1000
            Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-17 11:00 +0200
        Re: A new module for performing tail-call elimination Chris Angelico <rosuav@gmail.com> - 2015-07-16 21:11 +1000
        Re: A new module for performing tail-call elimination Jeremy Sanders <jeremy@jeremysanders.net> - 2015-07-16 13:29 +0200
        Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-16 15:35 +0200
        Re: A new module for performing tail-call elimination Chris Angelico <rosuav@gmail.com> - 2015-07-16 23:47 +1000
          Re: A new module for performing tail-call elimination Paul Rubin <no.email@nospam.invalid> - 2015-07-17 20:06 -0700
        Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-16 16:21 +0200
        Re: A new module for performing tail-call elimination Chris Angelico <rosuav@gmail.com> - 2015-07-17 00:27 +1000
        Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-16 17:14 +0200
        Re: A new module for performing tail-call elimination Ian Kelly <ian.g.kelly@gmail.com> - 2015-07-16 10:17 -0600
        Re: A new module for performing tail-call elimination Ethan Furman <ethan@stoneleaf.us> - 2015-07-16 10:54 -0700
        Re: A new module for performing tail-call elimination Ethan Furman <ethan@stoneleaf.us> - 2015-07-16 11:02 -0700
        Re: A new module for performing tail-call elimination Terry Reedy <tjreedy@udel.edu> - 2015-07-16 15:45 -0400
        Re: A new module for performing tail-call elimination Ethan Furman <ethan@stoneleaf.us> - 2015-07-16 12:58 -0700
        Re: A new module for performing tail-call elimination Robin Becker <robin@reportlab.com> - 2015-07-17 09:57 +0100
      Re: A new module for performing tail-call elimination Alain Ketterlin <alain@universite-de-strasbourg.fr.invalid> - 2015-07-16 13:23 +0200
    Re: A new module for performing tail-call elimination Terry Reedy <tjreedy@udel.edu> - 2015-07-15 17:19 -0400
    Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-16 09:45 +0200
    Re: A new module for performing tail-call elimination Terry Reedy <tjreedy@udel.edu> - 2015-07-16 15:34 -0400
      Re: A new module for performing tail-call elimination Marko Rauhamaa <marko@pacujo.net> - 2015-07-16 22:45 +0300
        Re: A new module for performing tail-call elimination Terry Reedy <tjreedy@udel.edu> - 2015-07-17 15:47 -0400
          Re: A new module for performing tail-call elimination Marko Rauhamaa <marko@pacujo.net> - 2015-07-17 23:55 +0300
            Re: A new module for performing tail-call elimination Terry Reedy <tjreedy@udel.edu> - 2015-07-17 20:40 -0400
              Re: A new module for performing tail-call elimination Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2015-07-19 10:39 +1200
            Re: A new module for performing tail-call elimination Chris Angelico <rosuav@gmail.com> - 2015-07-18 10:47 +1000
            Re: A new module for performing tail-call elimination Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2015-07-19 10:39 +1200
        Re: A new module for performing tail-call elimination Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2015-07-19 10:39 +1200
          Re: A new module for performing tail-call elimination Mark Lawrence <breamoreboy@yahoo.co.uk> - 2015-07-19 01:09 +0100
          Re: A new module for performing tail-call elimination MRAB <python@mrabarnett.plus.com> - 2015-07-19 01:19 +0100
          Re: A new module for performing tail-call elimination Marko Rauhamaa <marko@pacujo.net> - 2015-07-19 09:29 +0300
    Re: A new module for performing tail-call elimination Antoon Pardon <antoon.pardon@rece.vub.ac.be> - 2015-07-17 10:06 +0200

Page 2 of 2 — ← Prev page 1 [2]


#93957

FromTerry Reedy <tjreedy@udel.edu>
Date2015-07-16 15:45 -0400
Message-ID<mailman.607.1437075969.3674.python-list@python.org>
In reply to#93914
On 7/16/2015 2:02 PM, Ethan Furman wrote:
> On 07/16/2015 06:35 AM, Antoon Pardon wrote:

>> Here is one way to do the odd, even example.
>>
>> def even(n):
>>      return odd_even('even', n)
>>
>> def odd(n):
>>      return odd_even('odd', n)
>>
>> def odd_even(fn, n):
>>      while fn is not None:
>>          if fn == 'even':
>>              fn, n = (None, True) if n == 0 else ('odd', n - 1)
>>          elif fn == 'odd':
>>              fn, n = (None, False) if n == 0 else ('even', n - 1)
>>          else:
>>              raise ValueError("Unknown state: %s" % fn)
>>      return n
>
> Wow -- definitely uglier and less easy to understand than the original
> (even if the original had had the error-checking).

What you call ugly is an example of the core of the proof that 
alternation and indefinite while looping are enough for turing 
completeness, and that no recursive syntax is needed.  Another way to 
put it is that while loops perform recursion in the operational sense, 
as opposed to the syntactical sense.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#93959

FromEthan Furman <ethan@stoneleaf.us>
Date2015-07-16 12:58 -0700
Message-ID<mailman.609.1437076730.3674.python-list@python.org>
In reply to#93914
On 07/16/2015 12:45 PM, Terry Reedy wrote:
> On 7/16/2015 2:02 PM, Ethan Furman wrote:
>> On 07/16/2015 06:35 AM, Antoon Pardon wrote:
>
>>> Here is one way to do the odd, even example.
>>>
>>> def even(n):
>>>      return odd_even('even', n)
>>>
>>> def odd(n):
>>>      return odd_even('odd', n)
>>>
>>> def odd_even(fn, n):
>>>      while fn is not None:
>>>          if fn == 'even':
>>>              fn, n = (None, True) if n == 0 else ('odd', n - 1)
>>>          elif fn == 'odd':
>>>              fn, n = (None, False) if n == 0 else ('even', n - 1)
>>>          else:
>>>              raise ValueError("Unknown state: %s" % fn)
>>>      return n
>>
>> Wow -- definitely uglier and less easy to understand than the original
>> (even if the original had had the error-checking).
>
> What you call ugly is an example of the core of the proof that alternation and indefinite
>  while looping are enough for turing completeness, and that no recursive syntax is needed.
> Another way to put it is that while loops perform recursion in the operational sense, as
>  opposed to the syntactical sense.

And we add new constructs and new language features to make certain ways of thinking/programming easier -- async and await come to mind. ;)  After all, we don't really even need while loops as we 
could get by with if and goto.

--
~Ethan~

[toc] | [prev] | [next] | [standalone]


#93997

FromRobin Becker <robin@reportlab.com>
Date2015-07-17 09:57 +0100
Message-ID<mailman.634.1437123479.3674.python-list@python.org>
In reply to#93914
On 16/07/2015 17:17, Ian Kelly wrote:
> On Thu, Jul 16, 2015 at 3:28 AM, Robin Becker <robin@reportlab.com> wrote:
.............
>>
>> ....
>> I believe the classic answer is Ackermann's function
>>
>> http://demonstrations.wolfram.com/RecursionInTheAckermannFunction/
>>
>> which is said to be not "primitive recursive" ie cannot be unwound into
>> loops; not sure whether that implies it has to be recursively defined or can
>> perhaps be broken down some other way.
>
that should have said "simple loops"


> My recollection -- and it's been awhile since I've studied
> computability theory so I may be distorting things here -- is that
> primitive recursive functions can be computed using for loops, i.e.
> loops where the number of iterations is bounded in advance, whereas
> non-primitive recursive functions require while loops.
>
I'm guessing, but is the implication that for loops can be specified finitely in 
advance, but while loops need some processing in the calculation to determine 
termination? I'm an engineer :(
-- 
Robin Becker

[toc] | [prev] | [next] | [standalone]


#93923

FromAlain Ketterlin <alain@universite-de-strasbourg.fr.invalid>
Date2015-07-16 13:23 +0200
Message-ID<87egk8qrb0.fsf@universite-de-strasbourg.fr.invalid>
In reply to#93857
Antoon Pardon <antoon.pardon@rece.vub.ac.be> writes:

> On 07/13/2015 05:44 PM, Th. Baruchel wrote:
>> Hi, after having spent much time thinking about tail-call elimination
>> in Python (see for instance http://baruchel.github.io/blog/ ), I finally
>> decided to write a module for that. You may find it at:
>>
>>   https://github.com/baruchel/tco
>>
>> Tail-call elimination is done for tail-recursion as well as for
>> continuation-passing style (cps) in a consistent syntax for both usages.
>>
>> Any tail-recursive function having the suitable format for being
>> embeddable in the Y combinator can be used here with no change at all
>> (with the main difference that tail-calls will be eliminated), but other
>> continuations can also be added
>
> Can you explain how you would do mutual recursive functions?
>
> Suppose I start with the following:
>
> def even(n):
>     True if n == 0 else odd(n - 1)
>
> def odd(n):
>     False if n == 0 else even(n - 1)
>
>
> How do I rewrite those with your module?

I'm not sure the module mentioned above has provision for that, but in
any case the Y combinator has a corresponding fixed-point combinator
called Y*. There is quite a bit of theory behind this, anybody
interested can google, e.g., "Y combinator mutual recursion", whose
first result is:

http://stackoverflow.com/questions/4899113/fixed-point-combinator-for-mutually-recursive-functions

-- Alain.

[toc] | [prev] | [next] | [standalone]


#93881

FromTerry Reedy <tjreedy@udel.edu>
Date2015-07-15 17:19 -0400
Message-ID<mailman.549.1436995214.3674.python-list@python.org>
In reply to#93763
On 7/15/2015 5:29 AM, Antoon Pardon wrote:
> On 07/13/2015 05:44 PM, Th. Baruchel wrote:
>> Hi, after having spent much time thinking about tail-call elimination
>> in Python (see for instance http://baruchel.github.io/blog/ ), I finally
>> decided to write a module for that. You may find it at:
>>
>>    https://github.com/baruchel/tco
>>
>> Tail-call elimination is done for tail-recursion as well as for
>> continuation-passing style (cps) in a consistent syntax for both usages.
>>
>> Any tail-recursive function having the suitable format for being
>> embeddable in the Y combinator can be used here with no change at all
>> (with the main difference that tail-calls will be eliminated), but other
>> continuations can also be added
>
> Can you explain how you would do mutual recursive functions?
> Suppose I start with the following:
>
> def even(n):
>      True if n == 0 else odd(n - 1)
>
> def odd(n):
>      False if n == 0 else even(n - 1)
>
> How do I rewrite those with your module?

I will not answer for Baruchel's tco module.  However, combining the two 
bodies and following the general rule of replacing tail recursive calls 
with assignments inside a while loop gives us

def even(n):
     return not odd(n)

def odd(n):
     while True:
         if not n:
             return False
         else:
             n  -= 1
         if not n:
             return True
         else:
             n -= 1

# which passes this test

print(even(0) is True, odd(0) is False,
       even(1) is False, odd(1) is True,
       even(2) is True, odd(2) is False,
       even(5) is False, odd(5) is True,
       even(6) is True, odd(6) is False)

I believe that this pattern should work with any set of mutually 
recursive functions that always call each other in cyclic order.  A more 
elaborate version does not have this limitation.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#93913

FromAntoon Pardon <antoon.pardon@rece.vub.ac.be>
Date2015-07-16 09:45 +0200
Message-ID<mailman.572.1437032728.3674.python-list@python.org>
In reply to#93763
On 07/15/2015 11:19 PM, Terry Reedy wrote:
> On 7/15/2015 5:29 AM, Antoon Pardon wrote:
>>
>> Can you explain how you would do mutual recursive functions?
>> Suppose I start with the following:
>>
>> def even(n):
>>      True if n == 0 else odd(n - 1)
>>
>> def odd(n):
>>      False if n == 0 else even(n - 1)
>>
>> How do I rewrite those with your module?
>
> I will not answer for Baruchel's tco module.  However, combining the
> two bodies and following the general rule of replacing tail recursive
> calls with assignments inside a while loop gives us
>
> def even(n):
>     return not odd(n)
>
> def odd(n):
>     while True:
>         if not n:
>             return False
>         else:
>             n  -= 1
>         if not n:
>             return True
>         else:
>             n -= 1
> [ ... ]
>
> I believe that this pattern should work with any set of mutually
> recursive functions that always call each other in cyclic order.  A
> more elaborate version does not have this limitation.
>

Nice of you to illustrate the warping involved.  ;-) 

-- 
Antoon Pardon.

[toc] | [prev] | [next] | [standalone]


#93955

FromTerry Reedy <tjreedy@udel.edu>
Date2015-07-16 15:34 -0400
Message-ID<mailman.606.1437075284.3674.python-list@python.org>
In reply to#93763
On 7/16/2015 3:45 AM, Antoon Pardon wrote:
> On 07/15/2015 11:19 PM, Terry Reedy wrote:
>> On 7/15/2015 5:29 AM, Antoon Pardon wrote:
>>>
>>> Can you explain how you would do mutual recursive functions?
>>> Suppose I start with the following:
>>>
>>> def even(n):
>>>       True if n == 0 else odd(n - 1)
>>>
>>> def odd(n):
>>>       False if n == 0 else even(n - 1)
>>>
>>> How do I rewrite those with your module?
>>
>> I will not answer for Baruchel's tco module.  However, combining the
>> two bodies and following the general rule of replacing tail recursive
>> calls with assignments inside a while loop gives us
>>
>> def even(n):
>>      return not odd(n)
>>
>> def odd(n):
>>      while True:
>>          if not n:
>>              return False
>>          else:
>>              n  -= 1
>>          if not n:
>>              return True
>>          else:
>>              n -= 1
>> [ ... ]
>>
>> I believe that this pattern should work with any set of mutually
>> recursive functions that always call each other in cyclic order.  A
>> more elaborate version does not have this limitation.
>>
>
> Nice of you to illustrate the warping involved.  ;-)

Glad you appreciate it. To me, the warping is no more than, and perhaps 
less than, and certainly less obnoxious than, the warping required when 
using Baruchel's tco module.  (Opinions can vary, of course.)  The 
result will definitely run faster than with B's tco.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#93956

FromMarko Rauhamaa <marko@pacujo.net>
Date2015-07-16 22:45 +0300
Message-ID<87lhefanui.fsf@elektro.pacujo.net>
In reply to#93955
Nobody seemed to notice that I just posted a fairly typical tail call
function:

========================================================================
    def setvalue(self, keyseq, value, offset=0):
        try:
            next = keyseq[offset]
        except IndexError:
            self.value = value
            return
        if next is Node.ANY:
            raise KeyError()
        try:
            child = self.children[next]
        except KeyError:
            self.children[next] = child = Node()
        child.setvalue(keyseq, value, offset + 1)
========================================================================

In the context, the tail call is at least as clear as the corresponding
while/for loop.

In the case of this function, tail call elimination is hardly needed
because the key sequence is probably not all that long (and if it were,
the accompanying lookup function would overflow the stack anyway). At
any rate, it demonstrates how the idiom has its place in Python.


Marko

[toc] | [prev] | [next] | [standalone]


#94027

FromTerry Reedy <tjreedy@udel.edu>
Date2015-07-17 15:47 -0400
Message-ID<mailman.656.1437162474.3674.python-list@python.org>
In reply to#93956
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. Example:
     return len(self.children)

Even tail operations like
     return a + b
are tail calls if rewritten as
     return a.__add__(b)
(usually but not always equivalent) or rewritten as
     return operator.add(a, b)
(always equivalent) or compiled by an implementation as an equivalent 
internal function call.

It happens to be that CPython implements calls to symbol operator 
functions by putting the bodies of such functions into a C switch inside 
a while loop.  It this compiles operator symbols into bytecodes that 
cause a jump to the corresponding code.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#94032

FromMarko Rauhamaa <marko@pacujo.net>
Date2015-07-17 23:55 +0300
Message-ID<877fpya4hy.fsf@elektro.pacujo.net>
In reply to#94027
Terry Reedy <tjreedy@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 but that one did happen to
be recursive. It could easily have been replaced with a while loop but
there were good aesthetic reasons to leave it recursive.


Marko

[toc] | [prev] | [next] | [standalone]


#94042

FromTerry Reedy <tjreedy@udel.edu>
Date2015-07-17 20:40 -0400
Message-ID<mailman.669.1437180088.3674.python-list@python.org>
In reply to#94032
On 7/17/2015 4:55 PM, Marko Rauhamaa wrote:
> Terry Reedy <tjreedy@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

[toc] | [prev] | [next] | [standalone]


#94066

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2015-07-19 10:39 +1200
Message-ID<d102t8F8rq4U3@mid.individual.net>
In reply to#94042
Terry Reedy wrote:
> Problems with branching recursion (multiple recursive calls per 
> call) are rare except for very deep trees and graphs.

And if your recursion is branching, tail calls won't help
you, except in certain special cases (such as the quicksort
example) where you can predict in advance which branches
are safe to recurse down.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#94044

FromChris Angelico <rosuav@gmail.com>
Date2015-07-18 10:47 +1000
Message-ID<mailman.670.1437180453.3674.python-list@python.org>
In reply to#94032
On Sat, Jul 18, 2015 at 10:40 AM, Terry Reedy <tjreedy@udel.edu> wrote:
> 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.

More serious concern: If it's possible for one of the children to be
an arbitrary subclass of that type, it could have overridden the
setvalue method. To maintain the expectations, you absolutely have to
do the full lookup and pass the message down the chain.

ChrisA

[toc] | [prev] | [next] | [standalone]


#94065

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2015-07-19 10:39 +1200
Message-ID<d102t5F8rq4U2@mid.individual.net>
In reply to#94032
Marko Rauhamaa wrote:

> I don't know how recursion makes a difference

It makes a difference because people don't expect frames
to disappear from tracebacks when they're not consciously
doing recursion.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#94064

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2015-07-19 10:39 +1200
Message-ID<d102t1F8rq4U1@mid.individual.net>
In reply to#93956
Marko Rauhamaa wrote:
> At any rate, it demonstrates how the idiom has its place in Python.

Perhaps it does, but I think I'd still prefer it to be
explicit.

The call in Marko's example is not actually a tail call
as written. To make it a tail call, a return would need
to be added:

 >       return child.setvalue(keyseq, value, offset + 1)

To someone reading the code, it's not obvious why the
return is there. It looks redundant, and is likely to
get removed by someone who thinks it's a mistake.

Using a dedicated keyword would make it clear that tail
call behaviour is being relied upon, and avoid looking
like a spurious return.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#94073

FromMark Lawrence <breamoreboy@yahoo.co.uk>
Date2015-07-19 01:09 +0100
Message-ID<mailman.687.1437264610.3674.python-list@python.org>
In reply to#94064
On 18/07/2015 23:39, Gregory Ewing wrote:
> Marko Rauhamaa wrote:
>> At any rate, it demonstrates how the idiom has its place in Python.
>
> Perhaps it does, but I think I'd still prefer it to be
> explicit.
>
> The call in Marko's example is not actually a tail call
> as written. To make it a tail call, a return would need
> to be added:
>
>  >       return child.setvalue(keyseq, value, offset + 1)
>
> To someone reading the code, it's not obvious why the
> return is there. It looks redundant, and is likely to
> get removed by someone who thinks it's a mistake.

A time to use perhaps the most abused part of any programming language, 
a comment?

>
> Using a dedicated keyword would make it clear that tail
> call behaviour is being relied upon, and avoid looking
> like a spurious return.
>

+1

-- 
My fellow Pythonistas, ask not what our language can do for you, ask
what you can do for our language.

Mark Lawrence

[toc] | [prev] | [next] | [standalone]


#94075

FromMRAB <python@mrabarnett.plus.com>
Date2015-07-19 01:19 +0100
Message-ID<mailman.689.1437265194.3674.python-list@python.org>
In reply to#94064
On 2015-07-18 23:39, Gregory Ewing wrote:
> Marko Rauhamaa wrote:
>> At any rate, it demonstrates how the idiom has its place in Python.
>
> Perhaps it does, but I think I'd still prefer it to be
> explicit.
>
> The call in Marko's example is not actually a tail call
> as written. To make it a tail call, a return would need
> to be added:
>
>   >       return child.setvalue(keyseq, value, offset + 1)
>
> To someone reading the code, it's not obvious why the
> return is there. It looks redundant, and is likely to
> get removed by someone who thinks it's a mistake.
>
> Using a dedicated keyword would make it clear that tail
> call behaviour is being relied upon, and avoid looking
> like a spurious return.
>
Of the current reserved words, the choice is between 'continue' and
'pass'.

How clear is:

     continue child.setvalue(keyseq, value, offset + 1)

?

I think the problem is that it reads like it's something to do with
co-routines.

[toc] | [prev] | [next] | [standalone]


#94100

FromMarko Rauhamaa <marko@pacujo.net>
Date2015-07-19 09:29 +0300
Message-ID<87y4ic7j8m.fsf@elektro.pacujo.net>
In reply to#94064
Gregory Ewing <greg.ewing@canterbury.ac.nz>:

> Marko Rauhamaa wrote:
>> At any rate, it demonstrates how the idiom has its place in Python.
>
> Perhaps it does, but I think I'd still prefer it to be explicit.

Don't get me wrong. The method doesn't depend on tail call elimination.
It couldn't because its sibling method has the same recursion depth
without the possibility of tail call elimination.

I was just showing an example of an iteration expressed idiomatically
through a tail call.

> The call in Marko's example is not actually a tail call as written. To
> make it a tail call, a return would need to be added:
>
>>       return child.setvalue(keyseq, value, offset + 1)

Adding the return statement there would be bad because it would suggest
to the maintainer of the code that the method has a meaningful return
value, which it doesn't.

> To someone reading the code, it's not obvious why the return is there.
> It looks redundant, and is likely to get removed by someone who thinks
> it's a mistake.

Exactly. That's why I said also that it is unfortunate for Python to
promise to return None implicitly. That stipulation of the language
specification is against the de-facto semantics of procedures (your
sentence above) and effectively prevents automatic tail call
optimizations.

> Using a dedicated keyword would make it clear that tail call behaviour
> is being relied upon, and avoid looking like a spurious return.

A dedicated keyword would indeed work as advertised. However, I'm not
too keen on the idea. My position is: Python probably should have made
tail call elimination a matter of course, but since it doesn't, oh well.


Marko

[toc] | [prev] | [next] | [standalone]


#93996

FromAntoon Pardon <antoon.pardon@rece.vub.ac.be>
Date2015-07-17 10:06 +0200
Message-ID<mailman.633.1437120370.3674.python-list@python.org>
In reply to#93763
On 07/16/2015 09:34 PM, Terry Reedy wrote:
> On 7/16/2015 3:45 AM, Antoon Pardon wrote:
>> On 07/15/2015 11:19 PM, Terry Reedy wrote:
>>>
>>> I believe that this pattern should work with any set of mutually
>>> recursive functions that always call each other in cyclic order.  A
>>> more elaborate version does not have this limitation.
>>>
>>
>> Nice of you to illustrate the warping involved.  ;-)
>
> Glad you appreciate it. To me, the warping is no more than, and
> perhaps less than, and certainly less obnoxious than,the warping
> required when using Baruchel's tco module.  (Opinions can vary,
> of course.)  The result will definitely run faster than with B's tco.

I don't care about the speed that much. Clarity of code is more important.
And I agree how Baruchel's tco module needs to be used, doesn't seem
very helpful in that respect. I wouldn't call it obnoxious, cause I
can appreciate the mathematical elegance behind it, but my impression is
that understanding what is going on, requires a back ground knowledge
that is generally not expected. So using this would probably be good for
job security but wouldn't be fair to my colleagues.

-- 
Antoon Pardon

[toc] | [prev] | [standalone]


Page 2 of 2 — ← Prev page 1 [2]

Back to top | Article view | comp.lang.python


csiph-web