Newsgroups: comp.lang.scheme
Path: cantaloupe.srv.cs.cmu.edu!das-news.harvard.edu!news2.near.net!MathWorks.Com!europa.eng.gtefsd.com!howland.reston.ans.net!newsserver.jvnc.net!nntpserver.pppl.gov!princeton!nimaster.princeton.edu!blume
From: blume@beth.princeton.edu (Matthias Blume)
Subject: Re: Scheme _in_ Emacs?
In-Reply-To: harrison@sp10.csrd.uiuc.edu's message of 06 Sep 1994 00:34:13 GMT
Message-ID: <BLUME.94Sep5212814@beth.princeton.edu>
Followup-To: comp.lang.scheme
Originator: news@nimaster
Sender: news@Princeton.EDU (USENET News System)
Nntp-Posting-Host: beth.cs.princeton.edu
Organization: Princeton University
References: <RAMSDELL.94Aug17063434@triad.mitre.org> <TTHORN.94Aug17150829@ceres.daimi>
	<RAMSDELL.94Aug24085039@triad.mitre.org>
	<WGD.94Aug25012615@martigny.ai.mit.edu>
	<TFB.94Aug31020302@oliphant.cogsci.ed.ac.uk>
	<WGD.94Sep3205634@martigny.ai.mit.edu>
	<TFB.94Sep5132531@burns.cogsci.ed.ac.uk>
	<HARRISON.94Sep5085451@sp10.csrd.uiuc.edu>
	<BLUME.94Sep5110232@beth.princeton.edu>
	<HARRISON.94Sep5193413@sp10.csrd.uiuc.edu>
Date: Tue, 6 Sep 1994 01:28:14 GMT
Lines: 138

In article <HARRISON.94Sep5193413@sp10.csrd.uiuc.edu> harrison@sp10.csrd.uiuc.edu (Luddy Harrison) writes:

   In any event my point was this.  When the original poster writes:

      >>>You can't optimize a tail call
      >>>by throwing away the stack frame of the function because the function
      >>>you call can legitimately refer to the bindings you have made.

   he seems to suggest that the problem is with dynamic binding; but the
   same problem occurs (can't deallocate the caller's stack frame on a
   tail-recursive call) any time that data allocated in a caller's
   activation is needed by a callee.

No, it is *not* the same.  A closure is a data structure, which is
constructed by the user program.  Read the definition of the semantics
of LAMBDA!  Allocating space for LAMBDA is in the same category as
allocating space for, say, CONS.

				      In Scheme this arises when
   variables are captured by closure and passed to the callee; to solve
   it, variables captured by closure are put in the heap.

They have to be put into the heap anyway (at least in general),
because of the UPARG problem:

	(define (compose f g) (lambda (x) (f (g x))))

Even in a conventional (not properly tail-recursive) implementation
the stack frame for the call to COMPOSE goes away before the values of
F and G are needed.

                                                          If dynamically
   bound variables were similarly allocated on the heap (e.g., an alist
   representing the dynamic environment were passed from caller to
   callee), then dynamic binding would likewise not conflict with
   tail-recursion optimization.

This is indeed true, but pretty pointless, because it only matters
(space-wise), if the tail-recursive function doesn't have any local
variables or arguments.

   In summary: the problem is with stack-allocation of data, not with
   dynamic binding.

This is a vacuous statement.  Tail-recursion optimization deals with
optimizing certain stack allocations.  If you don't use a stack then
you don't need to do this.

   Now, the second question is whether proper tail recursion is a
   meaningful mandate.

Well, you carefully deleted all my explanation of tail-recursion and
its correspondence to certain eta-reductions in the CPS.  In fact, I
think you are making up artificial problems where no real problems
are.  Being properly tail-recursive for an implementation means that a
tail-recursive computation runs in constant space as far as the
control data structures are concerned.  Control data structures are
those, which are not explicitely constructed by the source program,
but which have to be introduced by the interpreter/compiler in order
to maintain the proper order of execution.

(Going to CPS gets rid of all implicit data structures by making them
explicit.  This way one can talk about those data structures without
going into details of a particular implementation.)

   Is it really so easy to divide the data into "that which is introduced
   implicitly to manage the control-flow" and <all the other data>?  If
   we consider the old example

	   (define fact (lambda (n k)
	      (if (= n 1)
		  (k 1)
		  (fact (- n 1) (lambda (m) (k (* n m)))))))

	   (fact N)

   there are lots of implementation choices.

Well, this example is already tail-recursive (because it is (almost)
in CPS), so (almost) no control data structures must be maintained.
(I say ``almost'', because the calls to =, -, and * do not follow the
conventions of CPS.)

   A) We could do the usual thing, i.e., CPS conversion and heap-storage for 
   closures.

CPS is not necessary for an implementation to achieve proper
tail-recursion.  Many existing Scheme implementation (including my
own) do not go through CPS.  Heap-storage for closures, however, cannot
be avoided in general, but this doesn't have anything to do with the
problem at hand.

   B) We could stack the activation records and implement the inner lambda
   like a nested function in Pascal.

We could do this, but such an implementation would not be
standard-conforming.

   C) (Being very fancy:) we could recognize the underlying recurrence relation
      x[n] = x[n-1]*n
      x[1] = 1
      and build a parallel computation that solved the problem in log(N) space,
      and log(N) time.

log(N) space is certainly not allowed for a conforming implementation.
A different ``fancy'' optimization could transform the program into:

	(define (fac n k)
	  (define (loop n f)
	    (if (zero? n) f (loop (- n 1) (* n f))))
	  (k (loop n 1)))

which really runs in constant space without doing any
heap-allocations.  This would be legal.

   D) we could look the answer up in a table of all the
   <(Scheme programs) X (input data)> that have ever been run on our system,
   and record the outcome of this computation in the table if it is not found.

Memoization is not permitted, because of

	1. space considerations (the cache *is* a control data
	   structure, because it is not explicit in the source code)
	2. side effects

To finish this up let me say this:  Let's not beat a dead horse any
further.  I don't see why you have such a problem with the concept of
proper tail-recursion.  Admittedly, the report is a bit vague about
this issue, which is something that has been discussed elsewhere (see
my reference to Appel's book in my last post).  I guess the authors of
the report didn't feel it would be necessary to elaborate on this
problem.  The only reason it was mentioned at all is that they wanted
to be sure that they can get rid of iterative constructs altogether by
efficiently replacing them with recursion.

Regards, over and out
--
-Matthias
