[racket] Tail recursive module cons

From: Prabhakar Ragde (plragde at uwaterloo.ca)
Date: Sat Mar 29 08:06:28 EDT 2014

Yuhao Dong wrote:

> The thing is, Racket's "stack" is a list of continuations. I don't see
> how explicitly keeping the "stack" in an accumulator would help. Stack
> overflow won't be a problem since the "stack" is a list on the heap, and
> unless memory runs out you wont overflow.

> I think that tail recursion doesn't help at all, and introduces
> conceptual overhead. Racket doesn't use the stack, and converts to
> continuation-passing, which is surprise-surprise *tail recursive* at
> runtime anyway.

Yuhao, I think you got some of these ideas from lectures I gave almost 
exactly a year ago, but you may have forgotten or misunderstood some 
points I made at the time:

* Simply making an interpreter tail-recursive does not guarantee that 
that the interpreted program will use memory wisely. It only ensures 
that there is no stack overhead due to recursive applications of the 
interpreter itself (either through tail-call optimization [TCO] in the 
language the interpreter is written in, or trampolining/looping in a 
language that does not support TCO).

* The way in which we made the interpreter tail-recursive, by using a 
zipper on the AST which introduced a list of continuations to manage 
control, did ensure that if the interpreted program was tail-recursive, 
the list of continuations would not grow. We could see this by looking 
at the interpreter code and noting that continuations grow only when 
arguments are evaluated down to values and not when a function is 
applied to a value. However, if the interpreted program is not 
tail-recursive, the list of continuations will grow, because of the 
pending computation in the interpreted program. Furthermore, because 
that list of continuations is on the heap instead of the stack, there 
will be overhead associated with allocation and garbage collection.

* The development in lecture was an illustration of one way of achieving 
the goal of TCO in an imperative implementation that could be understood 
after six months of exposure to functional programming in Racket. But 
Racket itself has a more sophisticated implementation that incorporates 
more advanced ideas and optimizations. It does use the stack as well as 
the heap, but not in the simple way that a direct translation of the 
naive (non-tail-recursive) interpreter would.

I hope that helps clarify things. The lecture material was my attempt at 
giving you a sense of the ideas behind Racket's implementation, but it 
was not a description of what Racket actually does under the hood. That 
would probably take a whole course on its own, and a better teacher. --PR

Posted on the users mailing list.