On 11/12/2018 10:45 AM, Rudi C wrote:
How is mutual recursion via a loop any better than mutual recursion just by tail call elimination?

You can't necessarily turn recursion into a loop using only TCE.

TCE turns an ordinary call/return sequence into a non-returning jump [or fall through for inline code] to the target function.  In the recursive it jumps back to the "start" of the function [for some definition of "start"].

But there's a catch:  you can't just overwrite the current stack frame with your new one because, typically, you need information from the current frame *while* constructing the new one ... in particular, you need to evaluate the recursive call arguments.  So how much trouble TCE and TRO represent depend crucially on how you implement your stack.


If you allocate the stack as a linked list on the heap, then there is little difference between a tail call and a normal one - in either case you allocate a new frame but a TC links the new frame to its grandparent instead of its parent, and the parent frame is unlinked and discarded.  The downsides to this stack strategy are that stack frames come and go rapidly, and having to handle them puts more pressure on the garbage collector. Moreover using C (or other conventional language) libraries becomes more complicated if you aren't using the CPU's own hardware stack.


If you implement your stack as an array [the classic mark/release allocation strategy] then you can use the CPU's hardware stack, but in this case a tail call is very different from a normal one.  The arguments to the call often can't be evaluated directly into the positions where the function code expects them to be, so you need additional memory to stage the evaluations before moving the resulting values into place.

Additionally, to implement TRO with an array stack, you may need to generate ad hoc code to initialize the loop, and ad hoc code to update loop variables and reenter the loop as necessary.

[There is a trick involving CPS code in which the CPU's hardware stack is abused by allocating frames but never deallocating them - iow calling functions but never returning from them.  When the stack grows to some predetermined extent, you copy the top frame back to the base of the stack and reset the stack pointer, effectively deallocating all the discarded stack frames at once.  This has many of the same benefits as allocating the stack on the heap, with the additional benefit that it mixes well with use of conventional libraries.  The problem is, it plays hell with fetch prediction mechanisms that expect conventional call/return code of rather limited depth - so code using this trick often executes more slowly than more conventional call/return code.]


In a mutual recursion case, the transformation to a loop may subsume many functions, and there may be multiple branching recursive execution paths.  The analysis required to identify and fuse mutually recursive functions into one "super" function that can be made self recursive is possible, but in the general case it is very difficult to do.



PS: Why do web-servers need CPS?

In general they don't.  However, being a Scheme, Racket supports arbitrary use of continuations, and the web-server framework allows the handler for a given URL to be the persisted continuation of another URL.

Persisting a continuation involves saving the entire state of the ongoing computation - in particular, it means saving the call/return stack which is needed to continue executing from the save point. In CPS code, the stack state consists of just one frame - the current one - rather than an arbitrary number of frames.  So, in general, in CPS code a continuation requires far less memory to persist.

That is why  #lang web-server  converts programs to CPS.  If your code doesn't make heavy use of *general* continuations (co-routines), or threads with continuations, or need to persist continuations for arbitrarily long periods, then using the web-server language buys you very little [in trade for much longer compilations].


George

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to