[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: requiring proper tail recursion



I've edited Richard's text to define tail calls using contexts.
Please consider the enclosed specification.

John

----------------

Implementations of Scheme are required to be properly tail-recursive.
With a tail-recursive implementation, iteration can be expressed using
the ordinary procedure-call mechanics, so that special iteration
constructs are useful only as abbreviations.  Procedure-calls that
occur in a special context, called a tail context, are the ones used
to specify an iterative computation.  Procedure-calls that occur in a
tail context are called tail calls.  An implementation is
tail-recursive if the continuation passed to the procedure invoked
from every tail call is that which was passed to the enclosing
procedure.

A tail context is defined inductively.

* Top-level is a tail context.

* If a lambda expression occurs in any context, the subexpression
  labeled T is a tail context.

	(lambda F E ... T)

* If one of the following expressions occurs in a tail context,
  the subexpression labeled T is a tail context.

  (if E T T)
  (if E T)

  (let-syntax ((I Trans) ...) E ... T)
  (letrec-syntax ((I Trans) ...) E ... T)

  (begin E ... T)

  (let ((I E) ...) E ... T)
  (let I ((I E) ...) E ... T)
  (letrec ((I E) ...) E ... T)

  (cond CoCl ...)
  (cond CoCl ... (else E ... T))
     CoCl ::= (E E ... T)

  (case CaCl ...)
  (case CaCl ... (else E ... T))
     CaCl ::= ((D D ...) E ... T)

  (and E ... T)
  (or E ... T)

  (do (DoC ...) (E E ... T) E ...)

  If a COND expression has a clause of the form (E0 => E1) then the
  (implied) application of E1 is in a tail context within the COND
  expression.  E1 itself is not in a tail context.

Using the above definition of tail contexts, in the following code
only the call to F is a tail call; none of the calls to G are tail
calls.  The reference to X is in a tail context, but it is not a call
and thus not a tail call.

  (lambda ()
    (if (g)
        (let ((x (g)))
          x)
        (and (g) (f))))

Implementations are also required to have certain built-in procedures
invoke one of their arguments using a tail call: the first argument of
APPLY and CALL-WITH-CURRENT-CONTINUATION, and the second argument of
CALL-WITH-VALUES.  Similarly, EVAL must evaluate its argument as if it
were in a tail context.  As an example, the following expression should
loop endlessly:

    (let ((exp '(lambda (exp)
                  (eval `(apply ,exp '(,exp))
                        (scheme-report-environment 5)))))
      (eval `(,exp ',exp)
            (scheme-report-environment 5)))

Note: The tail-recursion requirement exists because certain
implementation techniques used for other languages, such as pushing a
frame on a stack for all calls, whether tail calls or not, make it
difficult to predict the behavior of loops expressed using recursion.
Also note that in a particular tail-recursive implementation, other
procedure-calls may behave as tail calls, but programmers may only
rely on tail calls for iteration in all implementations of Scheme.

----------------

Comment:  The final sentence in the first paragraph, 

An implementation is tail-recursive if the continuation passed to the
procedure invoked from a tail call is that which was passed to the
enclosing procedure.

can only be used if we define the notion of a continuation.  I think
the sentence probably should be replaced, but I cannot think of a
replacement now.