From: Aleksandr Skobelev
Subject: Tail recursion & CL
Date: 
Message-ID: <tvmtn9.qq2.ln@hermit.athome>
I wonder why CL, as I read somewhere, is not properly tail recursive?  
If wasn't there Scheme at that moment when the CL ANSI standard has been
adopted, and if it Scheme wasn't claimed as a proper recursive language?

    Thank you in advance,
    Aleksandr

From: Barry Margolin
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <cBto7.40$7o6.282@burlma1-snr2>
In article <·············@hermit.athome>,
Aleksandr Skobelev  <····@mail.ru> wrote:
>I wonder why CL, as I read somewhere, is not properly tail recursive?  
>If wasn't there Scheme at that moment when the CL ANSI standard has been
>adopted, and if it Scheme wasn't claimed as a proper recursive language?

The designers of CL considered this to be an optimization that implementors
can use, rather than something that should be required by the language.
The dialects of Lisp that Common Lisp was designed based on were not
tail-recursive, and it wasn't considered a significant defect.

In my experience, most recursive functions are not written in a
tail-recursive manner, so the benefit is not even that significant.
Writing tail-recursive code often requires contorting the way the algorithm
is expressed.  For instance, the simple recursive factorial:

(defun fact (n)
  (if (<= n 2)
      1
      (* n (fact (1- n)))))

must become:

(defun fact (n)
  (flet ((fact-internal (n accumulator)
           (if (<= n 2)
               accumulator
               (fact-internal (1- n) (* n accumulator)))))
    (fact-internal n 1)))

The reason why Scheme needs to be tail-recursive is because this is the
only means of control flow that it provides; it doesn't have TAGBODY/GO,
CATCH/THROW, or BLOCK/RETURN-FROM.  Instead, it subsumes all of these with
continuations and function calling, and tail-call elimination is necessary
so that it works similarly to the operations that are being replaced.
Common Lisp has all these as standard features of the language, so
programmers aren't likely to try to use function calling in place of them.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvofod32bo.fsf@conquest.OCF.Berkeley.EDU>
Barry Margolin <······@genuity.net> writes:

> In article <·············@hermit.athome>,
> Aleksandr Skobelev  <····@mail.ru> wrote:
> >I wonder why CL, as I read somewhere, is not properly tail recursive?  
> >If wasn't there Scheme at that moment when the CL ANSI standard has been
> >adopted, and if it Scheme wasn't claimed as a proper recursive language?
> 
> The designers of CL considered this to be an optimization that implementors
> can use, rather than something that should be required by the language.
> The dialects of Lisp that Common Lisp was designed based on were not
> tail-recursive, and it wasn't considered a significant defect.

And it's an easy enough optimization that you can implement it
yourself if you can't trust the implementation you're deploying on to
do it for you.

> In my experience, most recursive functions are not written in a
> tail-recursive manner, so the benefit is not even that significant.
> Writing tail-recursive code often requires contorting the way the algorithm
> is expressed.  For instance, the simple recursive factorial:
> 
> (defun fact (n)
>   (if (<= n 2)
>       1
        ^-- you meant n, of course :)
>       (* n (fact (1- n)))))
> 
> must become:
> 
> (defun fact (n)
>   (flet ((fact-internal (n accumulator)
     ^^^^-- you meant LABELS, of course
>            (if (<= n 2)
>                accumulator
>                (fact-internal (1- n) (* n accumulator)))))
>     (fact-internal n 1)))

Yep, that's certainly unnecessarily obscure.  I find recursion over a
data stack to be a far more readable way to transform recursive
functions:

(defun fact (n)
    (let ((stack ()))
      (loop (cond
              ((<= n 2)
	       (push n stack)
	       (return))
              (t (push n stack)
		 (setf n (1- n)))))
      (reduce #'* stack)))

Of course, I think the value of tail-call optimization comes not in
simple cases of self calls, but for getting cases of n-mutual tail
calls.  
From: Frank A. Adrian
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2kBo7.1924$7I2.621658@news.uswest.net>
Barry Margolin wrote:
For instance, the simple recursive factorial:
> must become:
> 
> (defun fact (n)
>   (flet ((fact-internal (n accumulator)
>            (if (<= n 2)
>                accumulator
>                (fact-internal (1- n) (* n accumulator)))))
>     (fact-internal n 1)))

Can't this just be done as:

(defun fact (n &optional (acc 1))
  (if (< n 2)
    acc
    (fact (1- n) (* n acc))))

allowing tail recursion without (as much) pain?  Or does the use of 
optional parameters mess that up?

faa
From: Barry Margolin
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <0cop7.6$0s2.2973@burlma1-snr2>
In article <·····················@news.uswest.net>,
Frank A. Adrian  <·······@qwest.net> wrote:
>Barry Margolin wrote:
>For instance, the simple recursive factorial:
>> must become:
>> 
>> (defun fact (n)
>>   (flet ((fact-internal (n accumulator)
>>            (if (<= n 2)
>>                accumulator
>>                (fact-internal (1- n) (* n accumulator)))))
>>     (fact-internal n 1)))
>
>Can't this just be done as:
>
>(defun fact (n &optional (acc 1))
>  (if (< n 2)
>    acc
>    (fact (1- n) (* n acc))))
>
>allowing tail recursion without (as much) pain?  Or does the use of 
>optional parameters mess that up?

Yes, it could be written that way.  But since the accumulator isn't
supposed to be part of the public interface of the function, it seems wrong
to have it there.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Kalle Olavi Niemitalo
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87ofo9ydzr.fsf@Astalo.y2000.kon.iki.fi>
Barry Margolin <······@genuity.net> writes:

> But since the accumulator isn't supposed to be part of the
> public interface of the function, it seems wrong to have it
> there.

We can hide it:

  (defun fact (n &key ((#1=#:acc acc) 1))
    (if (< n 2)
        acc
      (fact (1- n) '#1# (* n acc))))

;-)

Has any of you written a macro that gensyms parameter keyword names?
From: Geoff Summerhayes
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <tqcp64h3gd2bbe@corp.supernews.com>
"Kalle Olavi Niemitalo" <···@iki.fi> wrote in message
···················@Astalo.y2000.kon.iki.fi...
> Barry Margolin <······@genuity.net> writes:
>
> > But since the accumulator isn't supposed to be part of the
> > public interface of the function, it seems wrong to have it
> > there.
>
> We can hide it:
>
>   (defun fact (n &key ((#1=#:acc acc) 1))
>     (if (< n 2)
>         acc
>       (fact (1- n) '#1# (* n acc))))
>

Hidden? Not really...

<LW>

CL-USER 1 > (defun fact (n &key ((#1=#:acc acc) 1))
              (if (< n 2)
                  acc
                (fact (1- n) '#1# (* n acc))))
FACT

CL-USER 2 > (function-lambda-list 'fact)
(N &KEY ((#:ACC ACC) 1))

CL-USER 3 > (fact 5 (first (first (third (function-lambda-list
'fact)))) -1)
-120

<CLISP v2.27 function-lambda-list not defined>

(fact 5 (first (first (third (second (function-lambda-expression
#'fact))))) -1)
-120

-----------
Geoff
From: Kalle Olavi Niemitalo
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87u1y1t5b6.fsf@Astalo.y2000.kon.iki.fi>
"Geoff Summerhayes" <·············@hNoOtSmPaAiMl.com> writes:

> <CLISP v2.27 function-lambda-list not defined>

Not in CLHS either.  Is it expected to reveal &AUX variables too?

> (fact 5 (first (first (third (second (function-lambda-expression
> #'fact))))) -1)

That's cheating!  If you use FUNCTION-LAMBDA-EXPRESSION then not
even LABELS is hidden.
From: Geoff Summerhayes
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <tqf1c1d042kaf3@corp.supernews.com>
"Kalle Olavi Niemitalo" <···@iki.fi> wrote in message
···················@Astalo.y2000.kon.iki.fi...
> "Geoff Summerhayes" <·············@hNoOtSmPaAiMl.com> writes:
>
> > <CLISP v2.27 function-lambda-list not defined>
>
> Not in CLHS either.  Is it expected to reveal &AUX variables too?

Realized this after posting when I looked for it in the HS instead
of relying on apropos. *SIGH*

>
> > (fact 5 (first (first (third (second (function-lambda-expression
> > #'fact))))) -1)
>
> That's cheating!  If you use FUNCTION-LAMBDA-EXPRESSION then not
> even LABELS is hidden.

Is it possible call the function? The only ways I can think
of involve eval and that still leaves the actual helper
hidden.

This is all moot anyway since FUNCTION-LAMBDA-EXPRESSION is
not guaranteed to return something useful for either of our
examples.

It's a bit of a shame the FUNCTION-LAMBDA-LIST isn't in the
standard, I've found it very handy in my exploration of the
language, it succeeds in cases where FUNCTION-LAMBDA-EXPRESSION
returns nil for the expression. In fact, I haven't found a
reasonable case where it fails.

Geoff
From: Barry Margolin
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <hNNp7.22$N4.1066@burlma1-snr2>
In article <··············@corp.supernews.com>,
Geoff Summerhayes <·············@hNoOtSmPaAiMl.com> wrote:
>It's a bit of a shame the FUNCTION-LAMBDA-LIST isn't in the
>standard, I've found it very handy in my exploration of the
>language, it succeeds in cases where FUNCTION-LAMBDA-EXPRESSION
>returns nil for the expression. In fact, I haven't found a
>reasonable case where it fails.

FUNCTION-LAMBDA-LIST isn't in the standard, and FUNCTION-LAMBDA-EXPRESSION
isn't required to return anything useful, because we didn't want to
*require* implementations to retain all this source-code information at run
time.  While most implementations do include this type of information for
debugging purposes, they often have options when compiling or dumping
production executables that will prune it out.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Frank A. Adrian
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sXBp7.3107$Pz4.396928@news.uswest.net>
Barry Margolin wrote:

> In article <·····················@news.uswest.net>,
>>Barry Margolin wrote:

> Yes, it could be written that way.  But since the accumulator isn't
> supposed to be part of the public interface of the function, it seems
> wrong to have it there.
> 

I never said it was more pretty than the original recursive solution :-)!

I still prefer it over the use of the hideous (but more rigorous) 
tail-recursive flet example that provoked this.  Besides, if you use this 
technique to compute Fibonacci Numbers, you get a generator for Lucas 
Numbers and other generalized Fibonacci Numbers.

I.e., when

(defun fib (n &optional (base1 0) (base2 1))
  (if (= n 0)
    base2
    (fib (1- n) base2 (+ base1 base2))))

is invoked as (fib 1 3), you get the Lucas Numbers, etc.  Actually, the 
fact function coded the way I described gives a set of functions equivalent 
to integral multiples of the factorial function with the multiplier given 
by the base case - and who's to say that the function family isn't more 
interesting or proper (in the general mathematical sense)?

I guess my point is that, although the &optional method of coding tail 
recursion seems to be a "hacky" sort of thing, many times it might also be 
useful for generating a set of functions just as useful as the one used for 
the "normative" case.

faa
From: Aleksandr Skobelev
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <0ph0o9.ut.ln@hermit.athome>
Barry Margolin <······@genuity.net> wrote:
> In article <·············@hermit.athome>,
> Aleksandr Skobelev  <····@mail.ru> wrote:
>>I wonder why CL, as I read somewhere, is not properly tail recursive?  
>>If wasn't there Scheme at that moment when the CL ANSI standard has been
>>adopted, and if it Scheme wasn't claimed as a proper recursive language?
> 
> The designers of CL considered this to be an optimization that implementors
> can use, rather than something that should be required by the language.
> The dialects of Lisp that Common Lisp was designed based on were not
> tail-recursive, and it wasn't considered a significant defect.
> 
> In my experience, most recursive functions are not written in a
> tail-recursive manner, so the benefit is not even that significant.
> Writing tail-recursive code often requires contorting the way the algorithm
> is expressed.  For instance, the simple recursive factorial:
> 
> (defun fact (n)
>  (if (<= n 2)
>      1
>      (* n (fact (1- n)))))
> 
> must become:
> 
> (defun fact (n)
>  (flet ((fact-internal (n accumulator)
>           (if (<= n 2)
>               accumulator
>               (fact-internal (1- n) (* n accumulator)))))
>    (fact-internal n 1)))
> 
> The reason why Scheme needs to be tail-recursive is because this is the
> only means of control flow that it provides; it doesn't have TAGBODY/GO,
> CATCH/THROW, or BLOCK/RETURN-FROM.  Instead, it subsumes all of these with
> continuations and function calling, and tail-call elimination is necessary
> so that it works similarly to the operations that are being replaced.
> Common Lisp has all these as standard features of the language, so
> programmers aren't likely to try to use function calling in place of them.
>

Thanks. So, as far as I understood, the reason of why CL hasn't been claimed 
"properly tail recursive is an existence of constructs TAGBODY/GO, CATCH/THROW, 
or BLOCK/RETURN-FROM, and binding of special variables  where tail recursion 
is inhibited? (The last point I've found in CMU CL User's Manual. I
definitely should read it before). :)

In other cases Lisp compilers might be "properly tail recursive" ones. At
least CMU CL Python compiler is a such. And I believe that this compiler
can also transformi some non tail recursive forms like FACT in your example
into tail recursive ones. And with such compiler one need not do manual
transformation into tail recursive form. But because it is not required by
specification one can't also rely on such behaviour working with any other 
compiler. Am I wrong?


The reason because I've started asking about TR is that devil and foolish
benchmarking. I've compared  speed of calculation of recursive algorithm for 
ackermann function with parameters 3 12 for different compilators and 
different languages. On my computer I've got 
for gcc++  ~48 sec
for gforth ~40 sec
for cmucl  ~32 sec ( I was a little proud) :)
for ocaml and clean ~3 sec! 8-( )  (I couldn't believe)

I think that such good results in the last case can be explaned only by
ability of ocaml and clean compilers to transform  call (func n (func m
l)) at the end of form into tail recursive form. Because in same
benchmarking of calulating Fibonnachi numbers cmucl wins ocaml with a
little advantge. 


I must say that it is mainly my own thoughts and guess-works which based
on very little expertise on Lisp, ocaml and recursion. :) So any comments
are appreciated and welcome.

    Aleksandr
From: Barry Margolin
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <Llop7.7$0s2.2988@burlma1-snr2>
In article <············@hermit.athome>,
Aleksandr Skobelev  <····@mail.ru> wrote:
>The reason because I've started asking about TR is that devil and foolish
>benchmarking. I've compared  speed of calculation of recursive algorithm for 
>ackermann function with parameters 3 12 for different compilators and 
>different languages. On my computer I've got 
>for gcc++  ~48 sec
>for gforth ~40 sec
>for cmucl  ~32 sec ( I was a little proud) :)
>for ocaml and clean ~3 sec! 8-( )  (I couldn't believe)
>
>I think that such good results in the last case can be explaned only by
>ability of ocaml and clean compilers to transform  call (func n (func m
>l)) at the end of form into tail recursive form. Because in same
>benchmarking of calulating Fibonnachi numbers cmucl wins ocaml with a
>little advantge. 

I don't think tail recursion would normally be expected to result in much
of a speedup of the program.  Pushing a stack frame is not that expensive,
and a tail-recursive call still has to copy the parameters just like a
non-TR call.  The final return will be faster, since it's not necessary to
unwind so many stack frames, but I can't imagine that resulting in 1000%
performance improvement.  Tail recursion reduces stack space usage, which
could improve paging, but again I don't think it would be a significant
improvement; recent stack frames are almost always in RAM and cache, and
pushing and popping frames will only rarely produce page faults.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87k7yy25ru.fsf@pps.jussieu.fr>
Barry Margolin:

BM> In my experience, most recursive functions are not written in a
BM> tail-recursive manner, so the benefit is not even that significant.

I don't see tail recursion as a restricted case of recursion, but
rather as an alternative notation for iteration.

Saying that Common Lisp doesn't need tail recursion because it already
has TAGBODY is rather unconvincing.  Pushing your argument to its
absurd conclusion, we don't need TAGBODY either because we've already
got PROG.

I have seen quite a few cases where tail recursion was the most
natural way to express a construction.  The obvious example are finite
state automata, which are elegantly expressed as a set of mutually
tail-recursive functions.  On one occasion, I made a set of coroutines
into a set of mutually tail-recursive functions by making the
coroutines' state explicit.

Obviously, tail recursion elimination is more than a mere
optimisation.  In the FSA case, lack of tail recursion elimination
would make a constant-space algorithm into a linear space one, which
is perfectly unacceptable most of the time.

I personally consider the lack of guarantees on the behaviour of tail
calls as a weak point of the Common Lisp standard.  When faced with a
situation that calls for tail recursion, I usually give up on writing
portable Common Lisp and write for CMUCL.

                                        Juliusz
From: Gareth McCaughan
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <slrn9qd476.4am.Gareth.McCaughan@g.local>
Juliusz Chroboczek wrote:

> Obviously, tail recursion elimination is more than a mere
> optimisation.  In the FSA case, lack of tail recursion elimination
> would make a constant-space algorithm into a linear space one, which
> is perfectly unacceptable most of the time.

So write a macro.

(defmacro with-state-machine (states &body body)
  (let* ((names (mapcar #'first states))
         (arglists (mapcar #'second states))
         (bodies (mapcar #'cddr states))
         (symbols    (reduce #'union arglists)))
    `(let ,symbols
       (macrolet
         ,(mapcar (lambda (name vars)
                    (let ((args (loop for x in vars collect (gensym))))
                      `(,name ,args
                        `(progn
                           (psetq . ,',(loop for arg in args for var in vars
                                             nconc `(,var `,,arg)))
                           (go ,',name)))))
                  names arglists)
         (block nil
           (tagbody
             ,@body
             (return)
             . ,(mapcan (lambda (name arglist body)
                          (declare (ignore arglist))
                          (cons name (append body (list '(return)))))
                        names arglists bodies)))))))

That's decidedly primitive, of course; a more serious version
would take steps to avoid unwanted symbol capture, for instance.

It's also wrong; it's too late at night for me to wrap my brain
around the backquoting and unquoting required to get the right
thing into the MACROLET expansions. "`,,arg" is definitely wrong;
I'm sure someone with a clearer head than mine can say what the
right thing is.

(Actually, the right thing probably involves using auxiliary
functions to clarify what's doing on and reduce the amount
of nesting. Again, I'm not sufficiently awake.)

-- 
Gareth McCaughan  ················@pobox.com
.sig under construc
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87d74ohyna.fsf@pps.jussieu.fr>
>> Obviously, tail recursion elimination is more than a mere
>> optimisation.

GM> So write a macro.

Gareth,

You're missing my point.  I know there are other ways.  I know half a
dozen techniques for implementing FSMs.

My point is that I like to implement them in a certain way, and that
the Common Lisp standard allows a conforming implementation to
mis-compile my code.  Fortunately, CMUCL doesn't take this liberty,
which is usually good enough for me.

                                        Juliusz
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvitefm224.fsf@famine.OCF.Berkeley.EDU>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> >> Obviously, tail recursion elimination is more than a mere
> >> optimisation.
> 
> GM> So write a macro.
> 
> Gareth,
> 
> You're missing my point.  I know there are other ways.  I know half a
> dozen techniques for implementing FSMs.
> 
> My point is that I like to implement them in a certain way, and that
> the Common Lisp standard allows a conforming implementation to
> mis-compile my code.  Fortunately, CMUCL doesn't take this liberty,
> which is usually good enough for me.

It doesn't mis-compile your code, it does exactly what it says it will
do: it calls the function.  I think Gareth had the right impulse,
though: write a macro.  It's just that WITH-STATE-MACHINE wasn't the
right one.  Write yourself WITH-TAILCALL-OPTIMIZATION.  I did for
Emacs when it was easier than changing my code to produce something
other than n-mutual tail-recursive code.  Though I admit to not having
ported it to CL (I just continue to hand rewrite or leave it to
CMUCL).  It's something that would have been nice to have in the
language, but since it doesn't *require* vendor support, and it's easy
enough to do yourself, it's not that big of a deal.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwy9nh4dq3.fsf@world.std.com>
Aleksandr Skobelev <····@mail.ru> writes:

> I wonder why CL, as I read somewhere, is not properly tail recursive?  

Because the language is not defined to be.

The form of your question appears to presuppose that you don't think there
would be any reason for a language not to be "properly tail recursive".
Just because of the word "proper" in that phrase, don't assume that the
whole world thinks it's proper (in the general English sense) to do this.

Some of us find stacks with missing elements hard to debug, for example.
I rely critically in my debugging on being able to look up the stack of
callers at the time of a bug to see how I got where I am.  In Scheme, I have
often found it hugely difficult to debug because the stack is not a record of
how I got to the current point.  The marginal benefit afforded to me by
the opportunity to program in other syntactic ways does not outweigh this
loss of debuggability.

Your mileage may, of course, vary.

But my key point is not that you should believe me that stack debugging
is more important; rather, you should believe me that there exist a set of
people who think that a move in the direction of tail recursion elimination
is not unambiguously a virtue.

> ... wasn't there Scheme at that moment when the CL ANSI standard has been
> adopted, 

Scheme predates CL.  Sussman and Steele designed Scheme in 1978, I believe.

Steele was editor of the first CL spec (CLTL); work began around 1980 and
the book was published in 1984.

There is no question that Steele was aware of his own prior work, so 
neither accidental omission is not a serious possibility. 

> and if it Scheme wasn't claimed as a proper recursive language?

CL is not Scheme, so what Scheme claims is not relevant to CL.

Think of languages as political parties and their features as planks
of their party platform, and you will find yourself surprised less often.
From: Aleksandr Skobelev
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <mfk0o9.a41.ln@hermit.athome>
Kent M Pitman <······@world.std.com> wrote:
> Aleksandr Skobelev <····@mail.ru> writes:
> 
>> I wonder why CL, as I read somewhere, is not properly tail recursive?  
> 
> Because the language is not defined to be.
> 
> The form of your question appears to presuppose that you don't think there
> would be any reason for a language not to be "properly tail recursive".
> Just because of the word "proper" in that phrase, don't assume that the
> whole world thinks it's proper (in the general English sense) to do this.

OK. Let it be not a 'proper' but 'just' tail recursive in cases when tail
recursive definitly can take place.

> 
> Some of us find stacks with missing elements hard to debug, for example.
> I rely critically in my debugging on being able to look up the stack of
> callers at the time of a bug to see how I got where I am.  In Scheme, I have
> often found it hugely difficult to debug because the stack is not a record of
> how I got to the current point.  The marginal benefit afforded to me by
> the opportunity to program in other syntactic ways does not outweigh this
> loss of debuggability.

I see your point. But possibility to eliminate tail recursive calls could
be considered like a quality that could be switched on/off with usial
(declare (optimize ...)) forms.

> 
> Your mileage may, of course, vary.
> 
> But my key point is not that you should believe me that stack debugging
> is more important; rather, you should believe me that there exist a set of
> people who think that a move in the direction of tail recursion elimination
> is not unambiguously a virtue.
>

I see. To speak honestly, being a C++ programmer I look at general
recursion with a little fear. For I see some vagueness in it. This is why
I so 'like' a conception of tail recursive elimination. But, of course, I
agree that it can be inspired mostly by a lack of enough experience here.
And it is possible to write reliable programms just using a general
recursion reasonable. 

> ...

    Aleksandr
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwn13vm16y.fsf@world.std.com>
Aleksandr Skobelev <····@mail.ru> writes:

> I see your point. But possibility to eliminate tail recursive calls could
> be considered like a quality that could be switched on/off with usial
> (declare (optimize ...)) forms.

If we were defining the language anew (or modifying the core, which for
various reasons of economic expense, we can't at this time), I wouldn't
mind seeing a
 (declare (optimize (tail-calls 3)))
being a way to get reliable tail call optimization and lesser values giving
you only certain tail call optimizations (perhaps depending on debug settings).

[It's a pity that we don't have a portable agreement abou whether, in general,
 declarations work in absolute terms or relative terms, that is whether
 (> speed safety) is the test or (> speed 2) is the test for various things.
 Declarations are non-portable to begin with.]

It would be fine by me, and I think conforming, if vendors  who wanted to
experimented with tail call removal.  I would suggest if they did it that
when the debug quality's value was higher than tail-call quality's value, 
they should not do the removal unless they had a scheme-style call history
cube or something like that they could substitute for debugging (such as it 
is--I don't like those things).

> I see. To speak honestly, being a C++ programmer I look at general
> recursion with a little fear. For I see some vagueness in it. This is why
> I so 'like' a conception of tail recursive elimination.

Interesting.  I bet that's more the exception than the rule.  I remember
when answering machines came out for telephones, and a lot of my 
telephone-hating friends wouldn't buy one.  I kept saying "no, an answering
machine is not a kind of telephone thing, it's a kind of anti-telephone
thing".  But it was a sophisticated observation to realize this, and they
shunned these helpful phone-shunning devices for a long time.  You're 
basically doing likewise here by saying you don't like recursion but you
like it where you can prove to yourself it isn't happening.  That requires
a degree of sophistication that I bet most people, certainly most people
new to recursion, aren't going to have.  For one thing, it's very hard for
new recursion users to "see" when tail recursion is happening and when not.
It's much easier for them to know when they're using looping primitives,
which is why many of us in the CL community prefer them. :-)

Still, I found your remark here quite interesting, and worth having had this
whole discussion to get to.

> But, of course, I
> agree that it can be inspired mostly by a lack of enough experience here.
> And it is possible to write reliable programms just using a general
> recursion reasonable. 
From: Aleksandr Skobelev
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <iu74o9.3k.ln@hermit.athome>
Kent M Pitman <······@world.std.com> wrote:
> Aleksandr Skobelev <····@mail.ru> writes:
> 
>> I see your point. But possibility to eliminate tail recursive calls could
>> be considered like a quality that could be switched on/off with usial
>> (declare (optimize ...)) forms.
> 
> If we were defining the language anew (or modifying the core, which for
> various reasons of economic expense, we can't at this time), I wouldn't
> mind seeing a
> (declare (optimize (tail-calls 3)))
> being a way to get reliable tail call optimization and lesser values giving
> you only certain tail call optimizations (perhaps depending on debug settings).

I'd offer more plain decision: if (zerop tail-calls) then tail call
optimization shouldn't be else they should be only in described schemes.

> 
> [It's a pity that we don't have a portable agreement abou whether, in general,
> declarations work in absolute terms or relative terms, that is whether
> (> speed safety) is the test or (> speed 2) is the test for various things.
> Declarations are non-portable to begin with.]

Hmm... It is interesting. I haven't know about it.

> 
> It would be fine by me, and I think conforming, if vendors  who wanted to
> experimented with tail call removal.  I would suggest if they did it that
> when the debug quality's value was higher than tail-call quality's value, 
> they should not do the removal unless they had a scheme-style call history
> cube or something like that they could substitute for debugging (such as it 
> is--I don't like those things).
>
>> I see. To speak honestly, being a C++ programmer I look at general
>> recursion with a little fear. For I see some vagueness in it. This is why
>> I so 'like' a conception of tail recursive elimination.
> 
> Interesting.  I bet that's more the exception than the rule.  I remember
> when answering machines came out for telephones, and a lot of my 
> telephone-hating friends wouldn't buy one.  I kept saying "no, an answering
> machine is not a kind of telephone thing, it's a kind of anti-telephone
> thing".  But it was a sophisticated observation to realize this, and they
> shunned these helpful phone-shunning devices for a long time. 

:)

> You're basically doing likewise here by saying you don't like recursion
> but you like it where you can prove to yourself it isn't happening.

You understand me better than I own. :) 
Really, I like recursion as an abstraction for representing some algorithms, 
but not as a realization of ones on machine's level.

> That requires a degree of sophistication that I bet most people,
> certainly most people new to recursion, aren't going to have.  For one
> thing, it's very hard for new recursion users to "see" when tail
> recursion is happening and when not.  It's much easier for them to know
> when they're using looping primitives, which is why many of us in the CL
> community prefer them. :-)

So, if we return to the begining, it would be useful if there were described
situations in CL's specification when recirsive calls should be eliminated
with tail optimization? (It is not a question that required an answer just
now.) :)

> 
> Still, I found your remark here quite interesting, and worth having had this
> whole discussion to get to.
> 
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87iteghyva.fsf@pps.jussieu.fr>
KP> I wouldn't mind seeing a

KP>  (declare (optimize (tail-calls 3)))

KP> being a way to get reliable tail call optimization and lesser
KP> values giving you only certain tail call optimizations (perhaps
KP> depending on debug settings).

It is my understanding that OPTIMIZE settings are not supposed to
change the semantics of a correct program.

Tail call elimination does change the semantics of correct programs.
The obvious example is

  (defun loop ()
    (loop))

which terminates without tail call elimination, but loops forever with
tail call elimination.

                                        Juliusz
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <hetzax49.fsf@itasoftware.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> KP> I wouldn't mind seeing a
> 
> KP>  (declare (optimize (tail-calls 3)))
> 
> KP> being a way to get reliable tail call optimization and lesser
> KP> values giving you only certain tail call optimizations (perhaps
> KP> depending on debug settings).
> 
> It is my understanding that OPTIMIZE settings are not supposed to
> change the semantics of a correct program.
> 
> Tail call elimination does change the semantics of correct programs.
> The obvious example is
> 
>   (defun loop ()
>     (loop))
> 
> which terminates without tail call elimination, but loops forever with
> tail call elimination.

What is the return value?
From: Barry Margolin
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <Be5q7.42$N4.2387@burlma1-snr2>
In article <············@itasoftware.com>,  <···@itasoftware.com> wrote:
>Juliusz Chroboczek <···@pps.jussieu.fr> writes:
>> Tail call elimination does change the semantics of correct programs.
>> The obvious example is
>> 
>>   (defun loop ()
>>     (loop))
>> 
>> which terminates without tail call elimination, but loops forever with
>> tail call elimination.
>
>What is the return value?

What's the return value of the following function:

(defun doesnt-return ()
  (error "Stack overflow"))

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Barry Margolin
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <Cc2q7.33$N4.2099@burlma1-snr2>
In article <··············@pps.jussieu.fr>,
Juliusz Chroboczek  <···@pps.jussieu.fr> wrote:
>Tail call elimination does change the semantics of correct programs.
>The obvious example is
>
>  (defun loop ()
>    (loop))
>
>which terminates without tail call elimination, but loops forever with
>tail call elimination.

The only reason that terminates is if there's a stack overflow.  That's an
implementation limitation, not part of the semantics.  Nowhere in the CL
specification does it require that the stack must have a limited size.  The
virtual machine described by the standard has infinite memory, but physics
prevents us from implementing the machine as described.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87sndfdx9h.fsf@pps.jussieu.fr>
Barry,

I think this discussion is getting off-topic, and doesn't really apply
to the point I was trying to make.

But I enjoy nit-picking about semantics, and will gladly join you.

BM> The only reason that terminates is if there's a stack overflow.
BM> That's an implementation limitation, not part of the semantics.
BM> Nowhere in the CL specification does it require that the stack
BM> must have a limited size.

There are two ways of understanding a specification ``assuming
unbounded resources'':

(i) assuming availability of potentially unbounded resources, said
program does FOO; and

(ii) there exists a finite (but arbitrarily large) amount of resources
within which said program does FOO.

Obviously, you're thinking in terms of (i), while I'm thinking in
terms of (ii).  (Exercice for the reader: specify a class of
properties FOO such that (i) and (ii) coincide.)

BM> The virtual machine described by the standard has infinite memory,

I will grant you that most well-known formal semantic frameworks do
not deal with resource tracking at all, and therefore use model (i).
It is not clear to me, however, which model is used by the CL
standard, or whether the CL standard is explicit about the issue in
the first place.  Perhaps Kent can enlighten us?

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3210171725190873@naggum.net>
* Juliusz Chroboczek
> Obviously, you're thinking in terms of (i), while I'm thinking in
> terms of (ii).  (Exercice for the reader: specify a class of
> properties FOO such that (i) and (ii) coincide.)

  It seems to me that arguing about the flaws of limited memory is
  pointless when you accept limited execution time.  These two may well
  coincide if the system halts execution when it runs out of memory, as
  when it invokes the debugger on the out-of-memory condition.

  If there is no upper bound on the consumption of resources of a program,
  it is ipso facto _broken_, and no silly quibbling over which resource is
  exhausted _first_ can possibly change any semantics of said program.

  It is _typical_ that this has to come up in relation to a Scheme concept.

///
-- 
  Why did that stupid George W. Bush turn to Christian fundamentalism to
  fight Islamic fundamentalism?  Why use terms like "crusade", which only
  invokes fear of a repetition of that disgraceful period of Christianity
  with its _sustained_ terrorist attacks on Islam?  He is _such_ an idiot.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwzo7ej291.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> 
> Barry,
> 
> I think this discussion is getting off-topic, and doesn't really apply
> to the point I was trying to make.
> 
> But I enjoy nit-picking about semantics, and will gladly join you.
> 
> BM> The only reason that terminates is if there's a stack overflow.
> BM> That's an implementation limitation, not part of the semantics.
> BM> Nowhere in the CL specification does it require that the stack
> BM> must have a limited size.
> 
> There are two ways of understanding a specification ``assuming
> unbounded resources'':
> 
> (i) assuming availability of potentially unbounded resources, said
> program does FOO; and
> 
> (ii) there exists a finite (but arbitrarily large) amount of resources
> within which said program does FOO.
> 

Lisp needs both (i) and (ii) togetether.

> Obviously, you're thinking in terms of (i), while I'm thinking in
> terms of (ii).  (Exercice for the reader: specify a class of
> properties FOO such that (i) and (ii) coincide.)
> 
> BM> The virtual machine described by the standard has infinite memory,
> 
> I will grant you that most well-known formal semantic frameworks do
> not deal with resource tracking at all, and therefore use model (i).
> It is not clear to me, however, which model is used by the CL
> standard, or whether the CL standard is explicit about the issue in
> the first place.  Perhaps Kent can enlighten us?

Necessarily because the language is reflective, it uses a hybrid model
for which I'm pretty sure your semantics just doesn't apply.  The
standard is expressed in English and it is the burden of anyone who
wants to apply any out-of-band lemmas from some other domain to CL to
first show that they have a correct semantic description of the language.

We specifically chose English because it was democratic and accessible to
the programming community we needed to reach, such as myself, for example.
I am far from the "guiding force" behind CL. I was there, I had a lot of
opinions that got incorporated, and I was the "neutral editor" once votes
were taken.  But people like Steele and Gabriel were there and fully
aware of enough issues of formal semantics that they could have objected at
any time if we were too far afield.  My own personal understanding of
semantics says that something is "semantically well-formed" if I think I can
read it and understand it and implement it without ambiguity.  I'm sorry
that's probably not very scientific, but I've found it operationally 
useful over the years.  The things I intuitively see as semantically
well-formed seem to be exactly the set of things the math guys are happy
describing, given that they ever take the patience to properly describe
it and don't just dive in with prefab scaffolding of some system they like
to use and hope it works in the current situation.

The system we use describes BOTH a virtual semantics in which you can recurse
indefinitely and not worry about stack, in which you can cons indefinitely
and not worry about heap, etc.  AND a practical semantics in which you can
limit these things and still be a correct implementation.  A conforming
implementation must not tell the user he has made a semantic violation if
he depends on stack to be too deep--SERIOUS-CONDITION is ok, but ERROR is 
not. Ditto for consing too much, though handling that case is going to be
hard for other reasons.  Still, it doesn't make an implementation 
non-conforming.

(ignore-errors (loop)) does not terminate.

neither does

(defun foo () (foo))
(ignore-errors (foo))

The latter, though, creates a resource violation in some (but not all)
implementations.  This is permitted.  And Lisp is reflective, so the
resource violation is permitted to be signaled.  It would be an unuusal
Lisp that signaled a resource violation for (ignore-errors (loop)) but
technically I think signalling a TIME-EXHAUSTED situation would be 
permissible, as long as it was a serious condition and not an error.
It should not be trapped by IGNORE-ERRORS.

Lisp does allow even resource limitations to be trapped, so any formal 
semantics you apply must be one that deals not only with bottom and its
infections nature compositionally, but also with some operator that can
trap bottom and keep it in check, at least some of the time.  My intuitive
sense is that this is overpowerful and probably gets messy becuase it's 
got analogies to a system with both an unstoppable force and an
infinite barrier.  One has to give.  Probably it will be the unstoppable
force, since writing infinite barriers probably can't keep lisp from 
stopping.  But conceptually, Lisp creates a reflective battleground in
which the struggle is permitted to take place.  And if it's uncomputable
who will win, that's too bad, because in practice it's still worth trying
to navigate this space in the name of robustness.  I would not want a weaker
language just becuase it promised more deterministic behavior.

Lisp is a hybrid of your modes (i) and (ii) above because it is intended
both to recognize an idealized semantics it may not be able to implement,
and also to accept its own limitations.  (In this way, it's probably not
that different from being religious, for those of you who are.)  It is both
trying to enforce the ideal semantics but also to still HAVE a semantics
in the case where its semantics are violated.  In this regard, it is 
partly reflective.  It is not 3lisp nor some other dialect that purports
to be wholly reflective, but it plainly involves some of the complex issues
that come up in such situations, and embracing it with the same tools for
semantics that you use for other languages is not meeting it on its own
terms, I think.  Lisp has traditionally begun by trying to figure out what
it wants to do and leaving it to the theorists to catch up; the problem with
working the other way around is that you always have a language which is 
limited by the knowledge of the day it was designed and only gets old from
there; Lisp continues to challenge the theorists even after its design,
which may be puzzling but I regard as appropriate.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwzo7ryw4j.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> KP> I wouldn't mind seeing a
> 
> KP>  (declare (optimize (tail-calls 3)))
> 
> KP> being a way to get reliable tail call optimization and lesser
> KP> values giving you only certain tail call optimizations (perhaps
> KP> depending on debug settings).
> 
> It is my understanding that OPTIMIZE settings are not supposed to
> change the semantics of a correct program.

[See Barry's remarks.]
 
> Tail call elimination does change the semantics of correct programs.
> The obvious example is
> 
>   (defun loop ()
>     (loop))
> 
> which terminates without tail call elimination, but loops forever with
> tail call elimination.

I couldn't read this example.  Can you rewrite it with another function name?
I can't tell if you mean to access the system LOOP primitive, which you are
not permitted to redefine anyway.

Even so, tail call elimination doesn't appear to me to affect looping 
forever (other than resource limitations). 

I once, for fun, added a halts? function to the T (Yale Scheme) language
dialect, and made it always return T, figuring all machines go down sometime.
But, you know, that's really not the kind of halting that the halting problem
is about.  I think in the above loop example, if I understand you (and I
am not sure I do), there is termination in both cases: in one case due to
a resource limitation on stack, and in the other case in a resource limitation
brought by the mean time between failure of the host OS, its hardware, etc.,
which I assume is finite.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87n13ndwus.fsf@pps.jussieu.fr>
KP> I think in the above loop example, if I understand you (and I am
KP> not sure I do), there is termination in both cases: in one case
KP> due to a resource limitation on stack, and in the other case in a
KP> resource limitation brought by the mean time between failure of
KP> the host OS, its hardware, etc., which I assume is finite.

Fair enough.

I think we're back to a rather fundamental problem, which is that of
program equivalence.

In practical terms, would you accept a definition of program
equivalence that identifies a program which runs in constant space
with one which runs in linear space?

I think that one can fairly argue for both answers.  (``Fairly,'' in
this context, means ``without invoking Adam Smith's Invisible Appendages.'')

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwhetvje7k.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> KP> I think in the above loop example, if I understand you (and I am
> KP> not sure I do), there is termination in both cases: in one case
> KP> due to a resource limitation on stack, and in the other case in a
> KP> resource limitation brought by the mean time between failure of
> KP> the host OS, its hardware, etc., which I assume is finite.
> 
> Fair enough.
> 
> I think we're back to a rather fundamental problem, which is that of
> program equivalence.
> 
> In practical terms, would you accept a definition of program
> equivalence that identifies a program which runs in constant space
> with one which runs in linear space?

I'm not sure what "linear space" is unless you mean "linear with
respect to some input".  Maybe (following on the subject line) you
mean "linear in the number of tail calls".  But if I understand the
question correctly, I think I think that it doesn't matter whether
it's "linear" or even "O(ackerman(n))".  What I think matters to me is
that it's O(finite_valued_fn(n)).  I distinguish O(infinity) as a
non-constant that no finite valued function can keep up with.  Put
another way, even with infinite tape on your turing machine, I think
you're not going to get to (foo) in (progn (loop) (foo)); and
therefore I think removing (loop) and letting you get to (foo) is not
a valid transformation.  On the other hand, in (progn (bar) (foo)), if
(bar) is an exprssion that will run any long but known-to-be-finite
time with no defined effects (other than memory use or other things we
don't quantify), I think I think it's ok to optimize to (progn (foo)).

I don't purport to be a mathemetician, but my understanding is that
the question of a discontinuity than a mere question of resources.
That is, a function whose value goes to infinity at a certain point is
different than "gee, that function goes off my page of graph paper"
and different even than "gee, I'll never be able to buy enough graph
paper to show where that function tops out" to the point of "If I set
out to buy graph paper sufficient to try to show where that function
tops out, I would never do anything afterward because there is no end
to it."  Also, I don't think it's the same to say "I recognize that
pursuing that purchase of graph paper is not worth doing; I'd better stop
here and do something else." (which is what I'm saying, with emphasis
on "STOP", meaning the program is done) and "I recognize that 
pursuing that purchase of graph paper is not worth doing, so I'll just
blythely continue with the next step as if it didn't matter that I
skipped an instruction and I'll pretend nothing that followed was contingent
on my successful completion of the prior step" which is a very "C-language"
thing to do but which is NOT what I see Lisp as being about.

> I think that one can fairly argue for both answers.  (``Fairly,'' in
> this context, means ``without invoking Adam Smith's Invisible Appendages.'')

This reference escapes me.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87eloz8417.fsf@pps.jussieu.fr>
>> In practical terms, would you accept a definition of program
>> equivalence that identifies a program which runs in constant space
>> with one which runs in linear space?

KMP> I'm not sure what "linear space" is unless you mean "linear with
KMP> respect to some input".

Given some problem with a well-defined notion of input size, and given
two (correct) programs that solve this problem, one of which does so
in bounded space, the other in linear space, can the two programs be
considered as equivalent?

I think one can argue both ways.  (Or else: the correct notion of
equivalence depends on the application.)

KMP> my understanding is that the question of a discontinuity than a
KMP> mere question of resources.

Both Barry and I have been very careful to avoid the term ``infinite''.
We were speaking of unbounded resources.

When modelling a finitary process such as deterministic computation,
infinity can only appear as the limit of an unbounded process.  Thus,
the question of whether the process is continuous cannot even be
formulated: the very assumption that leads us to introduce the notion
of infinity is that the process is continuous.

If you prefer, we were not even considering the notion of actual
infinity.  The largest (in some intuitive sense) notion we were
willing to consider was that of potentially unbounded usage of
resources: a usage that remains finite at any time, but with no a
priori bound.

>> (``Fairly,'' in this context, means ``without invoking Adam Smith's
>> Invisible Appendages.'')

KMP> This reference escapes me.

This modern form of mysticism, ``the market's invisible hand.''

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw66aax520.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> >> In practical terms, would you accept a definition of program
> >> equivalence that identifies a program which runs in constant space
> >> with one which runs in linear space?
> 
> KMP> I'm not sure what "linear space" is unless you mean "linear with
> KMP> respect to some input".
> 
> Given some problem with a well-defined notion of input size, and given
> two (correct) programs that solve this problem, one of which does so
> in bounded space, the other in linear space, can the two programs be
> considered as equivalent?
> 
> I think one can argue both ways.  (Or else: the correct notion of
> equivalence depends on the application.)
> 
> KMP> my understanding is that the question of a discontinuity than a
> KMP> mere question of resources.
> 
> Both Barry and I have been very careful to avoid the term ``infinite''.
> We were speaking of unbounded resources.

But the issue of (progn (loop) (foo))
is not a question of unbounded resources, it's a question of 
infinite resources.  (foo) will never be reached.

> When modelling a finitary process such as deterministic computation,
> infinity can only appear as the limit of an unbounded process.  Thus,
> the question of whether the process is continuous cannot even be
> formulated: the very assumption that leads us to introduce the notion
> of infinity is that the process is continuous.

I guess I'm just not mathy enough to make sense of this.  In my
parlance, (loop) is an "infinite loop".  There isn't any ambiguity
about this.  Nor is there any ambiguity that (foo) in (progn (loop) (foo))
cannot be reached.  Any application of mathematics which obscures this
quite obvious fact is either accidental or intentional obfuscation, IMO.
But whatever the motivation, it does not aid the understanding of the problem.

> If you prefer, we were not even considering the notion of actual
> infinity.

Then in my parlance, you are not addressing the form '(loop)'.

> The largest (in some intuitive sense) notion we were
> willing to consider was that of potentially unbounded usage of
> resources: a usage that remains finite at any time, but with no a
> priori bound.

This is trivially true of all computations but does not address the
"meaning" of those computations.  You don't understand infinity by walking
a ways and saying "it seems finite to me" and then walking a ways farther
and saying "it still seems finite" and so on.  You understand infinity
exactly by considering the fact that in the two step process "1. do infinite
work, then 2. do something else finite" that no matter how much you work on
step 1, you are no closer to doing step 2, and, moreover, understanding 
that step 2 will never be done.  That is the definition of infinity.
That is what (progn (loop) (foo)) says.  If you have a mathematics that says
it means something else, you are using that math in the wrong place.

> >> (``Fairly,'' in this context, means ``without invoking Adam Smith's
> >> Invisible Appendages.'')
> 
> KMP> This reference escapes me.
> 
> This modern form of mysticism, ``the market's invisible hand.''

Hmm.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87r8syosjs.fsf@pps.jussieu.fr>
KMP> But the issue of (progn (loop) (foo))
KMP> is not a question of unbounded resources, it's a question of 
KMP> infinite resources.  (foo) will never be reached.

Yes, we fully agree about that.  There is no such thing as an actually
infinite amount of work.  This was emphatically not the issue I was
trying to get you to comment on.

>> If you prefer, we were not even considering the notion of actual
>> infinity.

KMP> Then in my parlance, you are not addressing the form '(loop)'.

We agree about that, I am definitely not.

As you say, there is no ambiguity.  With any reasonable semantics, the
programs (loop) and (progn (loop) (error "Foo!")) are equivalent.

On the other hand, consider the following:

  (defun foo ()
    (foo))

and suppose that you compile FOO without tail-call elimination.  Does
FOO terminate?

The obvious answer is that my machine has finite memory, hence FOO
will run out of stack at some point, hence FOO terminates on my
machine.  This answer invokes a specific maximum stack size, and so is
not acceptable as part of a language definition.

The answer that the best-known formal semantic frameworks give is that
assuming a potentially unbounded stack, FOO will never run out of
resources, hence FOO doesn't terminate.  I claim that this answer is
not realistic.

Finally, you can say that, assuming any arbitrarily large but finite
amount of stack, FOO will terminate.  As FOO terminates in all finite
models, FOO terminates.  I claim that this last answer is more
realistic than the second one while avoiding mentioning a specific
amount of available resources.

The point I'm trying to make is that FOO terminates when compiled
without tail call elimination, and that FOO does not terminate if
compiled with tail call elimination.  Thus, tail call elimination is
not a mere optimisation, but a semantic feature.

And, incidentally, a feature that I miss in my favourite programming
language.

Regards,

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3210249273790367@naggum.net>
* Juliusz Chroboczek <···@pps.jussieu.fr>
> On the other hand, consider the following:
> 
>   (defun foo ()
>     (foo))
> 
> and suppose that you compile FOO without tail-call elimination.  Does
> FOO terminate?

  Why do you not distinguish between "terminate" and "signal an error"?

> Thus, tail call elimination is not a mere optimisation, but a semantic
> feature.

  This is just plain wrong.  Your semantic feature rests on an equivocation
  between termination and crashing.  I fail to see how this can reasonably
  be considered anything but _completely_ bogus.  What is the _value_ of a
  crash?  Rather, a crash is the antithesis of termination, because it
  _prevents_ every reasonably expectation from a termination.  The same is
  true for a non-idiotic example which takes enough time to terminate that
  it can run out of some resources, like electric power or memory or disk
  space or whatever -- hell, that might even happen immediate after it has
  _started_ running.  It is profoundly counter-productive to treat each and
  every cessation of execution the same way.  I wonder what kind of silly
  theoretical framework has found it useful to abstract away the value of a
  computation so one could study the conditions for cessation of execution
  without concern for what the computation yields.  It seems to be one of
  those useless theories that are used only to prove something that is
  wrong and that every reasonable person knows to be wrong, but which can
  be shown to be true if you accept a whole bunch of insane premises that
  have been divorced from the reality and purpose of computing.

  Again, it is typical that this discussion centers around a Scheme feature.

///
-- 
  Why did that stupid George W. Bush turn to Christian fundamentalism to
  fight Islamic fundamentalism?  Why use terms like "crusade", which only
  invokes fear of a repetition of that disgraceful period of Christianity
  with its _sustained_ terrorist attacks on Islam?  He is _such_ an idiot.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <dh3eloqhqha.fsf@glory.dcs.ed.ac.uk>
EN>   Why do you not distinguish between "terminate" and "signal an error"?

[...]

EN>   This is just plain wrong.  Your semantic feature rests on an
EN>   equivocation between termination and crashing.

No.  It rests on *distinguishing* between non-termination and
the signalling of STORAGE-CONDITION.

Suppose you identify signalling a condition and not terminating, i.e.

  (loop) 

and 

  (error "Foo!") 

are equivalent.

Then (assuming a compositional semantics) you have to identify

  (progn (ignore-errors (loop)) t) 

and

  (progn (ignore-errors (error "Foo!")) t)

If you choose to identify the latter program with T (a rather natural
hypothesis), and the former one with (LOOP), then you've just identi-
fied (LOOP) with T.  With a little more work (and some similarly
natural hypotheses), you've just identified all Lisp programs.

Not a very interesting semantics.

                                        Juliusz

P.S. Okay, I've cheated.  Whether a stack overflow generates a
STORAGE-CONDITION or causes a nuclear holocaust is ``implementation-
defined'' in ANSI Common Lisp.  Still, I think it's natural to
distinguish between a ``crash'' and non-termination.

The accepted terminology in the Semantics community, by the way, is to
include abnormal termination under the term ``termination''.
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3210707015341398@clemacs.org>
* Juliusz Chroboczek
> The accepted terminology in the Semantics community, by the way, is to
> include abnormal termination under the term ``termination''.

  That seems like a very good way to cheat yourself out of a large number
  of a really hairy problems.  Cleaning up such things is not interesting.

///
-- 
  Why, yes, I love freedom, Mr. President, but freedom means accepting risks.
  So, no, Mr. President, I am not with you, and I am not with the terrorists.
  I would be happier if you left the airports alone and took care of all the
  cars that are a much bigger threat to my personal safety than any hijacking.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwy9myj1y1.fsf@world.std.com>
Juliusz Chroboczek <···@glory.dcs.ed.ac.uk> writes:

> EN>   Why do you not distinguish between "terminate" and "signal an error"?
> 
> [...]
> 
> EN>   This is just plain wrong.  Your semantic feature rests on an
> EN>   equivocation between termination and crashing.
> 
> No.  It rests on *distinguishing* between non-termination and
> the signalling of STORAGE-CONDITION.

But why?  STORAGE-CONDITION merely signals a lack of resource in the
ability to wait.  It is not predictive of whether the waiting would
have done the same thing; it is utterly agnostic on the issue.  No
implementation is required to stop.  Implementations are permitted to
do tail call elimination.  

> Suppose you identify signalling a condition and not terminating, i.e.
> 
>   (loop) 
> 
> and 
> 
>   (error "Foo!") 
> 
> are equivalent.

They don't do the same thing. There are situations in which they are they
are operationally the same and situations in which they are not.

> Then (assuming a compositional semantics) you have to identify

I don't know what a "compositional semantics" implies here--sounds
like it means "a sequence of words not included in the spec".  Would
that be a fair interpretation?  Lisp is a partly reflective language,
and error invokes the reflective mechanism, while LOOP does not.  So
there are many ways in which the two are alike and some ways in which
they are not.
 
>   (progn (ignore-errors (loop)) t) 
> 
> and
> 
>   (progn (ignore-errors (error "Foo!")) t)

This is an example of how the two are not alike.
 
> If you choose to identify the latter program with T (a rather natural
> hypothesis), and the former one with (LOOP), then you've just identi-
> fied (LOOP) with T.  With a little more work (and some similarly
> natural hypotheses), you've just identified all Lisp programs.

I don't know what this "identified with" stuff here is, but it's not in the
language definition, so it's really just not relevant.  I see this as an
improper application of the mathy game of "deltas and epsilons" where you
have played deltas but are obliged to play epsilons.  It's your burden to
show that your reasoning conforms to the language defintiion, which is 
democratically expressed in English and open to all to understand; the 
language is not offered in some elitist mathy formal semantics and that
means that attempts to apply such semantics should be made only where you
can prove that it such semantics is appropriate.  That as contrasted with
Scheme where the language definition is offered in two allegedly redundant
forms, but where if there is ever a dispute a certain part of the public will
be locked out of it because they will be overwhelmed by obscure notation.
In the design of CL, we specifically addressed the idea of such redundancy
and decided it was a bad plan.

> Not a very interesting semantics.

You're welcome to conclude as you like (though you somewhat put the
lie to the claim by continuing to discuss it... sort of like someone
claiming that Jerry Springer is boring TV but tuning in every day to
watch the fist fights).  We're busy building programs are interesting,
not semantics that are interesting. Our dull semantics has not seemed
to impede that.

>                                         Juliusz
> 
> P.S. Okay, I've cheated.  Whether a stack overflow generates a
> STORAGE-CONDITION or causes a nuclear holocaust is ``implementation-
> defined'' in ANSI Common Lisp.  Still, I think it's natural to
> distinguish between a ``crash'' and non-termination.

I don't get it.  IGNORE-ERRORS doesn't trap stack overflow and it doesn't
trap infinite loops.  They are both the same.  They are resource limitations.
 
ERROR is trapped because it involves a semantic violation.

> The accepted terminology in the Semantics community, by the way, is to
> include abnormal termination under the term ``termination''.

Too bad, but you are misapplying the semantics of the semantics community.
The "abnormal termination" here is due to an inability of the processor
to have the resources to supply the correct semantics.  The correct semantics
allow the program you want.  HOWEVER, a processor is considered conforming
for not having those resources.  The following is the definition of a 
completely conforming common lisp processor; it's not likely to be 
marketworthy, but it is allowed to claim conformance:

(defun common-lisp ()
  (write-string "Resources exhausted."))

It probably also dumps out into a killer-small hello world, by the way, for
those who like to optimize such things and worry that CL has missed that boat.

CL has the notion of an ideal semantics separate from the notion of an
implemented semantics.  I'm sorry if the semantics community doesn't
like that, but they'll just have to deal.  We describe arbitrary
precision integers but we don't require all conforming processors to
have the memory needed to house them.  We assume describe programs
that can have arbitrary stack depth but we leave it to implementations
to sort out how deep the stacks really are and whether they are
extensible.  We SAY that these are not to be identified as errors but
as resource limitations.

We say programs in CL have a meaning independent of the question of
whether processors will reliably run them.  Processors for CL are
intended to try their best to meet that meaning, but are allowed to
fail under certain cases and still be conforming.  It is specifically
left to the market to sort out whether this is worth money to buy.  We
figured that was a much better test of worthiness since in a
pluralistic society we expect there to be cases where one solution is
ok for some and not for others and vice versa...  Some people will
want implementations with tail call elimination, some won't.  I don't,
for example.  You do.  So we'll prefer different implementations.  We
can still agree on the meaning of the programs that might different in
behavior between them, though.  And we vendors can decide whose money
they want, without losing their right to claim conformance. Seems ok to
me...
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87pu89sgwr.fsf@pps.jussieu.fr>
>> No.  It rests on *distinguishing* between non-termination and
>> the signalling of STORAGE-CONDITION.

KMP> But why?  STORAGE-CONDITION merely signals a lack of resource in
KMP> the ability to wait.  It is not predictive of whether the waiting
KMP> would have done the same thing; it is utterly agnostic on the
KMP> issue.  No implementation is required to stop.  Implementations
KMP> are permitted to do tail call elimination.

Sure.

The question is the other way around: would it make sense for a
hypothetical future standard to require that a given piece of code is
*not* allowed to signal STORAGE-CONDITION or crash?  In other words,
is there a technically correct way to *mandate* tail-call elimination
in a (hypothetical) standard of the quality of ANSI CL.

My claim is that there is; but this claim relies fundamentally on the
fact that STORAGE-CONDITION *is* different from looping.

In the rest of the discussion, you would appear to have dualised my
position: I am claiming that non-termination and signalling a
condition (or crashing) *are* different results...

>> (progn (ignore-errors (loop)) t) 
>> 
>> and
>> 
>> (progn (ignore-errors (error "Foo!")) t)

KMP> This is an example of how the two are not alike.

...which you would appear to at least partly agree with.

Regards,

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwd749pc42.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> >> No.  It rests on *distinguishing* between non-termination and
> >> the signalling of STORAGE-CONDITION.
> 
> KMP> But why?  STORAGE-CONDITION merely signals a lack of resource in
> KMP> the ability to wait.  It is not predictive of whether the waiting
> KMP> would have done the same thing; it is utterly agnostic on the
> KMP> issue.  No implementation is required to stop.  Implementations
> KMP> are permitted to do tail call elimination.
> 
> Sure.
> 
> The question is the other way around: would it make sense for a
> hypothetical future standard to require that a given piece of code is
> *not* allowed to signal STORAGE-CONDITION or crash?  In other words,
> is there a technically correct way to *mandate* tail-call elimination
> in a (hypothetical) standard of the quality of ANSI CL.

I think you're really asking me whether it is a valid thing to want
implementations to do, and I think "sure".  But if your question is
literally, as worded, "should standards be the mechanism by which this
is achieved", I'm less sure.

I will say that I have in the past advocated that the standard would
be more useful if it told you things like "the worst case time in
doing a AREF will be O(1)" vs "the worst case time in doing ELT will
be O(n) for lists of length n or O(1) for arrays".  There are places
where we've left it open and as a consequence an implementation can do
really stupid things that are baffling to users.  SET-FILE-POSITION
was discussed recently; some people expect it to be O(1) and if it's
O(n) [for n=filepos], that's pretty scary.  But the consensus was that
this should be left to an implementation.

To some extent, one might characterize my position as "there are
bigger fish to fry than tail recursion" or one might say "it's just
not consistent with the overall design to talk about memory use when
nothing really does".  You might appeal back to the "infinity" vs "finite"
argument and say it was particularly important for the tail call case;
I don't know.  I'd rather tail call elimination be under OPTIMIZE
declaration control and not a required feature of the language, so that
kind of issue would need to get factored in, too.

And then there's the practical matter that the current climate is that none
of this rises to the level of it being worth it to the community to change
the standard.  Merely opening the standard for the fixing of ANY technical
item exposes the process to the risk of major politicking; it is not allowed
to just make a work item to make a small correction under ANSI rules, I
believe.  I think you have to just open the spec for change and then other
things you didn't expect might creep in.  Stability is best maintained by
not touching it, and there's nothing now that cries out as needing fixing.

I'd almost say that if something DID come up that required fixing, ANSI would
not be the vehicle of choice to do it.

The reason I think people wanted to leave speed issues entirely out of the
standard is that the market is really better at sorting things out.  Let's
take the example of the MOP.  It's not standard either, but consensus seems
to me to be that it's ok to use because most implementations do have it.
Other things, like tail call elimination, haven't even been voluntarily 
adopted by implementations even though they are allowed to.  If this came back
to ANSI and we operated as we have done in the past, the first thing that
would be asked when your hypothetical proposal to define this came to the 
table would be "what is the current practice?" and the answer would be "no
one does it".  And that would immediately be translated to statements like
"sounds dicey" and "sounds like it will impose cost on all vendors that they
don't feel a need for" and "sounds like there is no call for it".

The first stage of making the change is not to look to the standard  unless
the standard forbids the change.  The first stage is to get vendors to 
experiment, and then to drive the wedge by asking that code that runs in
one vendor run in another.  Best, I think, would be to operate under a switch
so that code that did this had to self-identify and other compilers could
minimally say "I don't support that option, your code will likely lose" rather
than quietly compiling the code and then losing on it at runtime...

> My claim is that there is; but this claim relies fundamentally on the
> fact that STORAGE-CONDITION *is* different from looping.
> 
> In the rest of the discussion, you would appear to have dualised my
> position: I am claiming that non-termination and signalling a
> condition (or crashing) *are* different results...
> 
> >> (progn (ignore-errors (loop)) t) 
> >> 
> >> and
> >> 
> >> (progn (ignore-errors (error "Foo!")) t)
> 
> KMP> This is an example of how the two are not alike.
> 
> ...which you would appear to at least partly agree with.

Apparently.  :-)
From: Lieven Marchand
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <m3ite0zzlc.fsf@localhost.localdomain>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> The question is the other way around: would it make sense for a
> hypothetical future standard to require that a given piece of code is
> *not* allowed to signal STORAGE-CONDITION or crash?  In other words,
> is there a technically correct way to *mandate* tail-call elimination
> in a (hypothetical) standard of the quality of ANSI CL.

Defining tail call elimination is trickier than most people expect. In
fact, although Scheme has for a long time expressed the intention to
be "properly tail recursive", only the most recent report (R^5RS)
makes an attempt at defining this concept, and for the formal
definition it refers to a paper by Clinger(1998): "William D
Clinger. Proper tail recursion and space efficiency."

In the abstract of this article he says: "Proper tail recursion is not
the same as ad hoc tail call optimization in stack-based
languages. Proper tail recursion often precludes stack allocation of
variables, but yields a well-defined asymptotic space complexity that
can be relied upon by portable programs."

-- 
Lieven Marchand <···@wyrd.be>
She says, "Honey, you're a Bastard of great proportion."
He says, "Darling, I plead guilty to that sin."
Cowboy Junkies -- A few simple words
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw8zew8s5z.fsf@world.std.com>
Lieven Marchand <···@wyrd.be> writes:

> Defining tail call elimination is trickier than most people expect. In
> fact, although Scheme has for a long time expressed the intention to
> be "properly tail recursive", only the most recent report (R^5RS)
> makes an attempt at defining this concept, and for the formal
> definition it refers to a paper by Clinger(1998): "William D
> Clinger. Proper tail recursion and space efficiency." [...]

I can hear some people saying "how could it be complicated?".  For those
who are looking for at least one concrete example, I believe the following
kind of thing was raised as ambiguous even under the Scheme spec:

 (defun (factorial x n)
   (if (zerop x) n
       (let ((result (factorial (- x 1) (* x n))))
         result)))

As should be obvious, this affects the whole issue of how the language
treats variables--i.e., is it just "allowed" or is it actively "required"
to fold uses of variables, because this in turn forces implementations to
do some code analysis that they might not be prepared to do, especially in
an interpreter, the major point of which is to have lazy application of 
semantics by analyzing things only locally as they are encountered; this 
would effectively push an interpreter closer to a compiler, almost calling
into question whether an interpreter was even allowed.

Of course, you could just say it wasn't a tail call, but some people are
apparently offended by that because they want (let ((x y)) x) and y to be
semantically the same thing.  If introducing such bindings creates 
unwanted additional effects (beyond just naming a quantity), it inhibits 
the usefulness of code transformations--or, at least, makes such 
transformations a lot harder to write.

[Note that I often beat people up for wanting to apply certain optimizations
that are not part of the language and complaining that they can't, but this
is different:  we're not in the context of "how do I optimize the fixed
language?" but rather "how should I define the language such that optimizations
I know of will be applicable?".]

Anyway, I hope this brief overview gives the sense that this issue of
tail call elimination isn't just a "no brainer" that can just be
dropped into the language spec trivially.  It's much easier to have an
implementation maintainer take on the responsibility because that
maintainer is financially responsible for any impact on the usability
of their own implementation.  For the language designers to make the
same choice, they have to be sensitive to revenue to implementations
they don't own, and that's a considerably bigger responsibility.  ANSI 
defines away such responsibility legallyl by allowing organizations to
freely join and vote, and by requiring even minority parties that are
economically impacted to be seriously treated.  And the result of the vote
the last time around was that there was even majority concern ...
From: Francois-Rene Rideau
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <871ykonwo8.fsf@Samaris.tunes.org>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> The question is the other way around: would it make sense for a
> hypothetical future standard to require that a given piece of code is
> *not* allowed to signal STORAGE-CONDITION or crash?  In other words,
> is there a technically correct way to *mandate* tail-call elimination
> in a (hypothetical) standard of the quality of ANSI CL.

Why not instead have an explicit (TAIL-CALL (F X)) special form,
or otherwise two special forms (TAIL-APPLY #'F (LIST X))
and (TAIL-FUNCALL #'F X) ? It would seem much more in line with
the rest of the Common LISP tradition: if it has a special,
different meaning, then have a special, different, syntax for it,
that signals the difference.

Yours freely,

[ Fran�ois-Ren� �VB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
[  TUNES project for a Free Reflective Computing System  | http://tunes.org  ]
*EULA: By reading or responding to this message you agree that all my stated
opinions are correct.* "EULA" -- patent pending.
From: Rahul Jain
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87bsjsp2zh.fsf@photino.sid.rice.edu>
Francois-Rene Rideau <···········@tunes.org> writes:

> Why not instead have an explicit (TAIL-CALL (F X)) special form,
> or otherwise two special forms (TAIL-APPLY #'F (LIST X))
> and (TAIL-FUNCALL #'F X) ? It would seem much more in line with
> the rest of the Common LISP tradition: if it has a special,
> different meaning, then have a special, different, syntax for it,
> that signals the difference.

Because we already have DO and LOOP for that.

I suppose someone could write macros that transformed these tail-apply
and tail-funcall -containing forms into DO or LOOP forms, but that
sounds to me like part of the process of writing a compiler. :)

-- 
-> -/-                       - Rahul Jain -                       -\- <-
-> -\- http://linux.rice.edu/~rahul -=- ·················@usa.net -/- <-
-> -/- "I never could get the hang of Thursdays." - HHGTTG by DNA -\- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   Version 11.423.999.220020101.23.50110101.042
   (c)1996-2000, All rights reserved. Disclaimer available upon request.
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvn13c6ij9.fsf@conquest.OCF.Berkeley.EDU>
Rahul Jain <·····@rice.edu> writes:

> Francois-Rene Rideau <···········@tunes.org> writes:
> 
> > Why not instead have an explicit (TAIL-CALL (F X)) special form,
> > or otherwise two special forms (TAIL-APPLY #'F (LIST X))
> > and (TAIL-FUNCALL #'F X) ? It would seem much more in line with
> > the rest of the Common LISP tradition: if it has a special,
> > different meaning, then have a special, different, syntax for it,
> > that signals the difference.
> 
> Because we already have DO and LOOP for that.

(defun foo (n)
  (format t "There are ~D stack frames left~%" n))

(defun my-fun (stack-frames-left)
  (if (not (zerop stack-frames-left))
      (my-fun (1- stack-frames-left))
      (tail-call (foo stack-frames-left))))

How are DO and LOOP going to help here?
From: Rahul Jain
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <874rpjq3l1.fsf@photino.sid.rice.edu>
···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> (defun foo (n)
>   (format t "There are ~D stack frames left~%" n))

> (defun my-fun (stack-frames-left)
>   (if (not (zerop stack-frames-left))
>       (my-fun (1- stack-frames-left))
>       (tail-call (foo stack-frames-left))))

> How are DO and LOOP going to help here?

Technically, they're not. You could use LABELS or something, but it's
immaterial. Why not just use a compiler that has the behavior you
want?

-- 
-> -/-                       - Rahul Jain -                       -\- <-
-> -\- http://linux.rice.edu/~rahul -=- ·················@usa.net -/- <-
-> -/- "I never could get the hang of Thursdays." - HHGTTG by DNA -\- <-
|--|--------|--------------|----|-------------|------|---------|-----|-|
   Version 11.423.999.220020101.23.50110101.042
   (c)1996-2000, All rights reserved. Disclaimer available upon request.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwk7yfsszo.fsf@world.std.com>
Rahul Jain <·····@rice.edu> writes:

> ···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:
> 
> > (defun foo (n)
> >   (format t "There are ~D stack frames left~%" n))
> 
> > (defun my-fun (stack-frames-left)
> >   (if (not (zerop stack-frames-left))
> >       (my-fun (1- stack-frames-left))
> >       (tail-call (foo stack-frames-left))))
> 
> > How are DO and LOOP going to help here?
> 
> Technically, they're not. You could use LABELS or something, but it's
> immaterial. Why not just use a compiler that has the behavior you
> want?

I honestly don't undersatnd what this TAIL-CALL is going to do.

The only TAIL-CALL operator I've ever been familiar with was in Teco
and it was used to allow you to "GO" into another macro without popping
the q-register state, so would correspond more to:

 (defun foo1 () n)
 (defun foo (n) (tail-call (foo1)))
 (foo 3) => 3

I can't see a tail-call in CL meaning *that*, so I don't know what it would
mean.  What if I wrote:

 (defun factorial (x)
   (if (zerop x) 1
       (* x (tail-call (factorial (1- x))))))

A lot of this has to with whether the implementation is recognizing that
it *could* tail-call the function.  Some compilers might not know.  Making
them have to be able to tell if it's valid might impose a lot of work on
them.  The operator may be optional for the user, but it's not for the
implementation.  
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvn13b2ioy.fsf@apocalypse.OCF.Berkeley.EDU>
Kent M Pitman <······@world.std.com> writes:

> Rahul Jain <·····@rice.edu> writes:
> 
> > ···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:
> > 
> > > (defun foo (n)
> > >   (format t "There are ~D stack frames left~%" n))
> > 
> > > (defun my-fun (stack-frames-left)
> > >   (if (not (zerop stack-frames-left))
> > >       (my-fun (1- stack-frames-left))
> > >       (tail-call (foo stack-frames-left))))
> > 
> > > How are DO and LOOP going to help here?
> > 
> > Technically, they're not. You could use LABELS or something, but it's
> > immaterial.

Say what?  I was responding to the following:

Rahul Jain <·····@rice.edu> writes:

> Francois-Rene Rideau <···········@tunes.org> writes:
> 
> > Why not instead have an explicit (TAIL-CALL (F X)) special form,
> > or otherwise two special forms (TAIL-APPLY #'F (LIST X))
> > and (TAIL-FUNCALL #'F X) ? It would seem much more in line with
> > the rest of the Common LISP tradition: if it has a special,
> > different meaning, then have a special, different, syntax for it,
> > that signals the difference.
> 
> Because we already have DO and LOOP for that.
> 
> I suppose someone could write macros that transformed these tail-apply
> and tail-funcall -containing forms into DO or LOOP forms, but that
> sounds to me like part of the process of writing a compiler. :)

It's certainly material, it's the whole point of your post!  I was
just trying to point out there are cases where tail calls are not just
loops in disguise.  It's certainly not a loop, and if FOO is a
generally useful function, I don't want to put it in a LABELS or FLET.

> Why not just use a compiler that has the behavior you want?

Isn't that what the TAIL-CALL, TAIL-FUNCALL, TAIL-APPLY operators were
proposed as a part of?

KMP> I honestly don't undersatnd what this TAIL-CALL is going to do.

Well, I didn't propose it, and I wasn't particularly arguing for it
(just that it's not the same as DO), but I can actually think of a
nice use for it: request that the compiler eliminates that tail call.
Obviously it would need all the capabilities of a compiler that does
it more or less quietly.  If it could not eliminate the call, it could
signal an error when compiling, and it would not eliminate tail calls
that weren't marked for elimination.

Actually, I kind of like that idea.

 [...]
> I can't see a tail-call in CL meaning *that*, so I don't know what it would
> mean.  What if I wrote:
> 
>  (defun factorial (x)
>    (if (zerop x) 1
>        (* x (tail-call (factorial (1- x))))))

The compiler could signal an error.

> A lot of this has to with whether the implementation is recognizing that
> it *could* tail-call the function.  Some compilers might not know.  Making
> them have to be able to tell if it's valid might impose a lot of work on
> them.  The operator may be optional for the user, but it's not for the
> implementation.  

Actually, it could be optional for the compiler: if the compiler
writer didn't want to deal with the analysis, it could always signal
an error when it came across a TAIL-CALL form.
From: Francois-Rene Rideau
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <877kuforh7.fsf@Samaris.tunes.org>
···@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick) writes:
> It's certainly material, it's the whole point of your post!  I was
> just trying to point out there are cases where tail calls are not just
> loops in disguise.
Indeed. Tail calling a global function is just not translatable into a
labels or flet or go without losing the dynamic semantics of being able
to modify, trace, etc., the function. Conversely, you mightn't like to
do tail call elimination when not needed, because of relative inefficiency
(considering available implementation technique) or of loss during
some kind of debugging or security audit.

Actually, so as to eliminate confusion, instead of a TAIL-CALL special form,
or of TAIL-APPLY and TAIL-FUNCALL special forms, I think it might be better
to extend the RETURN and RETURN-FROM forms with a keyword argument :TAIL-CALL
or to provide TAIL-RETURN and TAIL-RETURN-FROM as in

(defun mult-fact (x n)
   (declare (type unsigned-byte n))
   (if (< n 2) n
       (return (mult-fact (* x n) (1- n)) :tail-call t)))

[ Fran�ois-Ren� �VB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
[  TUNES project for a Free Reflective Computing System  | http://tunes.org  ]
Laziness is mother of Intelligence. Father unknown.
		-- Far�
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-2A2E77.20510801102001@news.akl.ihug.co.nz>
In article <···············@world.std.com>, Kent M Pitman 
<······@world.std.com> wrote:

> I can't see a tail-call in CL meaning *that*, so I don't know what it 
> would mean.  What if I wrote:
> 
>  (defun factorial (x)
>    (if (zerop x) 1
>        (* x (tail-call (factorial (1- x))))))
> 
> A lot of this has to with whether the implementation is recognizing
> that it *could* tail-call the function.  Some compilers might not
> know.  Making them have to be able to tell if it's valid might
> impose a lot of work on them.  The operator may be optional for
> the user, but it's not for the implementation.  

You seem to have lost me.  What would you be expecting tail-call to do 
here?


The only thing that can be tail called here is the "*".  More than that 
-- it *will* be tail called by any implementation that does tail-call 
optimization (if it doesn't optimize even further and inline it).

You can't tail-call "factorial" because the result of the enclosing 
function isn't identical to the result of the call of "factorial".

-- Bruce
From: Tim Bradshaw
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <ey3snd3r81f.fsf@cley.com>
* Bruce Hoult wrote:

> You seem to have lost me.  What would you be expecting tail-call to do 
> here?

> The only thing that can be tail called here is the "*".  More than that 
> -- it *will* be tail called by any implementation that does tail-call 
> optimization (if it doesn't optimize even further and inline it).

> You can't tail-call "factorial" because the result of the enclosing 
> function isn't identical to the result of the call of "factorial".

I think the issue is that, in order to compile correct code, the
compiler has to *know* that it can or can not tail call something, or
equivalently that it should or should not ignore the TAIL-CALL form.
And that's probably most of the work it takes to do tail-call
elimination, so why have the form?

I once had a love-affair with named-LET-style iteration (I'm all cured
now, I use LOOP like Real Men).  CMUCL has an ITERATE form which does
this sort of thing, and I wrote a clone using the obvious expansion to
LABELS. The problem is when you use an implementation which doesn't do
tail-call elimination, of course.  So I hacked away some more and I
made my macro convert these `recursive' calls to GOs.  Except now it
breaks if they aren't tail-calls, so I did some yet further hack which
required the name of the local function to have the string "LOOP" in
it, and only in that case would iterative code be compiled.  Very
shortly after this I realised that this was all just stupid, and that
the only way to do this right was to do the whole analysis and
actually compile GO iff the thing was a tail call, and that doing
that would require me to write a reasonable chunk of a CL compiler.
This was the moment when I realised that I should have used LOOP all
along.

--tim
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-76CFD2.22564601102001@news.akl.ihug.co.nz>
In article <···············@cley.com>, Tim Bradshaw <···@cley.com> 
wrote:

> * Bruce Hoult wrote:
> 
> > You seem to have lost me.  What would you be expecting tail-call to do 
> > here?
> 
> > The only thing that can be tail called here is the "*".  More than that 
> > -- it *will* be tail called by any implementation that does tail-call 
> > optimization (if it doesn't optimize even further and inline it).
> 
> > You can't tail-call "factorial" because the result of the enclosing 
> > function isn't identical to the result of the call of "factorial".
> 
> I think the issue is that, in order to compile correct code, the
> compiler has to *know* that it can or can not tail call something, or
> equivalently that it should or should not ignore the TAIL-CALL form.
> And that's probably most of the work it takes to do tail-call
> elimination, so why have the form?

Ah, it was an argument against a special form?  Fair enough, I agree.

So, what is the difficulty in deciding whether or not a function should 
be tail called?  It should be tail called If and Only If the result of 
the current function is exactly the result of the function to be called.  
That is, no calculations are to be done using the result of the function 
being called and no environment cleanup is required (exception handlers, 
destructors in languages which have them, etc...).

It seems to me that the only problems that arise are:

1) the actual mechanics of doing the tail-call at the machine level.  
This can require a certain amount of stack-munging to pop off the return 
address and old arguments, push the new arguments and re-push the return 
address, and then jump to the tail-called function.  This is easier with 
e.g. RISC CPUs which don't use the stack for function calls and have 
arguments in registers and the return address in a special (or at least 
fixed) register.  It's virtually impossible with anything that uses a 
caller-cleanup function calling sequence (most C compilers used to do 
that by default in order to support varargs, newer ones tend to use 
callee-cleanup when they can) unless the current function and the 
function being tail-called have the same number of bytes of arguments.

2) optimization problems.  Things which are prima facie tail calls are 
easy, but Kent is appearing to indicate that users want compilers -- and 
even interpreters -- to do sufficient analysis to recognize some things 
that are NOT tail calls but that can be *optimized* into tail calls.

I guess Kent's example is a good one:

 (define (factorial x n)
   (if (zero? x) n
       (let ((result (factorial (- x 1) (* x n))))
         result)))

He says some (undefined) people would expect the recursive call to 
factorial to be a tail call even though it's not prima facie a tail call.

Expanding the definition of "let" this actually means...

 (define (factorial x n)
   (if (zero? x) n
       ((lambda (result) result)
        (factorial (- x 1) (* x n)))))

... which is ...

 (define (factorial x n)
   (if (zero? x) n
       (anon-lambda (factorial (- x 1) (* x n)))))

 (define (anon-lambda result)
   result)

Either way it's pretty clear that you need some pretty serious compiler 
work to implement "inlining" in order to allow the call to factorial to 
be a tail-call.  And even then it would *not* be a tail call if there 
was another expression in the body of the lambda/let ... especially one 
with side-effects such as something like (format "factorial %d = %d\n" x 
result).

I don't have a problem with saying that this is too complicated a 
situation to expect an interpreter or simple compiler to analyse and 
that if something isn't prima facie a tail-call then you can't *count* 
on it being optimized as one.

But you might get lucky with a sufficiently smart compiler.

-- Bruce
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwk7yfo19g.fsf@world.std.com>
Bruce Hoult <·····@hoult.org> writes:

> So, what is the difficulty in deciding whether or not a function should 
> be tail called?  It should be tail called If and Only If the result of 
> the current function is exactly the result of the function to be called.  

And otherwise what? Signal an error? Do a regular call?

> That is, no calculations are to be done using the result of the function 
> being called and no environment cleanup is required (exception handlers, 
> destructors in languages which have them, etc...).

What is "the function being called"?  Can I tail-call from 
 (defun foo (x) (foo #'(lambda () (tail-call (foo 3)))) (bar))
What if I have a with-foo macro that lets me write this as
 (defun foo (x) (with-foo (tail-call (foo 3))) (bar))
Does the user have to be told I'm wrapping a lambda in there?
This all sounds messy and ad hoc.  
  
My main point is not that it can't be done, but that the RIGHT way to 
do it is for a vendor to do it, present it to users, let it get
pounded on for a while, and then report EXPERIENCE.  Vendors are equipped
to sense whether risks like this are worthwhile and to track the response.
If no vendor wants to do it (and I included "free software" makers as
vendors here, since they aren't selling for money but they still are 
putting their reputation on the line, and risking their email box will fill),
there's little point to bugging the community as a whole to do it.
Standards work is not a place to do design; it is a place to consolidate
design among vendors that have either all done the same thing or have made
divergent solutions to a common problem that need tweaking in order to get
in line.

> It seems to me that the only problems that arise are:
> 
> 1) the actual mechanics of doing the tail-call at the machine level.  
> This can require a certain amount of stack-munging to pop off the return 
> address and old arguments, push the new arguments and re-push the return 
> address, and then jump to the tail-called function.  This is easier with 
> e.g. RISC CPUs which don't use the stack for function calls and have 
> arguments in registers and the return address in a special (or at least 
> fixed) register.  It's virtually impossible with anything that uses a 
> caller-cleanup function calling sequence (most C compilers used to do 
> that by default in order to support varargs, newer ones tend to use 
> callee-cleanup when they can) unless the current function and the 
> function being tail-called have the same number of bytes of arguments.
> 
> 2) optimization problems.  Things which are prima facie tail calls are 
> easy, but Kent is appearing to indicate that users want compilers -- and 
> even interpreters -- to do sufficient analysis to recognize some things 
> that are NOT tail calls but that can be *optimized* into tail calls.

I think it's backwards of this--that people doing code-transformation
want to know that the mere introduction of a named binding doesn't
obscure the meaning.  But it's equivalent, I guess.  I didn't mean
this to be a claim that it can't be done--just that a lot of people
who want this want it BECAUSE it can be depended upon, and so you have
to specify what can be depended upon clearly enough that they can
reliably tell the difference between what they can and cannot depend
upon.

> I guess Kent's example is a good one:
> 
>  (define (factorial x n)
>    (if (zero? x) n
>        (let ((result (factorial (- x 1) (* x n))))
>          result)))
> 
> He says some (undefined) people would expect the recursive call to 
> factorial to be a tail call even though it's not prima facie a tail call.
> 
> Expanding the definition of "let" this actually means...
> 
>  (define (factorial x n)
>    (if (zero? x) n
>        ((lambda (result) result)
>         (factorial (- x 1) (* x n)))))
> 
> ... which is ...
> 
>  (define (factorial x n)
>    (if (zero? x) n
>        (anon-lambda (factorial (- x 1) (* x n)))))
> 
>  (define (anon-lambda result)
>    result)
> 
> Either way it's pretty clear that you need some pretty serious compiler 
> work to implement "inlining" in order to allow the call to factorial to 
> be a tail-call.  And even then it would *not* be a tail call if there 
> was another expression in the body of the lambda/let ... especially one 
> with side-effects such as something like (format "factorial %d = %d\n" x 
> result).
> 
> I don't have a problem with saying that this is too complicated a 
> situation to expect an interpreter or simple compiler to analyse and 
> that if something isn't prima facie a tail-call then you can't *count* 
> on it being optimized as one.
> 
> But you might get lucky with a sufficiently smart compiler.
> 
> -- Bruce
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-798CDB.09442402102001@news.akl.ihug.co.nz>
In article <···············@world.std.com>, Kent M Pitman 
<······@world.std.com> wrote:

> Bruce Hoult <·····@hoult.org> writes:
> 
> > So, what is the difficulty in deciding whether or not a function should 
> > be tail called?  It should be tail called If and Only If the result of 
> > the current function is exactly the result of the function to be 
> > called.  
> 
> And otherwise what? Signal an error? Do a regular call?

??  there is no otherwise.

A function call is either the last function call in the body of the 
function or else it is not.  If it's last then tail-call it (with a 
"jump"), if it's not last then call it the regular way (with a "jump to 
subroutine").

"Last" means either the last expression evaluated in the function.  This 
means either being physically last, or else being the last expression in 
an IF or COND (etc) that is physically last in the function. 


> > That is, no calculations are to be done using the result of the 
> > function 
> > being called and no environment cleanup is required (exception 
> > handlers, 
> > destructors in languages which have them, etc...).
> 
> What is "the function being called"?

Any function being called from another function.


>  Can I tail-call from 
>  (defun foo (x) (foo #'(lambda () (tail-call (foo 3)))) (bar))

I don't know what "#'" means.  And why are you introducing this 
"tail-call" construct again after I pointed out that it is totally 
unnecessary?


> What if I have a with-foo macro that lets me write this as
>  (defun foo (x) (with-foo (tail-call (foo 3))) (bar))
> Does the user have to be told I'm wrapping a lambda in there?
> This all sounds messy and ad hoc.  

Any "with-XXX" is a red flag that there is cleanup code executed after 
the user's code.  This automatically makes tail-calls of the user's code 
impossible, since it is not in tail position within the function.

-- Bruce
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3210968437468418@naggum.net>
* Bruce Hoult
| A function call is either the last function call in the body of the
| function or else it is not.  If it's last then tail-call it (with a
| "jump"), if it's not last then call it the regular way (with a "jump to
| subroutine").

  But this is not quite as trivial as people seem to think.  There are all
  kinds of low-level cleanup issues that may not be visible or even known
  to the programmer and which the compiler writer may need to be told about
  (either through a formal specification of a requirement or by invoking a
  local declaration) before it makes sense to allow for making a tail call
  into a jump.  Also note that the callee needs to believe that it was
  called, unless the language has specified semantics to this effect, too.

| "Last" means either the last expression evaluated in the function.  This
| means either being physically last, or else being the last expression in
| an IF or COND (etc) that is physically last in the function.

  I think you might mean the form that evaluates to the value of the
  function.  It might, for instance, be possible to unwind special bindings
  and unwind-protect forms in such a way that you could jump to a function
  with it returning to the cleanup forms, so the notion of "last" might be
  very confusing and counterproductive.

///
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-196BBB.16114602102001@news.paradise.net.nz>
In article <················@naggum.net>, Erik Naggum <····@naggum.net> 
wrote:

> * Bruce Hoult
> | A function call is either the last function call in the body of the
> | function or else it is not.  If it's last then tail-call it (with a
> | "jump"), if it's not last then call it the regular way (with a "jump to
> | subroutine").
> 
>   But this is not quite as trivial as people seem to think.  There
>   are all kinds of low-level cleanup issues that may not be visible
>   or even known to the programmer

Yes and I covered several of these in my previous message:

  <···························@news.akl.ihug.co.nz>


>   I think you might mean the form that evaluates to the value of the
>   function.  It might, for instance, be possible to unwind special 
>   bindings and unwind-protect forms in such a way that you could jump
>   to a function with it returning to the cleanup forms, so the notion
>   of "last" might be very confusing and counterproductive.

I covered this in that previous message as well.

Anything subject to unwind protect or the like is prima facie NOT in 
tail position.  It is possible that a smart compiler may be able to 
optimize things so that it can be moved into tail position, but that's 
not something you could depend on.

It seems especially dangerous to move something out from the body of an 
unwind-protect -- you'd have to prove first that it was impossible for 
it to throw.

-- Bruce
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211000707208480@naggum.net>
* Erik Naggum
| I think you might mean the form that evaluates to the value of the
| function.  It might, for instance, be possible to unwind special 
| bindings and unwind-protect forms in such a way that you could jump
| to a function with it returning to the cleanup forms, so the notion
| of "last" might be very confusing and counterproductive.

* Bruce Hoult
| I covered this in that previous message as well.
| 
| Anything subject to unwind protect or the like is prima facie NOT in
| tail position.  It is possible that a smart compiler may be able to
| optimize things so that it can be moved into tail position, but that's
| not something you could depend on.

  If you had covered it, you would have known that this is not simply an
  "optimization", but a design decision that would have to be taken if you
  wanted true support for tail calls.  Both special bindings and unwind-
  protect are so common in Common Lisp programs that separating them from
  the call stack would have been a _necessary_ design decision, in order
  that tail calls actually produce useful results.  Since you said you had
  covered this, contrary to my usually flawless short-term memory (I study
  SQL intensely right now, so there may be some irregular lossage :), I
  looked for where you had, but I cannot find it.

| It seems especially dangerous to move something out from the body of an
| unwind-protect -- you'd have to prove first that it was impossible for it
| to throw.

  This is utter nonsense.  Please try to understand how a system that does
  unwind forms _must_ set up these things.  The body forms in practice _do_
  return, throws or not, through a chain of unwind form, including special
  bindings, and whether you do this by setting up in-band catcher code or
  through a separate stack of unwind-forms-to-be-executed, is immaterial.
  If you do the latter on a separate stack, there is no difference between
  a normal return from the code that sets it upand a return from somewhere
  else: the setup has already been done and the execution is performed as
  part of the return procedure.

  If you decide to mandate tail-call optimization into jumps, this design
  would have to be "required" of an implementation because refusing to do
  tail calls simply in the face of special bindings would just be stupid --
  having an elegant solution as it does.  If you do not mandate it, you can
  get away with only "simple" tail-call optimization, and you can get an
  even more inexpensive tail-call technique when you get it, if you get it.

  In essence, I believe "properly tail-recursive" is a design decision that
  affects how the call stack must be designed (and continuations fall right
  out of a heap-allocated call frame and chain design) and how you can deal
  with special bindings and unwind-protect (it is no accident that Scheme
  does not), while tail-call _optimization_ is possible even in languages
  such as ANSI C.  I therefore also believe that it is a _disservice_ to
  the community to require "properly tail-recursive" because it requires
  too many negative and unwanted properties of the implementation, but that
  it is perfectly legitimate to ask the implementations for such a feature.
  Amazingly, this is the actual situtation: E.g., Allegro CL does a very
  good job of optimizing tail calls to both self and non-self if you ask
  for it in their proprietary way, but optimization _is_ a "proprietary"
  process, anyway.  I think some language designers fail to understand this
  and believe in _mandated_ optimization, which only limit and restrict how
  their languages may be implemented to the techniques known at the time
  their favorite optimization theory was in vogue, or techniques developed
  along that evolutionary branch.  The less you prescribe in terms of how
  to optimize, the more you can take advantage of free-ranging research in
  optimization, both software and hardware.  This is why C is _still_ best
  optimized for PDP-11-style processors.
  
///
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-899249.23111802102001@news.paradise.net.nz>
In article <················@naggum.net>, Erik Naggum <····@naggum.net> 
wrote:

> * Erik Naggum
> | I think you might mean the form that evaluates to the value of the
> | function.  It might, for instance, be possible to unwind special 
> | bindings and unwind-protect forms in such a way that you could jump
> | to a function with it returning to the cleanup forms, so the notion
> | of "last" might be very confusing and counterproductive.
> 
> * Bruce Hoult
> | I covered this in that previous message as well.
> | 
> | Anything subject to unwind protect or the like is prima facie NOT in
> | tail position.  It is possible that a smart compiler may be able to
> | optimize things so that it can be moved into tail position, but that's
> | not something you could depend on.
> 
>   If you had covered it, you would have known that this is not simply an
>   "optimization", but a design decision that would have to be taken if 
>   you
>   wanted true support for tail calls.

I don't know why, but you and Kent seem to want a lot more from "tail 
call optimization" than I do.

Or, rather, you *don't* want it and so insist that if it can't work in 
every conceivable situation then it isn't worth having at all.


>   Both special bindings and unwind-
>   protect are so common in Common Lisp programs that separating them from
>   the call stack would have been a _necessary_ design decision, in order
>   that tail calls actually produce useful results.

That would depend on what you regard as "useful" results.

So you can't do useful tail call elimination on things which are wrapped 
inside "clean up after use" constucts?  I agree and that's just fine by 
me.  Why do you think such constructs are a useful target for tail call 
optimizations?  Do you frequently write recursive functions or sets of 
mutually-recursive functions in which each call invokes another layer of 
cleanup?  Can you give an example?  In such situations it's always 
seemed considerably cleaner to me to put the cleanup constructs at an 
outer level and let some inner stuff recurse away merrily sans 
intermediate cleanup.

One of the main reasons to support tail call optimization is so that you 
can CPS-convert code in certain circumstances.  Such code certainly 
never has cleanup after the tail call -- the tail calls of the 
continuation are *never* going to return.  That's the point.

The same goes for loops of various sorts expressed as recursive 
functions or sets of mutually-recursive functions.  Think of them as a 
different way of expressing iteration.  Would you want to insert another 
layer of cleanup code every time you executed the start of a loop?  Why?


> | It seems especially dangerous to move something out from the body of an
> | unwind-protect -- you'd have to prove first that it was impossible for 
> | it to throw.
> 
>   This is utter nonsense.

No it is not.  You'd have to prove a bunch of other things as well, of 
course, but that one alone is sufficient to show that it's very very 
difficult to impossible to convert something inside an unwind-protect 
into something that can be tail-called.


>   Please try to understand how a system that does
>   unwind forms _must_ set up these things.  The body forms in practice 
>   _do_ return, throws or not, through a chain of unwind form

Indeed.  Which is precisely why using a tail-call for things inside an 
unwind-protect is a nonsense.  The whole *point* of the tail call is 
that it doesn't return and that you can therefore immediately throw away 
(and make available for immediate reuse) all resources used by the 
current function because the entire result of that function is now 
described by the function you're about to call (together with the 
arguments being passed to it).  Anything that you have to come back and 
do later (e.g. unwind) totally nullifies that.


>   whether you do this by setting up in-band catcher code or
>   through a separate stack of unwind-forms-to-be-executed, is immaterial.
>   If you do the latter on a separate stack, there is no difference 
>   between
>   a normal return from the code that sets it upand a return from 
>   somewhere
>   else: the setup has already been done and the execution is performed as
>   part of the return procedure.

This is true, but it's pretty pointless.  In a recursive call that 
contains an unwind-form you're going to grow your stack of 
unwind-forms-to-be-executed without bound.  Sure, you'll save a little 
memory because the structure that you put on that stack each time is 
probably a little smaller than the entire stack frame for the function 
would be, but that's just a savings of a constant factor -- it doesn't 
make a tail-recursive function (or set of mutually tail-calling 
functions) execute in constant space, which is the purpose of the 
optimization.


>   I therefore also believe that it is a _disservice_ to
>   the community to require "properly tail-recursive" because it requires
>   too many negative and unwanted properties of the implementation

I certainly agree that it is undesirable to require what you appear to 
mean by "properly tail-recursive", party because it would appear to be 
impossible, and partly because I can't see any situation in which it 
would be more useful than a much simpler definition of "properly 
tail-recursive".

-- Bruce
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211018454924999@naggum.net>
* Bruce Hoult
| I don't know why, but you and Kent seem to want a lot more from "tail
| call optimization" than I do.

  Huh?  I have argued for a differentiation of "properly tail-recursive"
  and "tail call optimization".  What I want from tail call optimization is
  meant to be a severe restriction of what Scheme freaks want from their
  undesirable "properly tail-recursive" concept.

| Or, rather, you *don't* want it and so insist that if it can't work in
| every conceivable situation then it isn't worth having at all.

  Are you nuts?  Where did you get this garbage?  Of course I want it.
  However, I do not want it mandated by the standard, because that has a
  lot of negative aspects to it.  Hell, if I want a standard that says
  something so annoying as requiring a properly tail-recursive execution
  model, I would use Scheme.  I loathe Scheme.  Get a grip, Bruce, quit
  pretending that you are fighting an enemy you are not.  Please realize
  that there is a difference between a desire for a feature and a desire
  for a law (or specification) that require others to provide a feature.
  You seem to confuse the two, or at least not allow other people (Kent and
  me?) to make a distinction, and get all flustered because you think we do
  not want what we do not want there to be a law to _require_.

  I believe the rest of your message rests on these false premises, too.

| No it is not.

  Christ, Bruce, THINK!  I have no patience with this crap.  Go back and
  read what I wrote.  You have clearly not understood what I said, and now
  you argue against something you have misunderstood.  Quit that stupidity.

| You'd have to prove a bunch of other things as well, of  course, but that
| one alone is sufficient to show that it's very very  difficult to
| impossible to convert something inside an unwind-protect  into something
| that can be tail-called.

  This is just simply wrong.  _Listen_, now, OK?  

| Indeed.  Which is precisely why using a tail-call for things inside an 
| unwind-protect is a nonsense.

  You obviously fail to think about this, and that annoys me.  Consider
  this simple fact: What a function returns to is _not_ under its control.
  If a function returns to its caller, which does some work before it also
  returns, it could as well return to a different function that does that
  work.  This is the core of the argument for tail-call optimzation, is it
  not?  Why does it _not_ apply to unwind forms?  _Think_ about this, OK?

| The whole *point* of the tail call is that it doesn't return

  Huh?  What is this nonsense?  On the contrary, the whole point is that it
  returns to the caller of the caller in place of the caller, which has
  _redirected_ it to return elsewhere from what _its_ caller said it should
  return to.  The whole *point* with tail calls is that there is a simple
  semantic equivalence between call/return/return and jump/return, and that
  this is completely independent of _both_ what you jump _and_ return to.
  What I suggest is an _expansion_ of the mere implementation of tail-call
  optimization such that you can call functions for their side-effect after
  a call that returns a value simply by returning to them instead of
  callling them.  That is well within the conceptual framework of what you
  seem to be clamoring for.  Why do you not get this simple application of
  your concept of tail-call optimization?

| Anything that you have to come back and do later (e.g. unwind) totally
| nullifies that.

  _Wrong_.  _THINK_ about this, will you?  _How_ you arrange for something
  to be done after you have returned is ORHTHOGONAL to arranging to do it.

| This is true, but it's pretty pointless.  In a recursive call that
| contains an unwind-form you're going to grow your stack of
| unwind-forms-to-be-executed without bound.

  So you think tail-call optimization is _only_ for recursion?  What _is_
  this?  Bruce, you _are_ not the idiot you seem to be insisting on showing
  me right now.  What has gotten into you?  The Scheme freaks want a
  properly tail recursive execution model for a number of reasons.  Tail
  call optimization is a much weaker concept, and it will actually save a
  lot of stack space -- namely the stack space consumed by arguments and
  call frames -- _even_ if you have special bindings and unwind-protect
  forms.  _Those_ are the worth-while savings.  And what is this silly
  thing about "without bound"?  There will be no more and no less bound on
  the recursion regardless of tail-call optimization!  If you can save on
  _some_ resources, but not all, why are _you_ opposed to that?  Was it not
  _you_ who accused Kent and me of the following just a few lines up?

| Or, rather, you *don't* want it and so insist that if it can't work in
| every conceivable situation then it isn't worth having at all.

  Now _you_ are arguing this silly position which nobody else holds.  Quit
  the stupidity and _think_ about what you are saying, damn it!

| Sure, you'll save a little memory because the structure that you put on
| that stack each time is probably a little smaller than the entire stack
| frame for the function would be, but that's just a savings of a constant
| factor -- it doesn't make a tail-recursive function (or set of mutually
| tail-calling functions) execute in constant space, which is the purpose
| of the optimization.

  Why do you restrict an optimization to such a purpose?  Why do you need
  there to be "constant space" when there is a clear and present benefit
  from not over-using another resource?  And why do you drag in the _size_
  of call frames?  Christ, Bruce, that is _so_ beside the point.  Call
  frames can be *large* these days.  An unwind-protect/special stack can be
  very small.  Now _you_ want to discard the stack space savings because
  you will still need special/unwind-protect space?  What _is_ this?

  Whatever _are_ you defending, Bruce Hoult?  Whatever it is, I can assure
  you that it is _not_ under attack.  Just _think_ about the arguments you
  get and you might learn something that you are currently insisting on
  _not_ seeing.

///
From: Tim Bradshaw
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <nkjwv2edqyu.fsf@davros.tardis.ed.ac.uk>
Erik Naggum <····@naggum.net> writes:

> 
>   Are you nuts?  Where did you get this garbage?  Of course I want it.
>   However, I do not want it mandated by the standard, because that has a
>   lot of negative aspects to it. 

I sometimes think that some of these issues would be clearer if
something slightly less emotive than tail-call elimination was at
stake.

Imagine, for instance if the CL language standard mandated that at
certain levels of optimisation, bounds checking should not be done for
arrayss.  Is this reasonable?  No, it's obviously absurd, since it
makes all sorts of hairy requirements on implementations.  Worse than
this, it's absurd for implementations which manage to transform
something like this:

  (dotimes (i 10000)
    (setf (aref a i) 0))

into

  (progn
    (when (< (length a) 10000)
      (error ...))
    (dotimes (i 1000)
      (setf (system::unchecked-aref a i) 0)))

Such an implementation has already essentially removed the
bounds-checking overhead: should it be required to remove the
remaining check, and if so why? (perhaps to make C programmers more
comfortable by introducing subtle and obscure vulnerabilities for no
benefit).

It's also absurd for implementations which run on hardware that
supports bounds checking.

Finally, it's absurd because attempting to overcome problems like the
above *in the standard* would result in a vast document which
attempted to cover all sorts of obscure possible implementation
quirks.  Even if such a standard could be written, it would very
likely become obsolete as new implementation techniques were
discovered.

So a standard should not mandate this.  At best it should hint that it
would be a good idea, but language like that has little place in a
standard!.

But this is obviously completely different than saying that
implementations should not remove bounds checks at certain levels of
optimisation.  Of course they should be allowed to do that, and good
implementations probably will (and, I hope, some will do this raising
of checks out of loops to get the best of both worlds).

One thing a standard *could* do is to mandate that something
*equivalent* to bounds checking *must* be done at certain levels of
optimisation.  This is much easier to assert in a standard because it
merely has to say that `an error should be signaled if ...' in the
terminology of the Cl standard (I don't, off the top of my head, know
if the CL standard does in fact mandate this) .

The same, I think is true for tail-call elimination.  For the standard
to mandate that it must happen in a way that has meaning would require
an enormous amount of hair in the standard, some small amount of which
Erik has demonstrated. This hair would probably become obsolete in a
rather short time.  Far better for the standard to remain silent on
the issue and leave it up to implementors.

--tim
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87n133fqda.fsf@pps.jussieu.fr>
EN>   Hell, if I want a standard that says something so annoying as
EN>   requiring a properly tail-recursive execution model, I would use
EN>   Scheme.

I, on the other hand, want to use CL (which I happen to find more
comfortable than Scheme) and still require that tail-call elimination
should be performed in some circumstances.

Tail-call elimination, by the way, is not specific to Scheme, and is
mandated for example by ML.  (Both major dialects, I believe.)

EN>   So you think tail-call optimization is _only_ for recursion?
EN>   What _is_ this?

It is only with recursion that tail-call elimination makes a
qualitative (as opposed to merely quantitative) difference.

Without recursion, tail-call elimination can provide you with a
(possibly large) *constant* space saving.  While this may be worth
having, it is not something that belongs in a language definition.

With recursion (possibly indirect), tail-call elimination will, under
the right circumstances, convert unbounded space usage into constant
space.  With some programming techniques, this means turning a
guaranteed crash into a reliable program.

Regards,

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211576792209248@naggum.net>
* Juliusz Chroboczek <···@pps.jussieu.fr>
| I, on the other hand, want to use CL (which I happen to find more
| comfortable than Scheme) and still require that tail-call elimination
| should be performed in some circumstances.

  But you are not satisfied with it being done when you ask for it.  You
  seem to think that if it is required in the standard, you will also be
  "guaranteed" to actually get it from your vendors, but it is the other
  way around.  There are still lots of things that the standard mandates
  that people are not implementing exactly the same way.  Like directory,
  which we saw just recently.  However, instead of arguing for how it
  should be implemented and finding ways to get this, people get agitated
  to the point of looking like they are _betrayed_ because the standard is
  not sufficiently clear, as if it owed them that.  I find this very odd,
  but it _is_ also something that happens again and again in this forum.

| With recursion (possibly indirect), tail-call elimination will, under the
| right circumstances, convert unbounded space usage into constant space.
| With some programming techniques, this means turning a guaranteed crash
| into a reliable program.

  If so, why is it not sufficiently to actually get what you want?  Why do
  you have to have a "guarantee" to get it via the standard?  Do you think
  you will pay less for it if people "had" to do it than if you ask for it?

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87u1x9t1ol.fsf@pps.jussieu.fr>
EN>   However, instead of arguing for how it should be implemented and
EN>   finding ways to get this, people get agitated to the point of
EN>   looking like they are _betrayed_ because the standard is not
EN>   sufficiently clear, as if it owed them that.

I don't think that's a fair assessment.  Unlike some other languages,
Common Lisp is specified with enough rigour to make it possible to
write real code to the spec.  I have found that the only cases when I
consciously wrote non-portable Common Lisp code were either when I was
writing intrinsically platform-dependent stuff, or when relying on the
tail-call elimination behaviour of the implementation that I use.

In the light of the above, I think it is only natural to want to
attract the community's attention to this issue.

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw3d526r8g.fsf@world.std.com>
Bruce Hoult <·····@hoult.org> writes:

> I don't know why, but you and Kent seem to want a lot more from "tail 
> call optimization" than I do.
> 
> Or, rather, you *don't* want it and so insist that if it can't work in 
> every conceivable situation then it isn't worth having at all.

Bruce, I'm just engaging in conversation in my free time here.  I'm
tossing out issues I'm aware of for the sake of mixing the
conversation.  I'm not waging a campaign.

The fact is that the language isn't changing any time soon.

But the fact is also that when the language does change, there are certain
"reasonable care" requirements that implicitly apply.  Users often don't
mind if all kinds of issues are unresolve--most users only use 90% of the
language anyway, and would kill for us to offer only 90% of a certain
functionality and toss the rest.  Like XML without DTD's for example.
A user can write a library that only implements 90% of the spec and it can
be very popular because they have to answer to no one for not doing the rest.
"Fine, don't use it if you don't like it" is all they have to say.   A vendor,
on the other hand, has a higher degree of care required because he might
advertise an XML implementation and people would take them "more seriously"
and be more surprised if it wasn't complete, even if not advertised as such.
Or they might want it to be more complete next release because they assume
they're buying into a process, whereas user libraries are often one-off
take it or leave it.  Language standards are even more complex because the
designers are NOT gating the introduction of extensions which can already
mostly be done by vendors but RATHER are imposing requirements on vendors.

In spite of how it seems to users,more like ain the US where we have
the all-encompassing Federal government and a local State government
both affecting individuals, and the Federal goverment is often
creating what are called "unfunded state mandates", where they require
the State government to do something but don't say where the money is
supposed to come from or how the program is to be implemented in
finite resources.  There is, or should be at least, a large burden on
the people who impose such burdens to really show that it can be done.
So in a sense, and as a result the Federal government's key "design
job" should be to "be skeptical about the inclusion of anything
because it's so hard to tell that it will be universally a good idea.
One state may love it and already implement it, while another state may
be full of people who left that state to escape it!  

I hope you can see that this analogy is true of language design as well.
It works better on the first round, when no one has anything and you 
impose a bunch of things because you're in "attract mode" and you hope
people will gravitate to your language.  But then when they have done it
you have a more complex problem because people have already adhered
"geographically" to vendors they like and now if you mix the soup, you
are not just affecting your "attraction pitch" but you are potentially
driving people away  from something they have grown to like.  People don't
like having to up and leave a country they've settled just because some
big government suddenly decides the local laws aren't good enough.

As such, I see my role as a language designer is to keep track of lots
of facts and ideas and preconceptions that people have and regurgitate
them at the right time so that a later discussion on an idea is not left
with the simple feeling that just because the people who argued about
it before are out of the room, it has no opposition.  It is sometimes,
therefore, the case that I will raise an issue in a slightly ill-formed
way because I can't quite remember how it was best expressed, whether 
it was me or someone else who expressed it.  I'd rather you beat me up
for being vague or confused than have someone else beat me up for having
forgotten they cared about some issue at all.  

And, in general, whenever I have marked an issue as "complex and
controversial" in my mind, I try not to let anyone get away with
convincing me it's "simple".  It might be simple.  And I might
eventually be convinced such.  But I have an obligation because of how
I see my role to be considerably more obstinate than others in the
same discussion might be unless I'm sure that all parties with the
relevant concerns are properly represented by competent members of the
discussion.

So please don't oversimplify my position as to say "oh, you just don't
want this".  The fact is that I know there are a lot of people who think
that this is not a no-brainer, and I regard standards-making as something
that has to involve consensus-building, so the solutions and techniques
I seek are based on the standard ways of doing that.

In the past, "lexical scoping", "CLOS", the "CL condition system" and
even (I kid you not) "base 10 numbers" (it was base 8 in Maclisp, and
it was a BIG fight to get it changed to base 10 by default) were
controversial.  Sometimes people worry over things that are either
nothing to worry about at all (like "lexical scoping") or things that
even though controversial are too big a win to ignore (like "CLOS").
Whatever the case, starting small, and demonstrating "no ill effects"
in a small community is a better way to push forward.  For lexical
scoping, "starting small" meant pointing to Algol and enough people
bought in at the start of CL that we took the "big risk".  For CLOS,
"starting small" meant a portable implementation that people could
try; ditto for the CL condition system.  (Base 10 was just achieved by
political coup, with no more demonstration of workableness than "all
human history to date", but I'm proud to say I supported it all along.
:-)

My obstinance here is more of the form "wrong community to try to convince
first" and "here are some issues to think about" than "this is why you
will fail".  I'm only trying to say "start small" and "think about these
things".  Beyond that, I'm willing to sit back and wait and watch.

> >   Both special bindings and unwind-
> >   protect are so common in Common Lisp programs that separating them from
> >   the call stack would have been a _necessary_ design decision, in order
> >   that tail calls actually produce useful results.
> 
> That would depend on what you regard as "useful" results.

Language designers are obliged to think of something being a concern if
anyone might not think something useful.  All that is being raised in this
context is "lack of specificity".  You can't just say "tail recursion is
good" and expect people to join on.  Make a clear spec of what you're
after and you'll win at least some converts just by making something clear
that people can inspect. They can't inspect your intent if you have no 
spec--it enables you to be (accidentally or intentionally, I'll assume
accidentally but others may not) slippery, and that won't help you win
the debate.

> So you can't do useful tail call elimination on things which are wrapped 
> inside "clean up after use" constucts?  I agree and that's just fine by 
> me.

Users say this all the time.  They'd gladly sacrifice any feature they
don't use in order to get the part they do.  But your real debate error
here is making this "exception" part of your debate about why to accept
this notion rather than making it part of the definition of the notion
you want to debate.  You're effectively, by taking this posture, asking
people to debate multiple possible specs.   Yes, picking a particular
semantics is riskier -- you want anything in this area and nailing down
one semantics may lose you the debate when you were entitled to win
in another.  But you can always have a second debate from what you learn
on this one.  Don't have the debate on all at once or people will think
it imprecise and you'll lose anyway.

> Why do you think such constructs are a useful target for tail call 
> optimizations?

Because they might be and because you have not specified otherwise.
We're debating something too fuzzy and in the wrong forum.

> Do you frequently write recursive functions or sets of 
> mutually-recursive functions in which each call invokes another layer of 
> cleanup?  Can you give an example?  In such situations it's always 
> seemed considerably cleaner to me to put the cleanup constructs at an 
> outer level and let some inner stuff recurse away merrily sans 
> intermediate cleanup.

The burden is not on them, it's on you.  You've asked people to consider
an abstract.  If you'd made it concrete, they might give you an example.
But it's worth no one's time to make an example if you can change the
semantics out from underneath them.  They need to construct the example 
based on fixed rules, not construct an example that can be debunked by
changing the rules.
 
> One of the main reasons to support tail call optimization is so that you 
> can CPS-convert code in certain circumstances.

I could be mistaken, but I don't think you're losing this argument for
lack of positives.  I think  you're losing it for lack of helping people
dismiss their negatives.  People may or may not want to CPS convert their
code; some may not realize they want to use tools that want this.  But it
doesn't matter.  This is the wrong discussion focus, IMO.

> Such code certainly 
> never has cleanup after the tail call -- the tail calls of the 
> continuation are *never* going to return.  That's the point.

One of the reasons for tail call elimination is that people can rely
on it.  Lisp has macros that might obscure that, and they need to have
the sense that the "guarantee" it offers will be meaningful.  This is
not an irrelevant point.

Consider the US "star wars" missile defense system.  No one who opposes
it is opposing the argument "you want to get rid of incoming missiles".
They're opposing it because they're not sure it can really guarantee 
that claim; to change their mind, you're wasting your breath to expound
on the virtues.  You'd better start enumerating the negatives.
 
Whatever you might think here, you're really addressing the negative
whose nam eis "I'm not sure I can see when this is going to fail".
You will not address this by saying "I am sure."  You must address it by
(a) making af fixed set of rules and (b) under that fixed set of rules,
calling for examples of potential problems and then debugging the 
concerns in concrete fashion.  Or you will get nowhere.

[some stuff omitted]
> I certainly agree that it is undesirable to require what you appear to 
> mean by "properly tail-recursive", party because it would appear to be 
> impossible, and partly because I can't see any situation in which it 
> would be more useful than a much simpler definition of "properly 
> tail-recursive".


But the burden is not on the people not asking for the facility to define
the facility.  You have not defined what YOU mean in formal terms, and yet
you ask those who have no sense of needing or wanting it to first define
it to your satisfaction and THEN debunk it.  That's more burden than they
ought be obliged to take on.  Rather like asking the Amish community in
America to sit on US trade boards so they can define clearly the technologies
they don't want to accept into their lifestyle.  It isn't their burden to
do that.   They are entitled to the luxury of sitting back, waiting for
the design to happen, and then deciding.  Yes, that involves more up-front
risk on your part, but you are the one who wants to move ahead, not they.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87r8sffr01.fsf@pps.jussieu.fr>
EN>   Both special bindings and unwind- protect are so common in
EN>   Common Lisp programs that separating them from the call stack
EN>   would have been a _necessary_ design decision, in order that
EN>   tail calls actually produce useful results.

No, Erik, that would not be reasonable.  It would merely replace space
used on the call stack to space used on the dynamic bindings stack or
the unwind stack.

I think the correct approach here would be to ask the users who
actually do require tail-call elimination for their programming style
to specify a minimal set of conditions under which tail-call
elimination should be performed.  In my personal case, I need the
following:

 - tail-call elimination between source files and (less often) between
   packages;
 - tail-call elimination within and to a CLOS generic function;
 - eliminating a tail FUNCALL to a funarg, at least one with a null
   lexical environment.

The obvious case -- tail call to self -- is not interesting, as I
usually find it more natural to replace it with one of the standard
iteration constructs.

Regards,

                                        Juliusz

P.S. Sympathy for your SQL work.  I'm studying XSLT right now; any
     chance for a rant on the subject?
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87vghrfrzy.fsf@pps.jussieu.fr>
FR> Why not instead have an explicit (TAIL-CALL (F X)) special form,
FR> or otherwise two special forms (TAIL-APPLY #'F (LIST X)) and
FR> (TAIL-FUNCALL #'F X) ?

Hmm, interesting idea.  Seems Forth-like to me, though, rather than
Lisp-like.  I'm not sure whether I like it, but perhaps I've simply
had too much exposure to ML.

Note, by the way, that including such a function in CL naively would
break certain structural invariants.  Changing your proposed syntax
slightly, the following

  (declaim (special *foo*))

  (defun break-the-system ()
    (let ((*foo* 47))
      (tail-call #'identity nil)))

would cause the virtual machine to push a dynamic environment and jump
into IDENTITY without leaving the dynamic stack unwinding code in the
continuation.  A similar example could be built with UNWIND-PROTECT.

Of course, such cases can be detected statically (i.e. at compile
time) and could be forbidden, but requiring that an implementation
perform such checks imposes a similar burden to requiring automatic
tail-call elimination.

                                        Juliusz
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87zo73fsr4.fsf@pps.jussieu.fr>
>> Not a very interesting semantics.

KMP> We're busy building programs are interesting, not semantics that
KMP> are interesting. Our dull semantics has not seemed to impede
KMP> that.

Sorry, I was arguing that this is *not* the semantics of Common Lisp.

KMP> It is specifically left to the market to sort out ...

With all due respect, Kent, you overuse this argument.  Pushing it to
its logical extreme, we have no use for a carefully drafted Common
Lisp definition, because the market will choose right anyhow.

When I want something outside the standard implemented in my CL
system, I either go and speak with my vendor, or implement it myself.
Notwithstanding that possibility, I am grateful for the existence of a
rigorously drafted definition of Common Lisp that allows me to write
reliable programs reasonably independently of the implementation.  Alas,
the said definition does not include guarantees about tail-call
elimination are not included in that definition; I would like to see
such guarantees included in a future version.  (Whether my vendor does
actually perform tail-call elimination or not is a completely distinct
argument).

At the same time, I am conscious of the fact that the CL community
(including myself) is not ready right now for a new revision of the
standard.  This doesn't preclude people from making notes about what
users would want to see in a future version; tail-call elimination is
high on my list of desired futures.

(Another thing I would like to see are stronger guarantees about what
conditions are signalled in exceptional situations; again, I do not
have problems with my vendor here, as I can always test my implemen-
tation's behaviour, but with the standard itself.)

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211542753013083@naggum.net>
* Juliusz Chroboczek
| With all due respect, Kent, you overuse this argument.  Pushing it to
| its logical extreme, we have no use for a carefully drafted Common
| Lisp definition, because the market will choose right anyhow.

  Since the argument _only_ makes sense within the framework of a standard,
  I fail to see how "logical" it could possibly be to drop the context and
  still believe you have the same argument.  

| Alas, the said definition does not include guarantees about tail-call
| elimination are not included in that definition; I would like to see such
| guarantees included in a future version.

  You will not see such guarantees in any future version.  If you want
  this, there is Scheme.  So much of the rest of what you argue for are
  signs of a Scheme freak that I also fail to see why you do not just work
  happily within the Scheme community instead of unhappily in the Common
  Lisp community?  What does it do for you to whine about tail-calls and
  never get it?

| (Another thing I would like to see are stronger guarantees about what
| conditions are signalled in exceptional situations; again, I do not have
| problems with my vendor here, as I can always test my implementation's
| behaviour, but with the standard itself.)

  What do these stronger guarantees actually entail?  Is it the ability to
  sue vendors if they decide to claim to purport to conform to a voluntary
  standard?  If so, nobody will volunteer to conform to it.  We already
  have vendors who think it is OK to claim to purport to something they
  also display a strong disdain for when push comes to shove, and which
  they undermine when they see an opportunity to do so, and the only thing
  we can do with them is expose their destructiveness and applaud their
  constructiveness, but the whole attitude towards the standard is that it
  does not matter if you violate parts of it in ways that you cannot choose
  to ignore.  In my view, there should have been viable ways to solve these
  "incompatibility" problems so they could co-exist with standard ways, or
  even be _within_ the standard, but this is indeed the path less travelled.

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvadz13j7z.fsf@famine.OCF.Berkeley.EDU>
Erik Naggum <····@naggum.net> writes:

> * Juliusz Chroboczek

> | Alas, the said definition does not include guarantees about tail-call
> | elimination are not included in that definition; I would like to see such
> | guarantees included in a future version.
> 
>   You will not see such guarantees in any future version.  If you want
>   this, there is Scheme.  So much of the rest of what you argue for are
>   signs of a Scheme freak that I also fail to see why you do not just work
>   happily within the Scheme community instead of unhappily in the Common
>   Lisp community?  What does it do for you to whine about tail-calls and
>   never get it?

Oh, come on.  Imagine it's pre-ANSI CL and he's asking for an object
system to be included in the standard.  Would you accuse him of being
a SmallTalk freak who should go is-a and refactor his way back to
where he belongs?  CL is a multi-paradigm language, and tail-call
elimination is useful for more than just CPS and "I refuse to
iterate"-style.  It would be very nice to have some *portable* way of
ensuring that (1) certain important tail-calls are eliminated; or (2)
an error is raised.  This is obviously something the vendors thought
their user base wanted: CMUCL, CLISP, ECLS, Allegro, LispWorks, and
MCL all have some form of tail-call elimination.  This sounds to me
like a good candidate for standardization.

> | (Another thing I would like to see are stronger guarantees about what
> | conditions are signalled in exceptional situations; again, I do not have
> | problems with my vendor here, as I can always test my implementation's
> | behaviour, but with the standard itself.)
> 
>   What do these stronger guarantees actually entail?  Is it the ability to
>   sue vendors if they decide to claim to purport to conform to a voluntary
>   standard?  If so, nobody will volunteer to conform to it.

Uhm, I'm pretty sure he meant changing the wording to say that certain
conditions *will* be signalled in certain situations, instead of
*may*.  I see no reason why this would be an unreasonable request for
a future version of the standard.  Perhaps it wouldn't make it in, but
it sounds like a normal user request for portability.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwelodvjre.fsf@world.std.com>
···@famine.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> I'm pretty sure he meant changing the wording to say that certain
> conditions *will* be signalled in certain situations, instead of
> *may*.  I see no reason why this would be an unreasonable request for
> a future version of the standard.

Depends on a case by case basis.  Every requirement to signal is a
requirement to check.  Every requirement to check is time lost not
doing the application.  In an application where data is already
correct, such time is wasted.  It is between the application writer
and the vendor how much pre-testing has been done and how much is
needed at runtime.

IMO, one of the coolest and strangest features of CL, is the manner
in which its declarations are treated.  They are "promises to have done
right".  They can sometimes be checked (as CMU CL has shown) but they
sometimes cannot be.  In most languages with declarations, declarations
that can't be checked are failures rather than treating the programmer as
an "oracle" who can tell the compiler things it might not be able to infer.
Taking away this ability in favor of requirements to assure these truth
has the potential effect of slowing the language down in places where you
could have told the program a truth that proving would either be hard to
do at compile time or expensive to do at runtime.

> Perhaps it wouldn't make it in, but
> it sounds like a normal user request for portability.

And it sounded like a normal reaction of another member of the
community to one person's seemingly trivial suggestion.  Steele came
to the first meeting of the CL committee post-CLTL (it was in 1986 in
Monterrey, I believe) with a page full of "trivial" corrections he
thought sure everyone would just rubber stamp.  He was amazed to find
that, in constrast to the willingness of a community pre-publication
who had no investment in their implementation or user-code to accept
new ideas, it was suddenly much harder to get change.  New people had
arrived on the scene with different points of view.  People had
started to invest in a particular point of view. etc.  In the end, we
accepted none of his trivial changes and in fact realized from that
meeting that something formalized like ANSI would be needed (we
weren't sure what the mechanism was, but we knew "friendly consensus"
would no longer cut it) to move forward from there, since there were
irresolvable divides afoot...
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211612557359956@naggum.net>
* Thomas F. Burdick
| Oh, come on.  Imagine it's pre-ANSI CL and he's asking for an object
| system to be included in the standard.

  Oh, come on.  We are simply not in that position, anymore.

| It would be very nice to have some *portable* way of ensuring that (1)
| certain important tail-calls are eliminated; or (2) an error is raised.

  I keep rewriting it "tail-call merging" because I think _elimination_ is
  just plain wrong terminology.  Is that just me?

  What you seem to want is a portable way to query a function at the source
  level for the ability of all compilers to unwind its call stack to the
  point where it can redirect a particular function call to return to its
  caller.  What I keep arguing is that this requires a large set of other
  requirements that are simply not in the Common Lisp spirit.  The various
  conditions under which you can actually get tail-call merging in today's
  implementations of Common Lisp are what they are because of the freedom
  of these implementations to do their work in various smart ways.  If you
  _require_ a specific set of tail-call merging conditions, you will also
  mandate a particular set of implementation techniques for function calls
  of _all_ kinds, and you may actually find that a particular vendor is
  unable to comply with the new standard because function calls in Common
  Lisp are incredibly hairy things at the machine level, especially when
  you take CLOS into consideration.  [I have had the good fortune to work
  with Duane Rettig's code in Allegro CL, and I am quite simply in _awe_ of
  the attention to detail and efficiency and the cleverness of both the
  code transformations and the resulting machine code on such a weird and
  varied bunch of hardware archictures.  My own training and set of skills
  predisposes me towards strong hesitation towards the kinds of claims that
  Scheme makes, and I have studied Scheme implementations sufficiently to
  understand how this "properly tail-recursive" requirement has serious
  repercussions for the design and implementation of the whole language.]

| This is obviously something the vendors thought their user base wanted:
| CMUCL, CLISP, ECLS, Allegro, LispWorks, and MCL all have some form of
| tail-call elimination.  This sounds to me like a good candidate for
| standardization.

  It may indeed sound like that until you look beneath the hood and see how
  _differently_ they need to implement this seemingly simple concept
  because of their other design choices in implementing the function call.

  Contrary to popular belief in certain circles, it is not _sufficient_ to
  specify something in a standard to actually get it.  My own struggles
  with trusting people who claim publicly to be in favor of the standard
  while they sneer at it and ridicule it in private communication cause me
  to believe that if you require something that people are only politically
  motivated to back, they will find ways to implement it that are not worth
  much to you.  It is somewhat like implementing a relational database with
  SQL "support" that does not _actually_ support transactions and rollback,
  like MySQL, because of "efficiency" reasons.  This is fine as long as I
  do not need it, but the day I do, I may have to invest serious effort in
  recoding my application and move to a real relational database.  Now, why
  do people not implement something that is so fundamental to the task at
  hand to begin with?  Misguided ideas of their ability to judge the
  appropriateness of standard requirements is most often to blame, but
  irrational personal hangups can be just as powerful -- the upper-case
  nature of Common Lisp symbols is one of them, where one vendor argues
  strongly for a "modern" mode in lower-case, but does _not_ default the
  case-insensitive argument to their apropos to true so their users can at
  least give lower-case strings to apropos, and neither do they offer a way
  to use modern and standard mode side-by-side, which would have made it so
  much easier to experiment with modern mode.  This kind of "anti-standard
  attitude problem" is not at all specific to our community -- I have
  worked with standards of many kinds for 15 years and every single one of
  the areas were I have experience, there has been rogue vendors who think
  they know better or (more often than not) who simply fail to care enough
  to understand what they are doing.  Microsoft is the archetypical enemy
  of specifications because they have this holy mission of supporting what
  they claim are "customer needs", one such need being trust in conformance
  obviously ignored.

| > | (Another thing I would like to see are stronger guarantees about what
| > | conditions are signalled in exceptional situations; again, I do not have
| > | problems with my vendor here, as I can always test my implementation's
| > | behaviour, but with the standard itself.)
| > 
| >   What do these stronger guarantees actually entail?  Is it the ability to
| >   sue vendors if they decide to claim to purport to conform to a voluntary
| >   standard?  If so, nobody will volunteer to conform to it.
| 
| Uhm, I'm pretty sure he meant changing the wording to say that certain
| conditions *will* be signalled in certain situations, instead of
| *may*.  I see no reason why this would be an unreasonable request for
| a future version of the standard.  Perhaps it wouldn't make it in, but
| it sounds like a normal user request for portability.

  Sigh.  If you write something down in a law or a specification, you need
  a way to enforce it.  How do you envision that you would enforce the
  "guarantees" that you have gotten rolled into in the standard if you do?
  The unreasonability of the whole desire to see tail-call merging required
  is that the consequences of such a requirement leads to enforceability
  issues that I for one do not welcome at all.  It is just like bad law --
  it has the _sole_ effect of making people ignore and break less bad laws.

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Paolo Amoroso
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4jDDO4+sWd7EGsbKaJrm6tFaAqq4@4ax.com>
On Tue, 09 Oct 2001 10:36:02 GMT, Erik Naggum <····@naggum.net> wrote:

>   I keep rewriting it "tail-call merging" because I think _elimination_ is
>   just plain wrong terminology.  Is that just me?

I had actually never thought much about it. But now that you point this
out, "merging" seems more appropriate than "elimination".


>   It may indeed sound like that until you look beneath the hood and see how
>   _differently_ they need to implement this seemingly simple concept
>   because of their other design choices in implementing the function call.

Maybe one of the reasons why the implementation of tail-call merging looks
simple comes from the influence of textbook examples. A non-merging Scheme
interpreter/compiler is often illustrated, and then changing a bunch of
lines of code turns it into a merging one. For the toy examples discussed
in books this is indeed simple.


Paolo
-- 
EncyCMUCLopedia * Extensive collection of CMU Common Lisp documentation
http://web.mclink.it/amoroso/ency/README
[http://cvs2.cons.org:8000/cmucl/doc/EncyCMUCLopedia/]
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bc3f0e5.394414087@news.callatg.com>
On Tue, 09 Oct 2001 21:17:46 +0100, Paolo Amoroso <·······@mclink.it> wrote:

>On Tue, 09 Oct 2001 10:36:02 GMT, Erik Naggum <····@naggum.net> wrote:
>
>>   It may indeed sound like that until you look beneath the hood and see how
>>   _differently_ they need to implement this seemingly simple concept
>>   because of their other design choices in implementing the function call.
>
>Maybe one of the reasons why the implementation of tail-call merging looks
>simple comes from the influence of textbook examples. A non-merging Scheme
>interpreter/compiler is often illustrated, and then changing a bunch of
>lines of code turns it into a merging one. For the toy examples discussed
>in books this is indeed simple.

These are very good points. I can't tell you how many times I have read that
tail call elimination is trivial and any decent implementation will do it all
the time--only to struggle with it and tear my hair out trying to implement it.

Problems I have run into, most recently with Corman Lisp. (I had earlier
implemented a version in PowerLisp that occasionally generated the wrong code,
and ended up taking it out).

In Corman Lisp, I made design decisions to use C-like calling conventions. This
has some advantages over alternatives. As well as some disadvantages. In order
to eliminate all tail calls, the function needs to generate code which will tear
down the current stack frame, push the new arguments on the stack, and branch.
However, once the frame is gone, it is not possible to evaluate the arguments
any more. And if you evaluate them first, then you need to store them somewhere
as you create them, but then you have to shuffle them around when you are done.
And, in Corman Lisp, the garbage collector can happen any time (on another
thread, for instance) and the stack needs to be in a reasonable looking state
when it does. Shuffling of items on the top of the stack is messy, and it might
actually be less efficient than just using a normal call.

The stack is so large, and only rarely do you ever run out of stack space--if
you keep in mind how recursion works and use it appropriately.

I ended up implementing what I think is a half-way version in Corman Lisp.
It optimizes recursive tail calls, in a way that is very efficient, but only in
functions which have a fixed number of arguments. In this case, you don't have
to tear down and rebuild the stack frame, and you just overwrite the existing
arguments, giving the biggest bang for the buck. In addition, it is mostly done
at the source level, using GO to restart the function, making code generation
simple and not error-prone. I added some special compiler macros to overwrite
the arguments, bypassing any inner scope variables that might have the same
name.

This only works to call recursively however. If you try to call another
function, keeping the same frame, it may take a different number of arguments or
require a different sized stack frame. Even if these are the same at compile
time, the called function can always be redefined to take a different number of
parameters or a different sized frame.

I know there are probably some tricks I haven't thought of, but my point here is
that, as Eric said, many design decisions are made, and some compromises are
made, when implementing a compiler. I don't believe the problems of implementing
full tail call merging are sufficient to forsake the function calling mechanism
in this case. I am glad that tail call merging is an implementation decision and
not a mandate of the standard. For heaven's sake, the standard doesn't even
require a garbage collector. If an implementor designs a system that doesn't
provide sufficient resources to use, then it won't get much use.

Interestingly, I have never had one user complain to me about the lack of tail
recursion elimination (I only implemented it a couple months ago).
Without the tail call elimination, a simple function can make >80,000 recursive
calls before hitting the stack limit.

Roger
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87669m5b8y.fsf@pps.jussieu.fr>
I'm reordering some of your (very interesting) points.

RC> [the technique I use in CormanLisp] optimizes recursive tail
RC> calls, in a way that is very efficient, but only in functions
RC> which have a fixed number of arguments.

You mean, calls to self only?  Unfortunately, that's the case we
seldom need, as most such cases can be trivially replaced with an
explicit iteration construct.

The interesting case is that of indirect recursion.

RC> In order to eliminate all tail calls, the function needs to
RC> generate code which will tear down the current stack frame, push
RC> the new arguments on the stack, and branch.  However, once the
RC> frame is gone, it is not possible to evaluate the arguments any
RC> more. And if you evaluate them first, then you need to store them
RC> somewhere as you create them, but then you have to shuffle them
RC> around when you are done.

In general, you need to completely build the new stack frame above the
stack pointer, *then* copy it over the current stack frame, and adjust
the stack pointer.  I don't think this is messy -- although, as you
justly note, it may be slightly less efficient than a normal function
call (one extra copy of the stack frame).

I don't see how there can be any problems w.r.t. the GC with this
approach, although obviously some optimisations of this scheme may not
be safe.

Regards,

                                        Juliusz
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bc6887a.564291097@news.callatg.com>
On 11 Oct 2001 19:37:17 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:
>
>RC> [the technique I use in CormanLisp] optimizes recursive tail
>RC> calls, in a way that is very efficient, but only in functions
>RC> which have a fixed number of arguments.
>
>You mean, calls to self only?  Unfortunately, that's the case we
>seldom need, as most such cases can be trivially replaced with an
>explicit iteration construct.
>
>The interesting case is that of indirect recursion.

Granted.

>
>RC> In order to eliminate all tail calls, the function needs to
>RC> generate code which will tear down the current stack frame, push
>RC> the new arguments on the stack, and branch.  However, once the
>RC> frame is gone, it is not possible to evaluate the arguments any
>RC> more. And if you evaluate them first, then you need to store them
>RC> somewhere as you create them, but then you have to shuffle them
>RC> around when you are done.
>
>In general, you need to completely build the new stack frame above the
>stack pointer, *then* copy it over the current stack frame, and adjust
>the stack pointer.  I don't think this is messy -- although, as you
>justly note, it may be slightly less efficient than a normal function
>call (one extra copy of the stack frame).

My problem is that I don't want to give up any efficiency for the 99% of
function calls that don't need tail merging to deal with the few that do. Of
course some appropriate declarations could help here.

>
>I don't see how there can be any problems w.r.t. the GC with this
>approach, although obviously some optimisations of this scheme may not
>be safe.

In Corman Lisp, all lisp data is tagged. Unfortunately, code addresses are not
tagged, and Intel instructions (unlike most RISC processors) can be at any byte
address. Lisp code resides in the heap, and one of the trickier aspects of the
collector is keeping track of which items on the stack are return addresses as
opposed to tagged heap block addresses. The collector therefore relies on a very
strictly maintained chain of stack frames, an invariant which can only be broken
for the couple of instructions that are required to implement a RET or CALL.
The collector watches for those cases as it scans the stack.
Yes, it would be possible to orchestrate, but it seems ugly to me. This code is
already complex, and I would hate to make it more so. Foreign code does not
maintain this invariant, so the compiler and collector both have to do a bunch
of stuff to deal with that. But of course that is just the implementor's point
of view. I understand that if you want to implement an algorithm with indirect
recursion and thousands of recursive calls then you need this feature.

Roger
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <871yk8nc52.fsf@pps.jussieu.fr>
>> In general, you need to completely build the new stack frame above the
>> stack pointer, *then* copy it over the current stack frame, and adjust
>> the stack pointer.  I don't think this is messy -- although, as you
>> justly note, it may be slightly less efficient than a normal function
>> call (one extra copy of the stack frame).

RC> My problem is that I don't want to give up any efficiency for the
RC> 99% of function calls that don't need tail merging to deal with
RC> the few that do.

Fair enough.

Still, it's not absolutely clear a priori that this is a measurable
loss, especially on modern architectures (fast CPU, slow memory
system, first level cache small with respect to working set).  You pay
for an extra copy, but you reduce memory pressure by using a smaller
stack.  So it may very well turn out to be negligible or even a modest
win in general.

Optimising this extra copy away should in many cases be no more difficult
than optimising PSETQ or DO (not something you can do in general, though).

Point taken about implementation difficulty in presence of an asyn-
chronous garbage collector.

                                        Juliusz
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110130835.6ccb7894@posting.google.com>
Juliusz Chroboczek <···@pps.jussieu.fr> wrote in message news:<··············@pps.jussieu.fr>...
> Still, it's not absolutely clear a priori that this is a measurable
> loss, especially on modern architectures (fast CPU, slow memory
> system, first level cache small with respect to working set).  You pay
> for an extra copy, but you reduce memory pressure by using a smaller
> stack.  So it may very well turn out to be negligible or even a modest
> win in general.

I designed and verified modern architectures for a while.  While a
measurement wouldn't hurt, I'd be surprised if the copy had a valuable
effect on memory pressure.  I'd be very surprised if that effect
was observed across a wide range of systems.  (Remember, there's no
"optimization" that is good in all circumstances, and subtle memory
hacks are especially inconsistent.)

Even without tail call optimization, the locations on both sides of
the stack pointer tend to be very frequently accessed; the "new"
side was just used by recent non-tail calls.  (This observation
motivated register windows.)  Therefore, they're almost always in L1.

The exceptions are dumb programs and using a stack to traverse a
large data structure; the latter is expensive whether it's a data
stack or a program stack.  (In other words, the push/pop data scheme
doesn't buy anything.)  In that case, you really want to use an
interative implementation.

However, even in that case, if the data structure is represented with
lots of memory, you might not notice the cache misses due to the stack.
(It depends on how much data you touch for each stack alloc and the
locality of the data access - stack locality is always good.)

Unless you're doing something dumb, I think that the real value (as
far as the system is concerned) of tail-call merging is in name/address
space conservation/reuse.  It affects vm/swapping and "did the stack
run into something else" allocation issues.

Yes, tail call merging is important if you insist on using a recursive
definition of factorial (because the data structure is small).  However,
once you've transformed it so that tail-call merging helps, the iterative
transform is obvious.

The interesting question is whether that aspect (given a tail-call version,
iteration is obvious) of factorial is typical or exceptional.

Of course, if you're working with a language that has a weak/awkward set
of iteration constructs....  In that case, the common advice to a person
who complains that his head hurts after he hits it applies.

-andy
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-DA1108.12203014102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> Yes, tail call merging is important if you insist on using a recursive
> definition of factorial (because the data structure is small).  However,
> once you've transformed it so that tail-call merging helps, the iterative
> transform is obvious.

Right.  And obvious things should be done by the compiler, not the 
programer.


> The interesting question is whether that aspect (given a tail-call 
> version, iteration is obvious) of factorial is typical or exceptional.

It's more than typical -- it's always true.


> Of course, if you're working with a language that has a weak/awkward
> set of iteration constructs....  In that case, the common advice to
> a person who complains that his head hurts after he hits it applies.

*Every* language's set of iteration constructs is weak compared to a 
collection of mutually tail-recursive functions.  The Steele "Lambda the 
ultimate XXX" papers from the 70's are pretty definitlve on that -- if 
you have tail-call merging then calling a function in tail position is 
exactly equivilent to a GOTO with arguments.

Take Dylan, for example.  It's got some pretty nice iteration 
constructs, including a "for" loop that is quite similar in power and 
features to CL's loop macro.  They are all actually implemented (in the 
Gwydion Dylan compiler, at least) by macros that expand to 
tail-recursive functions.  The compiler does most of its work with loops 
in tail-recursive form (optimization, including type inferencing and so 
forth) and then turns it into a loop (with extra labels and GOTOs) 
during code generation.

You can see the iteration macros used by Gwydion Dylan at:

  
<http://berlin.ccc.de/cgi-bin/cvsweb/gd/src/d2c/runtime/dylan/macros.dyla
n?rev=1.16>

In particular the code generated by the "for" control structure is 
formed from this template:

  ??init0 ...
  local method repeat (??var1, ...)
    block (return)
      unless (??stop1 | ...)
        let (??var2, ...) = values(??next2, ...);
        let (??var3, ...) = values(??next3, ...);
        unless (??stop2 | ...)
          ?main;
          mv-call(return, repeat(??next1, ...));
        end;
      end;
      ?value;
    end;
  end;
  repeat(??init1, ...)

-- Bruce
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110141617.519d8a1f@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> In article <····························@posting.google.com>, 
> ······@earthlink.net (Andy Freeman) wrote:
> 
> > Yes, tail call merging is important if you insist on using a recursive
> > definition of factorial (because the data structure is small).  However,
> > once you've transformed it so that tail-call merging helps, the iterative
> > transform is obvious.
> 
> Right.  And obvious things should be done by the compiler, not the 
> programer.

Only when there's agreement between the compiler's defn of obvious+correct
and mine, and the transformation doesn't lose anything that I value.

I used to think that tail call stuff was obvious.  Then I noticed that the
scheme folks found otherwise.  Recent discussion in comp.lang.lisp suggests
that the world hasn't changed much, and we still haven't addressed the lost
information.  (People who disagree are invited to take one of the open
source CLs and add tail-call merging.  I've found that when I disagree
about the difficulty of doing something with the person actually doing
it, the only way to find out who is wrong is to do the work myself.)

> > Of course, if you're working with a language that has a weak/awkward
> > set of iteration constructs....  In that case, the common advice to
> > a person who complains that his head hurts after he hits it applies.
> 
> *Every* language's set of iteration constructs is weak compared to a 
> collection of mutually tail-recursive functions.

There's a difference between theoretical power and useable power.
I prefer iteration constructs to tail recursion; the computational
equivalence doesn't imply cognitive equivalence.

I'm far more concerned about debugging than I am with most space
efficiency hacks.

-andy
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-1FA465.14184615102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> Bruce Hoult <·····@hoult.org> wrote in message 
> news:<···························@news.paradise.net.nz>...
> > In article <····························@posting.google.com>, 
> > ······@earthlink.net (Andy Freeman) wrote:
> > 
> > > Yes, tail call merging is important if you insist on using a 
> > > recursive definition of factorial (because the data structure
> > > is small).  However, once you've transformed it so that
> > > tail-call merging helps, the iterative transform is obvious.
> > 
> > Right.  And obvious things should be done by the compiler, not the 
> > programer.
> 
> Only when there's agreement between the compiler's defn of 
> obvious+correct and mine, and the transformation doesn't lose
> anything that I value.

I should have thought that goes without saying.


> I used to think that tail call stuff was obvious.  Then I
> noticed that the scheme folks found otherwise.  Recent discussion
> in comp.lang.lisp suggests that the world hasn't changed much,
> and we still haven't addressed the lost information.  (People who
> disagree are invited to take one of the open source CLs and add
> tail-call merging.  I've found that when I disagree about the
> difficulty of doing something with the person actually doing it,
> the only way to find out who is wrong is to do the work myself.)

As it happens, one of my projects is to get the Gwydion "d2c" compiler 
to optimize sets of mutually-recursive local functions, rather than just 
the self-calls it handles now.  Of course that's neither CL nor Scheme, 
but it's got a lot in common with both.


> > > Of course, if you're working with a language that has a weak/awkward
> > > set of iteration constructs....  In that case, the common advice to
> > > a person who complains that his head hurts after he hits it applies.
> > 
> > *Every* language's set of iteration constructs is weak compared to a 
> > collection of mutually tail-recursive functions.
> 
> There's a difference between theoretical power and useable power.
> I prefer iteration constructs to tail recursion; the computational
> equivalence doesn't imply cognitive equivalence.

I often prefer to *write* iterative constructs in the cases in which 
they are powerful enough.  That's what macros are for, and indeed all 
the iterative constructs in d2c are implemented as macros that expand to 
recursion.


> I'm far more concerned about debugging than I am with most space
> efficiency hacks.

In what way does the debugability of tail-recursion differ from the 
debugability of iteration?  Looks exactly the same to me, and if you 
turn off the tail-call optimization in the recursive case then you get 
an *increase* in debugability.

-- Bruce
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110150628.50d939ab@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> In article <····························@posting.google.com>, 
> ······@earthlink.net (Andy Freeman) wrote:
> 
> > Bruce Hoult <·····@hoult.org> wrote in message 
> > news:<···························@news.paradise.net.nz>...
> > > In article <····························@posting.google.com>, 
> > > ······@earthlink.net (Andy Freeman) wrote:


> As it happens, one of my projects is to get the Gwydion "d2c" compiler 
> to optimize sets of mutually-recursive local functions, rather than just 
> the self-calls it handles now.  Of course that's neither CL nor Scheme, 
> but it's got a lot in common with both.

I'm confused.  I just read that optimizing tail calls was "trivial", but
the above suggests that "local functions" (whatever that means) are different.

> > I'm far more concerned about debugging than I am with most space
> > efficiency hacks.
> 
> In what way does the debugability of tail-recursion differ from the 
> debugability of iteration?  Looks exactly the same to me, and if you 
> turn off the tail-call optimization in the recursive case then you get 
> an *increase* in debugability.

I have iterative debugging "tools" and function call debugging "tools".
Treating certain function as "special" has a cost.

Look - I'd be very happy if tail calls were handled specially.  My
point is that "it's a no brainer, there aren't any costs" arguments
are wrong.  Moreover, that style of argument has had a horrendous cost.
As I wrote before, there's a reason why good languages are marginal
and the world runs C++ and Java.

-andy
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-88008C.12115016102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> Bruce Hoult <·····@hoult.org> wrote in message 
> news:<···························@news.paradise.net.nz>...
> > In article <····························@posting.google.com>, 
> > ······@earthlink.net (Andy Freeman) wrote:
> > 
> > > Bruce Hoult <·····@hoult.org> wrote in message 
> > > news:<···························@news.paradise.net.nz>...
> > > > In article <····························@posting.google.com>, 
> > > > ······@earthlink.net (Andy Freeman) wrote:
> 
> 
> > As it happens, one of my projects is to get the Gwydion "d2c" compiler 
> > to optimize sets of mutually-recursive local functions, rather than 
> > just 
> > the self-calls it handles now.  Of course that's neither CL nor Scheme, 
> > but it's got a lot in common with both.
> 
> I'm confused.  I just read that optimizing tail calls was "trivial"

Ah, I see.

If I don't implement it then I'm not qualified to talk about it.  On the 
other hand, if I *do* implement it (and mention it) then for some reason 
it obviously wasn't trivial.  So I'm defeated either way.


> the above suggests that "local functions" (whatever that means) are 
> different.

There's an interesting concept called "lexical scoping" which can 
restrict the visibility of bindings.  This is useful in a compiler 
because (among other things) it allows you to prove that a function can 
be called from the places from which it appears to be called, and from 
no other places.

Lexically nested functions also tend to have the property that they are 
not called via generic-function dispatch, which also simplifies things.

The interesting part of what I wrote above was "sets of 
mutually-recursive".  This is not especially difficult to do, but 
*someone* has to actually do it.  The fun part, really, is deciding what 
C code it should be compiled to (since d2c is a compiler that emits C).  
If there is only a single non-tail call into the set of functions then 
the whole mess is best inlined into the caller (which is what usually 
happens with self-recursive functions now).  But if there are multiple 
external calls into the set of functions then it will need to be a 
single seperate C function to represent the set of source code 
functions, with closure bindings passed in and multiple sets of 
different arguments, dispatched internally using goto or switch 
depending on which of the source code functions is being called.

If it was being compiled to assembler then this particular thing would 
be a good bit easier.  Except then you'd need a multitude of different 
implementations since Gwydion runs on a host of different CPU 
architectures and ABIs.

-- Bruce
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110160656.68407b69@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> In article <····························@posting.google.com>, 
> ······@earthlink.net (Andy Freeman) wrote:
> > 
> > I'm confused.  I just read that optimizing tail calls was "trivial"
> 
> Ah, I see.
> 
> If I don't implement it then I'm not qualified to talk about it.  On the 
> other hand, if I *do* implement it (and mention it) then for some reason 
> it obviously wasn't trivial.  So I'm defeated either way.

The only thing "defeated" is the claim that optimizing tail calls is
trivial.  We now have some evidence that for at least one implementation,
there's a difference in implementing tail call merging between local
functions and non-local functions.  Since we were told before that
it could be implemented by a bit of code that copied some stack locations
and moved a pointer, a bit of code used whenever a routine made a tail
call, ....

> > the above suggests that "local functions" (whatever that means) are 
> > different.
> 
> There's an interesting concept called "lexical scoping" which can 
> restrict the visibility of bindings.  This is useful in a compiler 
> because (among other things) it allows you to prove that a function can 
> be called from the places from which it appears to be called, and from 
> no other places.

Wowsers, the things Ilearn in comp.lang.lisp.  Maybe next someone will
tell me about parsers.

That's all well and good, but it's only relevant if tail call merging
has some dependence on characteristics of the callee, and isn't simply
a caller property.  It could be that the implementation wants to use
a different entry point when something is tail-called, or the
implementation ever uses C's subroutines to implement the source language's
subroutines.  (I resisted that temptation the last time I used C
as a target language; the end result was a huge technical win but
I got to explain why there wasn't a "natural" correspondence
between the generated C subroutines and the source language subroutines.)

Either way, it's more evidence that tail-call merging isn't a trivial
hack, as was claimed by people not doing the work.

-andy
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-E95C11.10344117102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> Bruce Hoult <·····@hoult.org> wrote in message 
> news:<···························@news.paradise.net.nz>...
> > In article <····························@posting.google.com>, 
> > ······@earthlink.net (Andy Freeman) wrote:
> > > 
> > > I'm confused.  I just read that optimizing tail calls was "trivial"
> > 
> > Ah, I see.
> > 
> > If I don't implement it then I'm not qualified to talk about it.  On 
> > the 
> > other hand, if I *do* implement it (and mention it) then for some 
> > reason 
> > it obviously wasn't trivial.  So I'm defeated either way.
> 
> The only thing "defeated" is the claim that optimizing tail calls is
> trivial.  We now have some evidence that for at least one implementation,
> there's a difference in implementing tail call merging between local
> functions and non-local functions.

Since C compilers don't themselves currently do tail call optimization, 
a compiler targetting C (as d2c does) can't use C functions to implement 
source language functions if you want tail call optimization.  d2c 
normally *does* use C functions to implement Dylan functions.  QED.

Doesn't imply that the general case is hard though.

-- Bruce
From: Raymond Toy
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4nr8s3i74k.fsf@rtp.ericsson.se>
>>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:

    Bruce> Since C compilers don't themselves currently do tail call optimization, 

I thought gcc could do tail-call optimization in certain situations?

Ray
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-A245FE.02293418102001@news.paradise.net.nz>
In article <··············@rtp.ericsson.se>, Raymond Toy 
<···@rtp.ericsson.se> wrote:

> >>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:
> 
>     Bruce> Since C compilers don't themselves currently do tail call 
>     optimization, 
> 
> I thought gcc could do tail-call optimization in certain situations?

d2c generates portable C, not whatever gcc happens to accept.

Life would be simpler if that wasn't the case, but that's the current 
concensus.

-- Bruce
From: Raymond Toy
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4nwv1ugt0s.fsf@rtp.ericsson.se>
>>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:

    Bruce> In article <··············@rtp.ericsson.se>, Raymond Toy 
    Bruce> <···@rtp.ericsson.se> wrote:

    >> >>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:
    >> 
    Bruce> Since C compilers don't themselves currently do tail call 
    >> optimization, 
    >> 
    >> I thought gcc could do tail-call optimization in certain situations?

    Bruce> d2c generates portable C, not whatever gcc happens to accept.

If gcc doesn't accept portable C, what does it accept?

I'm not claiming gcc will tail-call optimize your generated C code.
But you said "C compilers don't...do tail call optimization".  I know
gcc (and Solaris cc as well) can do some tail-call optimization.  I
checked.

Ray
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-0FAAD3.17334018102001@news.paradise.net.nz>
In article <··············@rtp.ericsson.se>, Raymond Toy 
<···@rtp.ericsson.se> wrote:

> >>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:
> 
>     Bruce> In article <··············@rtp.ericsson.se>, Raymond Toy 
>     Bruce> <···@rtp.ericsson.se> wrote:
> 
>     >> >>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:
>     >> 
>     Bruce> Since C compilers don't themselves currently do tail call 
>     >> optimization, 
>     >> 
>     >> I thought gcc could do tail-call optimization in certain 
>     >> situations?
> 
>     Bruce> d2c generates portable C, not whatever gcc happens to accept.
> 
> If gcc doesn't accept portable C, what does it accept?

Lots of things that aren't portable C and which we therefore can't use 
even though they might be quite useful.


> I'm not claiming gcc will tail-call optimize your generated C code.
> But you said "C compilers don't...do tail call optimization".  I know
> gcc (and Solaris cc as well) can do some tail-call optimization.  I
> checked.

I didn't mean that no C compiler does tail call optimization, but that 
you can't rely on it.  In fact, if gcc does it in some casess (and I 
know that it makes some such claim), they do not appear to be any of the 
cases which we generate.

If there was an abundance of people working on d2c then it might be 
worth chasing it up as a gcc special case, but we don't have such an 
abundance and we need things to work on compilers such as CodeWarrior 
and VC++ which are not gcc.

-- Bruce
From: ········@acm.org
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3Ohz7.4529$W61.497703@news20.bellglobal.com>
Bruce Hoult <·····@hoult.org> writes:
> In article <··············@rtp.ericsson.se>, Raymond Toy 
> <···@rtp.ericsson.se> wrote:
> 
> > >>>>> "Bruce" == Bruce Hoult <·····@hoult.org> writes:
> > 
> >     Bruce> Since C compilers don't themselves currently do tail call 
> >     optimization, 
> > 
> > I thought gcc could do tail-call optimization in certain situations?

> d2c generates portable C, not whatever gcc happens to accept.

> Life would be simpler if that wasn't the case, but that's the
> current concensus.

It's still not fair to generalize that "C compilers do not do tail
call optimization;" that may be true for some instances, but not for
others.

The situation is actually much as with CL: _some_ compilers may do
_some_ tail call optimization, whilst others may not.
-- 
(reverse (concatenate 'string ··········@" "enworbbc"))
http://www.cbbrowne.com/info/nonrdbms.html
Entia non sunt multiplicanda sine necessitate.
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110170824.6647e891@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> Since C compilers don't themselves currently do tail call optimization, 
> a compiler targetting C (as d2c does) can't use C functions to implement 
> source language functions if you want tail call optimization.  d2c 
> normally *does* use C functions to implement Dylan functions.  QED.

I'm somewhat surprised that a serious implementation of a scheme-like
language targetting C would use C functions that way.  I'd have
expected something with explicit continuation passing, as I did.

In my application, the CPS transformation made certain analysis
unnecessary and let me handle the complete source language.  If
I'd used C functions in the "natural way", I'd have had to do a
lot of analysis and still wouldn't have handled everything.

I found that C compilers are really good at straight-forward
CPS-style C, so the suggested (by colleagues) inefficiency
wasn't an issue.  In my case, the CPS-style C was occasionally
faster, and never slower, than the "natural" C.

> Doesn't imply that the general case is hard though.

The claim was that it was trivial to add tail-call merging to
an arbitrary implementation.  We've now seen that the analysis to
determine whether it's appropriate is non-trivial.  d2c demonstrates
that there are reasonable implementation choices that one might make
which make tail-call merging "difficult".  (Corman made the same
claim, but we don't seem to believe him for some reason, perhaps
because he didn't know enough to do the right thing.)

I'd guess that there is some chance that a better C implementation
would help here, but it would be hard for me to know for sure without
doing the work.

-andy
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvofn63r9e.fsf@famine.OCF.Berkeley.EDU>
······@earthlink.net (Andy Freeman) writes:

> Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> > Since C compilers don't themselves currently do tail call optimization, 
> > a compiler targetting C (as d2c does) can't use C functions to implement 
> > source language functions if you want tail call optimization.  d2c 
> > normally *does* use C functions to implement Dylan functions.  QED.
> 
> I'm somewhat surprised that a serious implementation of a scheme-like
> language targetting C would use C functions that way.  I'd have
> expected something with explicit continuation passing, as I did.
> 
> In my application, the CPS transformation made certain analysis
> unnecessary and let me handle the complete source language.  If
> I'd used C functions in the "natural way", I'd have had to do a
> lot of analysis and still wouldn't have handled everything.
> 
> I found that C compilers are really good at straight-forward
> CPS-style C, so the suggested (by colleagues) inefficiency
> wasn't an issue.  In my case, the CPS-style C was occasionally
> faster, and never slower, than the "natural" C.

Could you elaborate on this a bit?  I'm curious, because I'm currently
working on a lisp-to-c++ system (called CL-80, for "the easy 80% of
CL").  I'm feeling very much torn in implementation decisions, because
the major purpose of the system is to integrate extremely tightly with
the C++ code (including its kind-of-weird multiprocessing system,
semi-automatic memory management system, and weird custom object
system), so every time I run into a performance-vs.-tight-integration
decision, I go with integration (otherwise I'd just use an existing
system).  But it is piquing (or re-piquing) my interest in
lisp(like)-to-c(like) language compilation techniques.

> I'd guess that there is some chance that a better C implementation
> would help here, but it would be hard for me to know for sure without
> doing the work.

I have the luxury of knowing what compiler my code will be compiled
with (g++ 2.95), but unfortunately its documentation is pretty lousy.
So when deciding if it would be more painful to do something myself,
or to ensure that the c++ compiler can do it, I also have to factor in
the experimentation needed to figure out *what* g++ can do, and
precisely when.  Which means that, at the moment, I'm producing pretty
lousy code, both at the c++ and assembly levels :-( -- I can fix it
later, if it's not too much work, but it would be nice if my c++
compiler made it easier for me.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-B5DA12.17254418102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> Bruce Hoult <·····@hoult.org> wrote in message 
> news:<···························@news.paradise.net.nz>...
> > Since C compilers don't themselves currently do tail call optimization, 
> > a compiler targetting C (as d2c does) can't use C functions to 
> > implement 
> > source language functions if you want tail call optimization.  d2c 
> > normally *does* use C functions to implement Dylan functions.  QED.
> 
> I'm somewhat surprised that a serious implementation of a scheme-like
> language targetting C would use C functions that way.  I'd have
> expected something with explicit continuation passing, as I did.

You'd have to take that up with Scott Fahlman and his colleagues since 
the decision was made long before the project left CMU.


> I found that C compilers are really good at straight-forward
> CPS-style C, so the suggested (by colleagues) inefficiency
> wasn't an issue.  In my case, the CPS-style C was occasionally
> faster, and never slower, than the "natural" C.

How and when do you unwind the stack?


> > Doesn't imply that the general case is hard though.
> 
> The claim was that it was trivial to add tail-call merging to
> an arbitrary implementation.  We've now seen that the analysis to
> determine whether it's appropriate is non-trivial.  d2c demonstrates
> that there are reasonable implementation choices that one might make
> which make tail-call merging "difficult".  (Corman made the same
> claim, but we don't seem to believe him for some reason, perhaps
> because he didn't know enough to do the right thing.)

It depends on what control you have over what stages of the compilation 
process.  Generating C takes away a lot of the options that you have if 
you're generating your own assembly-language.

There are compilers which go through C and achieve tail-call 
optimization by introducing a filter program after the C compiler and 
before the assembler, doing it essentially as a kind of peephole 
optimization.  Which appears to be entirely adequate.

 
> I'd guess that there is some chance that a better C implementation
> would help here, but it would be hard for me to know for sure without
> doing the work.

Targetting C-- instead of C is a likely future direction for the d2c 
compiler.

-- Bruce
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110180715.32cea41a@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> In article <····························@posting.google.com>, 
> ······@earthlink.net (Andy Freeman) wrote:
> > I found that C compilers are really good at straight-forward
> > CPS-style C, so the suggested (by colleagues) inefficiency
> > wasn't an issue.  In my case, the CPS-style C was occasionally
> > faster, and never slower, than the "natural" C.
> 
> How and when do you unwind the stack?

Which stack?

The generated C was not recursive - I could have used Fortran IV
(or whatever fortran has statically allocated activation records).
(Yes - I'm assuming that C libraries for i/o, math, and memory
allocation aren't recursive - I think that it's safe to assume
that they're nicely bounded.)

The control "stack" for the source language was linked continuations,
managed (created, returned, passed around, destroyed) by said generated C.
I did optimize that management for self-tail call situations, but it isn't
clear that that optimization was important.  (It probably made a measurable
difference in performance, but performance was so good that I probably
should have spent that time on other projects.  Unfortunately, that
optimization was one of the first things that I did and it was designed
in so deep that it would have been work to take it out so that I could
do the obvious experiment.  I don't know if it would have been less work
to graft it on afterwards.)

Yes, there was a "top" level routine, not unlike what Corman suggested
for the FSM problem.  This routine accepted returned continuations
from the generated C code and used them to determine what to call next.
("Top" is in quotes because it was actually involved for every continuation
generated.  It was at the top of the implementation's control stack, but
it was used at almost every intermediate level in the application's control
scheme.  Remember, the generated C was not recursive.)  The generated C did
NOT call continuations; it was called with such continuations (by "top"),
and it generated them (and returned them to "top") as necessary.

If you want to think of it in Steele's "goto with arguments terms, all
interesting gotos were implemented by creating and returning a continuation
to the "top", which then called the appropriate C routine.  "Determining
what to do next" was implemented as a call through a function pointer,
much as Corman did in his example.
 
> It depends on what control you have over what stages of the compilation 
> process.  Generating C takes away a lot of the options that you have if 
> you're generating your own assembly-language.

Sorry, but I don't see that.  (I do see how portable C makes it
unlikely that you'll generate specialized instructions, because they're
often supported by the manufacturer's C compiler as special subroutines,
but those subr calls can be generated so that the "right" compiler
will do the right thing.)

When a C construct doesn't do exactly what you want, you can either
change what you want or you can use another construct.  (I didn't
use C's loop constructs at all for my language's loops.)  Managing
continuations "by hand" seems extreme, but freed me from the
limitations of C's calling model.  It took a lot less time to write
that management code (including the optimization) than it would have
to write the analysis code that it made unnecessary.

One might argue that I used C as a glorified generic assembler,
one that does register allocation, instruction generation, and some
interesting name management.  I'm not sure what the point of such
an argument would be.

-andy
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <r8s155vp.fsf@itasoftware.com>
> Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
>  
> > It depends on what control you have over what stages of the compilation 
> > process.  Generating C takes away a lot of the options that you have if 
> > you're generating your own assembly-language.
> 

······@earthlink.net (Andy Freeman) writes:
> Sorry, but I don't see that.  (I do see how portable C makes it
> unlikely that you'll generate specialized instructions, because they're
> often supported by the manufacturer's C compiler as special subroutines,
> but those subr calls can be generated so that the "right" compiler
> will do the right thing.)

What about things like stack frame management and calling conventions?
You have no control over these in C.  You can't get at the carry bit
or the `direction' bit in the x86.

(vis-a-vis the stack frame management:  Since you are using Baker's
trick, it really doesn't matter what C does with the stack frame
because you never really use it.  However, the C code still manages
this useless record.  If you had control at the assembly code level,
you could elide all the useless saving of return addresses, previous
frame and stack pointers, etc.)
  
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110181725.16137e1@posting.google.com>
···@itasoftware.com wrote in message news:<············@itasoftware.com>...
> > Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> > > It depends on what control you have over what stages of the compilation 
> > > process.  Generating C takes away a lot of the options that you have if 
> > > you're generating your own assembly-language.
> > 
> 
> ······@earthlink.net (Andy Freeman) writes:
> > Sorry, but I don't see that.  (I do see how portable C makes it
> > unlikely that you'll generate specialized instructions, because they're
> > often supported by the manufacturer's C compiler as special subroutines,
> > but those subr calls can be generated so that the "right" compiler
> > will do the right thing.)
> 
> What about things like stack frame management and calling conventions?

mu.  One's implementation strategy determines whether those questions
are relevant.  (I can't get excited about the x86 overflow bit - I don't
know why I'd ever care about it outside of library routines, and I'm
perfectly willing to write them in assembler when they're an issue.)

> (vis-a-vis the stack frame management:  Since you are using Baker's
> trick, it really doesn't matter what C does with the stack frame
> because you never really use it.  However, the C code still manages
> this useless record.  If you had control at the assembly code level,
> you could elide all the useless saving of return addresses, previous
> frame and stack pointers, etc.)

I don't know about other archs, but fairly old gcc on sparcs was
reasonably good about killing unused instructions.  The generated code
tended to be leaf procedures, so return addresses weren't an issue
and I didn't see anything else in the assembler output worth worrying
about.

Now, one can argue that I paid extra because I didn't have decent
register allocation.  However, I don't buy it.

With modern (wide and deep) machines, instructions are almost free.
Stalls are the thing to minimize.  My "top" routine's indirect calls
are worth worrying about - C's stack frames aren't.  (The next architecture
advances will reduce the cost of indirect calls; returns stopped
being an issue 5 years ago.)

For lisps and schemes, library functions take a signficant amount of
time so one would expect that overhead to be negligible.  In my case,
there weren't any library functions to hide behind, but the observed
overhead was negligible.  (It was almost unmeasurable.)

BTW - My project was a skunk works project.  The "real project",
had lots of resources, time, and smart people who worried about
such "overhead", but was never finished, and was slower for the things
it did do.

Of course, one can use this approach to generate assembler directly
if you really need the last 0.2% performance.  Few of us are in a
position where doing so makes economic sense.  Almost all of us
are in a position where algorithms and efficient development matter
more.

-andy
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-2A6ACB.13111919102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> Bruce Hoult <·····@hoult.org> wrote in message 
> news:<···························@news.paradise.net.nz>...
> > In article <····························@posting.google.com>, 
> > ······@earthlink.net (Andy Freeman) wrote:
> > > I found that C compilers are really good at straight-forward
> > > CPS-style C, so the suggested (by colleagues) inefficiency
> > > wasn't an issue.  In my case, the CPS-style C was occasionally
> > > faster, and never slower, than the "natural" C.
> > 
> > How and when do you unwind the stack?
> 
> Which stack?

The C stack.


> Yes, there was a "top" level routine, not unlike what Corman suggested
> for the FSM problem.  This routine accepted returned continuations
> from the generated C code and used them to determine what to call next.

Right.  This is the standard Scheme-to-C technique, used by quite a 
number of implementations e.g. Gambit-C.


> ("Top" is in quotes because it was actually involved for every 
> continuation generated.

Well I assume you actually merge the continuations in basic blocks into 
a single C function, rather than returning to the driver for *every* 
expression...


> > It depends on what control you have over what stages of the compilation 
> > process.  Generating C takes away a lot of the options that you have if 
> > you're generating your own assembly-language.
> 
> Sorry, but I don't see that.  (I do see how portable C makes it
> unlikely that you'll generate specialized instructions, because they're
> often supported by the manufacturer's C compiler as special subroutines,
> but those subr calls can be generated so that the "right" compiler
> will do the right thing.)

Well, you're losing lots of things with this scheme.  What could be a 
simple GOTO in a native compiler becomes a multi-valued function return 
-- you have to somehow pass back something identifying the next 
continuation, plus its arguments, and C isn't as good at returning 
multiple values as it is at passing multiple arguments -- and then the 
driver needs to extract the continuation and call it (which will be a 
couple more instructions than a simple GOTO).  Basically the C compiler 
is generating code to give you facilities that you then don't use, and 
you're having to implement your own version as WELL as the C ones.

A compiler such as Chicken that actually uses C function calls normally 
gets all sorts of speed advantages through being able to use direct 
function calls instead of indirect, use the C argument passing mechanism 
to pass multiple return values, and so forth.  The C compiler generates 
a little extra code for function return that is never actually executed 
but that is a small price in space and no price in speed, except in 
secondary effects through cache being wasted (and it's reducable through 
non-standard "noreturn" compiler directives).  The cost is having to 
check for stack overflow at the start of each function and extra code to 
recover from that -- basically the technique that your implementation 
uses, but in Chicken it is seldom used and can be shared or interpreted 
or table-driven with negligable speed penalty.


Mapping source functions directly to C functions as d2c does is not a 
viable technique for Scheme because it doesn't allow call/cc but Dylan 
doesn't have that.  It also certainly makes tail-call optimization 
harder and d2c will probably *never* support tail-call optimization 
between top-level functions.

Interestingly, the commercial "Functional Developer" doesn't either, 
even though it generates machine code directly.  So, in both Gwydion 
"d2c" and Functional Developer this silly code...

   define method countto(i, t)
      if (i = 0) t else countto(i - 1, t + 1) end
   end;

   countto(1000000, 0);

... produces a stack overflow, while this code ...

   define method countto(i)
     local
       method foo(i, t)
         if (i = 0) t else foo(i - 1, t + 1) end
       end;
     foo(i, 0);
   end;

   countto(1000000, 0);

... works fine in both implementations.  The concensus in the Dylan 
community seems to be that tail-call optimization is required, but not 
for top-level functions.

Another example:

  define method classify(n :: <integer)
    local
      method even?(i)
        if (i = 0) "even" else odd?(i - 1) end
      end,
      method odd?(i)
        if (i = 0) "odd" else even?(i - 1) end
      end;
    even?(n);
  end;

  classify(123456789);

This works fine in Functional Developer.  At the moment it doesn't work 
in d2c (stack overflow).  It should.  Dylan programmers have a right to 
*expect* this code to be optimized, and I plan to ensure that d2c 
fulfils that expectation.


Dylan of course isn't CL, and expectations differ in the two 
communities.  As these examples show, Dylan isn't Scheme either.  In 
many respects -- including the question of tail-call optimization -- it 
lies somewhere between the two.

-- Bruce
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110190744.79cb07fa@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> Well I assume you actually merge the continuations in basic blocks into 
> a single C function, rather than returning to the driver for *every* 
> expression...

Right.  I didn't generate continuations for open coded basic blocks or
sequences of library calls.

However, my tests for "negligible" overhead used programs that used
continuations every few simple statements.  Since these statements
were usually implemented with C that became a few sparc instructions,
the overhead got its chance to shine.  On more realistic code,
continuations were far less common.

> Well, you're losing lots of things with this scheme.  What could be a 
> simple GOTO in a native compiler becomes a multi-valued function return 
> -- you have to somehow pass back something identifying the next 
> continuation, plus its arguments, and C isn't as good at returning 
> multiple values as it is at passing multiple arguments -- and then the 
> driver needs to extract the continuation and call it (which will be a 
> couple more instructions than a simple GOTO).

Hmm.  Since almost all of my source language's gotos became C gotos,
"have to" seems a bit strong.

My source language didn't have multiple return values, but if it had,
I'd have used the same scheme for returning values that I used for
passing them.

I found that the dumb thing (source vars stored in structs) was almost
as fast as the "smart thing" (trying to use C args or caching struct
fields in C vars and relying on register allocation).  Why?  Modern
machines are more sensitive to stalls than instruction counts.  Indexed
loads/stores are "good enough" if you're doing much with the values.

>  Basically the C compiler 
> is generating code to give you facilities that you then don't use, and 
> you're having to implement your own version as WELL as the C ones.

Yup.  I found that the "pain" of rolling my own was far less than the
pain of trying to use the "similar, but not close enough" facilities
provided by C.

I think of C's calling mechanism as being like the VAX "call" instruction.
That instruction does a lot of things, and when you need those things,
it's the fast way to get them.  However, when there's a mismatch, ....

> non-standard "noreturn" compiler directives).  The cost is having to 
> check for stack overflow at the start of each function and extra code to 
> recover from that -- basically the technique that your implementation 
> uses, but in Chicken it is seldom used and can be shared or interpreted 
> or table-driven with negligable speed penalty.

I don't understand Chicken, but I don't have any code to check for
"stack overflow".  I might run out of space for continuations, but
that's a different issue.

-andy
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-4C5EB1.13533420102001@news.paradise.net.nz>
In article <····························@posting.google.com>, 
······@earthlink.net (Andy Freeman) wrote:

> > non-standard "noreturn" compiler directives).  The cost is having to 
> > check for stack overflow at the start of each function and extra code 
> > to 
> > recover from that -- basically the technique that your implementation 
> > uses, but in Chicken it is seldom used and can be shared or interpreted 
> > or table-driven with negligable speed penalty.
> 
> I don't understand Chicken, but I don't have any code to check for
> "stack overflow".  I might run out of space for continuations, but
> that's a different issue.

Right.

When you return to the top level for every continuation you don't need a 
check for stack overflow.  Chicken OTOH doesn't return to the top level 
every time.  It uses normal C function calls to call the next 
continuation and then when it runs out of stack space (generally set to 
about the size of the machine's L1 cache seems to be fastest) it uses a 
longjump() to dispose of the entire stack at once and return to a top 
level driver loop like yours.

Thus on a typical machine which passes C function args in registers 
(PowerPC, SPARC, MIPS, Alpha...) the overhead to call a continuation is:

Chicken:

- get the args into the right registers (usually free)
- normal, direct, C function call
- check stack pointer against a global stack limit variable


Trampoline (e.g. yours):

- save the args and continuation address into a struct
- C function return returning the struct (?by reference, or is it a 
global?)
- indirect function call
- pull the args out of the struct


On something like the x86 which has few registers and normally passes 
arguments on the stack yours is probably pretty good.  The various x86 
vendors now throw huge resources at load/store units and accessing L1 
cache nearly as fast as registers.

On a RISC, though, your technique will I think really hurt -- it's going 
to result in most code being serialized on the load/store unit.

The indirect call in yours will hurt bad on all machines, and probably 
worse and worse as time goes on.


Chicken also, incidentally, uses the C stack as the nursery generation 
of the heap.  Heap allocations are done with either calloc() or else as 
C local variables.  When stack overflow occurs and it is about to be 
unwound (using longjump()) a garbage collection is done, transferring 
any live data from the stack to the long-term heap.

-- Bruce
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110220926.49c2f854@posting.google.com>
Bruce Hoult <·····@hoult.org> wrote in message news:<···························@news.paradise.net.nz>...
> - indirect function call

That's the stall on current architectures.

> - pull the args out of the struct

Of course, that can be amortized over all of the usage in the procedure.
However, doing so "by hand" (read into c vars on entry, write back
before exit) isn't necessarily a win.
 
> On a RISC, though, your technique will I think really hurt -- it's going 
> to result in most code being serialized on the load/store unit.

That's a reasonable hypothesis, but we measured elapsed times and it really
wasn't a problem.  (Going in, we "knew" how many sparc instructions were
required for each statement, so we could easily compute the overhead.
When we did, we found that it was negative.  Hmm.  Looking at the
actual assembly code, we found that the C compiler's inter-statement
optimizations saved more than the overhead cost.)  Our case was somewhat
extreme in that our statements were so simple.  Given a more complex
data model ....

This was on an UltraSparcII, I think.  People work really hard these days
to not build memory starved machines.  (If you're building a 4-wide machine,
a 1-wide load/store pipe will kill you, so ....)

> The indirect call in yours will hurt bad on all machines, and probably 
> worse and worse as time goes on.

IA64 has special instructions to make indirect calls fast.  Any
instruction/operation that interacts badly with pipelines is getting
a whole lot of attention now.

-andy
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87vghgcyyt.fsf@pps.jussieu.fr>
AF> The interesting question is whether that aspect (given a tail-call
AF> version, iteration is obvious) of factorial is typical or exceptional.

The question, as I see it, is whether given access to full CL with
arbitrary macro hackery there are any good examples of tail recursion
where this transformation is awkward enough to justify wanting to use
tail call elimination.

Consider, for example, the reduced finite state automaton that
recognises (ab)*c.  A simple, direct way to implement it in CL would
be to write something like:

  (defun foo ()
    (tagbody
      state-1
      (case (read-char)
        ((#\a) (go state-2))
        ((#\c) (return-from 'foo t))
        (t (return-from 'foo nil)))
      state-2
      (case (read-char)
        ((#\b) (go state-1))
        (t (return-from 'foo nil)))
    ))

While this is most definitely clean CL code, it is awkward to
manipulate.  There is no way I can trace the behaviour of a single
state; there is no way I can redefine a single state without modifying
the whole state machine; and there is no simple way I can distribute
the state machine accross multiple source files.

  (defun foo ()
    (state-1))

  (defun state-1 ()
    (case (read-char)
      ((#\a) (state-2))
      ((#\c) t)
      (t nil)))

  (defun state-2 ()
    (case (read-char)
      ((#\b (state-1)))
      (t nil)))

Clear, elegant, and piecewise manipulable.  But crucially reliant on
tail call elimination.

                                        Juliusz
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bcd2e7a.1000063244@news.callatg.com>
On 16 Oct 2001 00:36:42 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:

>AF> The interesting question is whether that aspect (given a tail-call
>AF> version, iteration is obvious) of factorial is typical or exceptional.
>
>The question, as I see it, is whether given access to full CL with
>arbitrary macro hackery there are any good examples of tail recursion
>where this transformation is awkward enough to justify wanting to use
>tail call elimination.
>
>Consider, for example, the reduced finite state automaton that
>recognises (ab)*c.  A simple, direct way to implement it in CL would
>be to write something like:
>
>  (defun foo ()
>    (tagbody
>      state-1
>      (case (read-char)
>        ((#\a) (go state-2))
>        ((#\c) (return-from 'foo t))
>        (t (return-from 'foo nil)))
>      state-2
>      (case (read-char)
>        ((#\b) (go state-1))
>        (t (return-from 'foo nil)))
>    ))
>
>While this is most definitely clean CL code, it is awkward to
>manipulate.  There is no way I can trace the behaviour of a single
>state; there is no way I can redefine a single state without modifying
>the whole state machine; and there is no simple way I can distribute
>the state machine accross multiple source files.
>
>  (defun foo ()
>    (state-1))
>
>  (defun state-1 ()
>    (case (read-char)
>      ((#\a) (state-2))
>      ((#\c) t)
>      (t nil)))
>
>  (defun state-2 ()
>    (case (read-char)
>      ((#\b (state-1)))
>      (t nil)))
>
>Clear, elegant, and piecewise manipulable.  But crucially reliant on
>tail call elimination.

Finally, an example. How about this?

(defun foo ()
    (do ((state #'state-1 (funcall state)))
          ((not (functionp state)) state)))

  (defun state-1 ()
    (case (read-char)
      ((#\a) #'state-2)
      ((#\c) t)
      (t nil)))

  (defun state-2 ()
    (case (read-char)
      ((#\b) #'state-1)
      (t nil)))

It seems pretty much equivalent to me, but does not rely on tail call
elimination. 

Roger
From: Marco Antoniotti
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <y6clmiacq64.fsf@octagon.mrl.nyu.edu>
·····@corman.net (Roger Corman) writes:

> On 16 Oct 2001 00:36:42 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:
> 
	...

> >While this is most definitely clean CL code, it is awkward to
> >manipulate.  There is no way I can trace the behaviour of a single
> >state; there is no way I can redefine a single state without modifying
> >the whole state machine; and there is no simple way I can distribute
> >the state machine accross multiple source files.
> >
> >  (defun foo ()
> >    (state-1))
> >
> >  (defun state-1 ()
> >    (case (read-char)
> >      ((#\a) (state-2))
> >      ((#\c) t)
> >      (t nil)))
> >
> >  (defun state-2 ()
> >    (case (read-char)
> >      ((#\b (state-1)))
> >      (t nil)))
> >
> >Clear, elegant, and piecewise manipulable.  But crucially reliant on
> >tail call elimination.
> 
> Finally, an example. How about this?
> 
> (defun foo ()
>     (do ((state #'state-1 (funcall state)))
>           ((not (functionp state)) state)))
> 
>   (defun state-1 ()
>     (case (read-char)
>       ((#\a) #'state-2)
>       ((#\c) t)
>       (t nil)))
> 
>   (defun state-2 ()
>     (case (read-char)
>       ((#\b) #'state-1)
>       (t nil)))
> 
> It seems pretty much equivalent to me, but does not rely on tail call
> elimination. 

Yes.  But the whole point a CL is that it must aid the programmer.  I
am sorry for you. You are in the compiler business :)

The tail recursive definition is far more readable and concise.

Cheers

-- 
Marco Antoniotti ========================================================
NYU Courant Bioinformatics Group        tel. +1 - 212 - 998 3488
719 Broadway 12th Floor                 fax  +1 - 212 - 995 4122
New York, NY 10003, USA                 http://bioinformatics.cat.nyu.edu
                    "Hello New York! We'll do what we can!"
                           Bill Murray in `Ghostbusters'.
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bcdb5b0.1034677687@news.callatg.com>
On 17 Oct 2001 10:11:15 -0400, Marco Antoniotti <·······@cs.nyu.edu> wrote:

>
>·····@corman.net (Roger Corman) writes:
>
>> On 16 Oct 2001 00:36:42 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:
>> 
>	...
>> >  (defun foo ()
>> >    (state-1))
>> >
>> >  (defun state-1 ()
>> >    (case (read-char)
>> >      ((#\a) (state-2))
>> >      ((#\c) t)
>> >      (t nil)))
>> >
>> >  (defun state-2 ()
>> >    (case (read-char)
>> >      ((#\b (state-1)))
>> >      (t nil)))
>> >
>> >Clear, elegant, and piecewise manipulable.  But crucially reliant on
>> >tail call elimination.
>> 
>> Finally, an example. How about this?
>> 
>> (defun foo ()
>>     (do ((state #'state-1 (funcall state)))
>>           ((not (functionp state)) state)))
>> 
>>   (defun state-1 ()
>>     (case (read-char)
>>       ((#\a) #'state-2)
>>       ((#\c) t)
>>       (t nil)))
>> 
>>   (defun state-2 ()
>>     (case (read-char)
>>       ((#\b) #'state-1)
>>       (t nil)))
>> 
>> It seems pretty much equivalent to me, but does not rely on tail call
>> elimination. 
>
>Yes.  But the whole point a CL is that it must aid the programmer.  I
>am sorry for you. You are in the compiler business :)
>
>The tail recursive definition is far more readable and concise.

The two functions state-1 and state-2 are identical, practically  (other than
state-2 in my example is correct and the original was not). Certainly you cannot
say they are less concise.

Then the only difference is foo(), which contains a simple loop and one which
will be identical for every finite state machine you build--you could easily
encapsulate the loop in a function or a macro.

I won't debate "readable" because that is so subjective, but I think "far more
readable and concise" is an overstatement in any case. The original poster said
there was "no simple way", and in my opinion this is pretty simple.

Back to the recursive definition, this only requires tail call merging if you
support valid terminals which have the number of state transitions greater than
the stack can support. That does not seem that common to me (I have never had
cause to implement such a FSM).

I realize this was just an example, and that mutually recursive functions are
useful and solve real problems, and can exceed the stack depth. In these cases
having to resort to iterative approaches can conceivably be a burden. Just not,
in my opinion, in this case.

Roger
From: Marco Antoniotti
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <y6c669ecdp3.fsf@octagon.mrl.nyu.edu>
·····@corman.net (Roger Corman) writes:

> >Yes.  But the whole point a CL is that it must aid the programmer.  I
> >am sorry for you. You are in the compiler business :)
> >
> >The tail recursive definition is far more readable and concise.
> 
> The two functions state-1 and state-2 are identical, practically  (other than
> state-2 in my example is correct and the original was not). Certainly you cannot
> say they are less concise.

Fair enough.

> Then the only difference is foo(), which contains a simple loop and one which
> will be identical for every finite state machine you build--you could easily
> encapsulate the loop in a function or a macro.
> 
> I won't debate "readable" because that is so subjective, but I think "far more
> readable and concise" is an overstatement in any case. The original poster said
> there was "no simple way", and in my opinion this is pretty simple.
> 
> Back to the recursive definition, this only requires tail call merging if you
> support valid terminals which have the number of state transitions greater than
> the stack can support. That does not seem that common to me (I have never had
> cause to implement such a FSM).
> 
> I realize this was just an example, and that mutually recursive functions are
> useful and solve real problems, and can exceed the stack depth. In these cases
> having to resort to iterative approaches can conceivably be a burden. Just not,
> in my opinion, in this case.

In this specific case no.  However, as soon as you scale up a little
bit (but not too far: you are probably better off with more complex
data structures representing FSMs, should they become big.) the
tail-recursive definitions do win.

Anyway, I have been following this whole thread and I convinced myself
that tail-call merging is not easy,  and that is your problem :) since
there is some demand for it.

Having said so, I will happily continue programming following the
simple rule: first get it right, then get it fast.  If in the process
I have to eliminate tail-calls in favor of looping constructs, too
bad.

Cheers

-- 
Marco Antoniotti ========================================================
NYU Courant Bioinformatics Group        tel. +1 - 212 - 998 3488
719 Broadway 12th Floor                 fax  +1 - 212 - 995 4122
New York, NY 10003, USA                 http://bioinformatics.cat.nyu.edu
                    "Hello New York! We'll do what we can!"
                           Bill Murray in `Ghostbusters'.
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvsnci3s0m.fsf@famine.OCF.Berkeley.EDU>
Marco Antoniotti <·······@cs.nyu.edu> writes:

> Having said so, I will happily continue programming following the
> simple rule: first get it right, then get it fast.  If in the process
> I have to eliminate tail-calls in favor of looping constructs, too
> bad.

While I'd still like to see some sort of standardization of tail-call
merging (and no, I haven't had a chance to start working on
implementing the appropriate reflection in SBCL, yet), I agree with
this for practical purposes.  Using my lisp systems, I can get
tail-call merging, so it's not a big problem.  From my experience with
e-lisp, I'm pretty sure it's not too difficult to write your own
optimizer, particularly since you know what sort of tail calls you're
after.  So if hand eliminating the calls is too much of a PITA, it's
not too bad to automate it yourself.  It's still a PITA, but not too
bad.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87zo6iqroh.fsf@pps.jussieu.fr>
Marco Antoniotti:

MA> In this specific case no.  However, as soon as you scale up a little
MA> bit (but not too far: you are probably better off with more complex
MA> data structures representing FSMs, should they become big.) the
MA> tail-recursive definitions do win.

I fully agree.  This way of coding is useful when the number of states
is moderate, but every state performs a sufficiently complex task so
that debugging of individual states may be necessary.

                                        Juliusz
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-01E616.02380418102001@news.paradise.net.nz>
In article <···················@news.callatg.com>, ·····@corman.net 
(Roger Corman) wrote:

> >  (defun foo ()
> >    (state-1))
> >
> >  (defun state-1 ()
> >    (case (read-char)
> >      ((#\a) (state-2))
> >      ((#\c) t)
> >      (t nil)))
> >
> >  (defun state-2 ()
> >    (case (read-char)
> >      ((#\b (state-1)))
> >      (t nil)))
> >
> >Clear, elegant, and piecewise manipulable.  But crucially reliant on
> >tail call elimination.
> 
> Finally, an example. How about this?
> 
> (defun foo ()
>     (do ((state #'state-1 (funcall state)))
>           ((not (functionp state)) state)))
> 
>   (defun state-1 ()
>     (case (read-char)
>       ((#\a) #'state-2)
>       ((#\c) t)
>       (t nil)))
> 
>   (defun state-2 ()
>     (case (read-char)
>       ((#\b) #'state-1)
>       (t nil)))
> 
> It seems pretty much equivalent to me, but does not rely on tail call
> elimination. 

Actually this is the "trampoline" technique used to *implement* tail 
call elimination in several Scheme-to-C compilers (and I think also 
ML-to-C compilers).  It's rather inefficient.

One Scheme-to-C compiler, "Chicken", uses a different technique, due to 
Cheney, which also translates Scheme function calls into ordinary C 
function calls, but checks for stack overflow at the start of each 
function.  When the stack overflows the code does a longjump() to a top 
level driver function, thus clearing the stack.  In Chicken the C stack 
also serves as the nursery generation for the garbage collector.

-- Bruce
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bcdb42c.1034288978@news.callatg.com>
On Thu, 18 Oct 2001 02:38:04 +1300, Bruce Hoult <·····@hoult.org> wrote:

>In article <···················@news.callatg.com>, ·····@corman.net 
>(Roger Corman) wrote:
>> Finally, an example. How about this?
>> 
>> (defun foo ()
>>     (do ((state #'state-1 (funcall state)))
>>           ((not (functionp state)) state)))
>> 
>>   (defun state-1 ()
>>     (case (read-char)
>>       ((#\a) #'state-2)
>>       ((#\c) t)
>>       (t nil)))
>> 
>>   (defun state-2 ()
>>     (case (read-char)
>>       ((#\b) #'state-1)
>>       (t nil)))
>> 
>> It seems pretty much equivalent to me, but does not rely on tail call
>> elimination. 
>
>Actually this is the "trampoline" technique used to *implement* tail 
>call elimination in several Scheme-to-C compilers (and I think also 
>ML-to-C compilers).  It's rather inefficient.

It seems to me that the only difference in efficiency (in this example, at
least--I am not claiming anything about the trampoline technique in general) is
the use of FUNCALL as opposed to a direct function call. This can be less
efficient, but I think that depends on the implementation.

>One Scheme-to-C compiler, "Chicken", uses a different technique, due to 
>Cheney, which also translates Scheme function calls into ordinary C 
>function calls, but checks for stack overflow at the start of each 
>function.  When the stack overflows the code does a longjump() to a top 
>level driver function, thus clearing the stack.  In Chicken the C stack 
>also serves as the nursery generation for the garbage collector.

Chicken sounds very interesting. I am interested to find out more about that.
It sounds like a very creative implementation.

Roger
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-D89D8A.17140318102001@news.paradise.net.nz>
In article <···················@news.callatg.com>, ·····@corman.net 
(Roger Corman) wrote:

> >One Scheme-to-C compiler, "Chicken", uses a different technique, due to 
> >Cheney, which also translates Scheme function calls into ordinary C 

Aaaarrrgghhh!! Brain fart.  Due to Baker, of course!!  oops.


> >function calls, but checks for stack overflow at the start of each 
> >function.  When the stack overflows the code does a longjump() to a top 
> >level driver function, thus clearing the stack.  In Chicken the C stack 
> >also serves as the nursery generation for the garbage collector.
> 
> Chicken sounds very interesting. I am interested to find out more
> about that. It sounds like a very creative implementation.

http://www.call-with-current-continuation.org/chicken.html

-- Bruce
From: felix
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bd0cbd8$0$30612$9b622d9e@news.freenet.de>
Roger Corman wrote in message <···················@news.callatg.com>...
>
>>One Scheme-to-C compiler, "Chicken", uses a different technique, due to 
>>Cheney, which also translates Scheme function calls into ordinary C 
>>function calls, but checks for stack overflow at the start of each 
>>function.  When the stack overflows the code does a longjump() to a top 
>>level driver function, thus clearing the stack.  In Chicken the C stack 
>>also serves as the nursery generation for the garbage collector.
>
>Chicken sounds very interesting. I am interested to find out more about that.
>It sounds like a very creative implementation.
>


Well, thanks! But the idea is entirely Henry Baker's. Chicken is just the
first implementation that uses this approach, AFAIK.

Converting to CPS and translating this straight to C solves a number
of problems quite elegantly, because you get things like (efficient)
first class continuations, multiple values and tail-recursion (in the sense
that a tail-recursive function doesn't run out of memory, even if it
allocates continuation frames). But I should also note that there are some
caveats (as usual):

1. This system allocates storage at a frightning pace. The generational garbage collection
  helps, but this should be kept in mind.
2. Straightforward CPS-converted code is horribly inefficient. A large part
  of the highlevel-optimizations Chicken performs are used to simplify
  the code back to a more efficient form.
3. As Bruce already points out there are many stack-checks to be done.
  Continuation-procedures (those introduced by the CPS conversion) normally
  don't need those, but you still get a lot of checking overhead.

Direct-style translation into C (as systems like Bigloo, Stalin or KCL and it's
children) will normally generate more efficient code, but of course have awful problems
with call/cc and/or tail-rec. The trampoline (driver-loop) approach
that is used for example in Gambit is somehow in between those two
extremes: I made the experience that it is sometimes able to outperform
direct style compilers in fixnum-/unsafe-mode, but is often slower than Chicken
when the declarations are removed.

To demonstrate the translation concept, I will give an example. 
Consider the following code:    

(define (len x)
  (if (null? x)
      0
      (+ 1 (len (cdr x))) ) )

Now if we compile this with "-benchmark-mode", then we get the 
following (comments inserted by me):

static void f14(int c,C_word t0,C_word t1,C_word t2){
C_word tmp;
C_word t3;
C_word t4;
C_word t5;
C_word ab[3],*a=ab;
/* Check stack */
if(!C_stack_probe(&a)){
/* Save current continuation and do a minor GC */
C_adjust_stack(3);
C_rescue(t0,2);
C_rescue(t1,1);
C_rescue(t2,0);
C_reclaim(tr3,f14);}
/* We land here if stack is OK, or GC is done */
if(C_truep((C_word)C_i_nullp(t2))){
t3=t1;
((C_proc2)(void*)(*((C_word*)t3+1)))(2,t3,C_fix(0));}
else{
/* Allocate continuation closure */
t3=(*a=C_CLOSURE_TYPE|2,a[1]=(C_word)f28,a[2]=t1,tmp=(C_word)a,a+=3,tmp);
t4=(C_word)C_slot(t2,C_fix(1));
/* Fetch global variable "len" */
t5=*((C_word*)lf[0]+1);
/* Invoke "len"s procedure (in tail position, since this is CPS) */
((C_proc3)(void*)(*((C_word*)t5+1)))(3,t5,t3,t4);}}

/* Continuation code */
static void f28(int c,C_word t0,C_word t1){
C_word tmp;
C_word t2;
C_word *a;
t2=((C_word*)t0)[2];
((C_proc2)(void*)(*((C_word*)t2+1)))(2,t2,(C_word)C_u_fixnum_plus(C_fix(1),t1));}

As can be seen the code is not really for Human eyes. It also wouldn't pass
any programming style contest other than IOCCC. ;-)


cheers,
felix
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bd10346.1251147184@news.callatg.com>
On Sat, 20 Oct 2001 02:44:29 +0200, "felix" <············@freenet.de> wrote:

>
>Roger Corman wrote in message <···················@news.callatg.com>...
>>
>>>One Scheme-to-C compiler, "Chicken", uses a different technique, due to 
>>>Cheney, which also translates Scheme function calls into ordinary C 
>>>function calls, but checks for stack overflow at the start of each 
>>>function.  When the stack overflows the code does a longjump() to a top 
>>>level driver function, thus clearing the stack.  In Chicken the C stack 
>>>also serves as the nursery generation for the garbage collector.
>>
>>Chicken sounds very interesting. I am interested to find out more about that.
>>It sounds like a very creative implementation.
>>
>
>
>Well, thanks! But the idea is entirely Henry Baker's. Chicken is just the
>first implementation that uses this approach, AFAIK.

I sure owe a lot to Henry Baker--all of his great papers and ideas.

I would think of Chicken as a stack-less architecture, which takes advantage of
C stack management code generation to implement the first generation of the
memory manager.  I understand ML is also stack-less (is that right?). I think
that is very interesting, and understand from Baker's papers that you can get
very good performance with such a system. I especially like how it so blows the
minds of most programmers. 

>3. As Bruce already points out there are many stack-checks to be done.
>  Continuation-procedures (those introduced by the CPS conversion) normally
>  don't need those, but you still get a lot of checking overhead.

Although to be entirely platform-independent the stack checks are required.
However, I would think on a particular platform you could get rid of them by
instead using the virtual memory system. You set the page above the stack (or
nursery, in this case) to read-only, and then catch and handle the exception.
This should help things, shouldn't it? The Win32 platform does this with the
stack automatically, so code never needs to check for stack overflow.

Roger
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2helny8vb9.fsf@dslab7.cs.uit.no>
I haven't followed this thread, so please excuse me for intruding with
something that is perhaps completely irrelevant or already covered.

I was thinking a about possible syntax for explicitly requesting
tail-call merging. Isn't return-from almost that operator? Every
return-from used to exit a function is in effect a tail call for that
function (?). Maybe return-from could be extended to communicate the
programmer's intention when the result-form is a function call?

Something along the lines of

  return-from name result &key tail-call-merge if-cannot-merge

Where tail-call-merge is a boolean, and if-cannot-merge one of :error,
:warn, or :ignore.

-- 
Frode Vatvedt Fjeld
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3212581297705213@naggum.net>
* Frode Vatvedt Fjeld <······@acm.org>
| I was thinking a about possible syntax for explicitly requesting
| tail-call merging.  Isn't return-from almost that operator?  Every
| return-from used to exit a function is in effect a tail call for that
| function (?).  Maybe return-from could be extended to communicate the
| programmer's intention when the result-form is a function call?

  Since you could wrap the entire function body in a return-from, I think
  this would not be helpful.  Figuring out whether a function call is in a
  tail-call-mergable "position" is not helped by such use of this form.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2h669a8cni.fsf@dslab7.cs.uit.no>
Erik Naggum <····@naggum.net> writes:

> Since you could wrap the entire function body in a return-from, I
> think this would not be helpful.  Figuring out whether a function
> call is in a tail-call-mergable "position" is not helped by such use
> of this form.

I think you misunderstood me. I'll try to spell out what I meant more
clearly.

  (defun foo (x y)
    (when x
      (return-from foo (bar)))
    (if y (baz)
      (return-from foo (bax))))

Here, all of the forms (bar), (baz) and (bax) are in a
tail-call-mergable position, but only (bar) and (bax) explicitly so.

Now, my suggestion was that one can rewrite your typical (tail-)
recursive function:

  (defun member (item list)
    (cond
     ((null list) nil)
     ((eq item (car list)) list)
     (t (member item (cdr list)))))

to this:

  (defun member (item list)
    (cond
     ((null list) nil)
     ((eq item (car list)) list)
     (t (return-from member
          (member item (cdr list))
          :tail-call-merge t
          :if-cannot-merge :error))))

..and be sure your member function either runs in constant space or
compilation will fail. Or the compiler will emit a warning if that is
what you specify. Of course, if the result-form is anything besides a
function call, tail-call-merging will fail.

-- 
Frode Vatvedt Fjeld
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwsnceb4s2.fsf@world.std.com>
Frode Vatvedt Fjeld <······@acm.org> writes:

>   (defun foo (x y)
>     (when x
>       (return-from foo (bar)))
>     (if y (baz)
>       (return-from foo (bax))))
> 
> Here, all of the forms (bar), (baz) and (bax) are in a
> tail-call-mergable position, but only (bar) and (bax) explicitly so.

As we discussed, if there is a closure over the block FOO, then it will
veto the ability of these other things to merge.  I therefore don't like
the term "tail-call[-mergable] position" because it suggests that position
is what determines a tail call.  It does not.  What is or is not a tail
call is determined BOTH by position AND by other semantics.

>   (defun member (item list)
>     (cond
>      ((null list) nil)
>      ((eq item (car list)) list)
>      (t (return-from member
>           (member item (cdr list))
>           :tail-call-merge t
>           :if-cannot-merge :error))))
> 
> ..and be sure your member function either runs in constant space or
> compilation will fail. Or the compiler will emit a warning if that is
> what you specify. Of course, if the result-form is anything besides a
> function call, tail-call-merging will fail.

I won't even bother to ask about
 (defun member (item list &optional (tail-merge t))
   ... (return-from member (member item (cdr list))
         :tail-call-merge tail-merge ...))

My personal feeling is that the language should either define a
reasonable semantics or not, but that keyword arguments controlling
the very lowest level of language sementics is both unsightly and a
waste of time.  There are myriad design decisions in the language that
might want such things, and the result of actually using such a style
would be too gross to look at.  
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2h1yjy87fr.fsf@dslab7.cs.uit.no>
Kent M Pitman <······@world.std.com> writes:

> [..] What is or is not a tail call is determined BOTH by position
> AND by other semantics.

Of course.

> I won't even bother to ask about
>  (defun member (item list &optional (tail-merge t))
>    ... (return-from member (member item (cdr list))
>          :tail-call-merge tail-merge ...))

I didn't really consider this, but my intention was that the keyword
arguments would have to be constants. Not to be evaluated, I suppose.

> My personal feeling is that the language should either define a
> reasonable semantics or not, but that keyword arguments controlling
> the very lowest level of language sementics is both unsightly and a
> waste of time.

I think it's ugly too. Maybe &optionals would be a slight improvement.

> There are myriad design decisions in the language that might want
> such things, and the result of actually using such a style would be
> too gross to look at.

Agreed.

-- 
Frode Vatvedt Fjeld
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bsiz225h.fsf@itasoftware.com>
Kent M Pitman <······@world.std.com> writes:

> Frode Vatvedt Fjeld <······@acm.org> writes:
> 
> >   (defun foo (x y)
> >     (when x
> >       (return-from foo (bar)))
> >     (if y (baz)
> >       (return-from foo (bax))))
> > 
> > Here, all of the forms (bar), (baz) and (bax) are in a
> > tail-call-mergable position, but only (bar) and (bax) explicitly so.
> 
> As we discussed, if there is a closure over the block FOO, then it will
> veto the ability of these other things to merge.  

There shouldn't be a problem with closing over block FOO, it doesn't
logically require storage beyond that which the closure would use. 

> My personal feeling is that the language should either define a
> reasonable semantics or not, but that keyword arguments controlling
> the very lowest level of language sementics is both unsightly and a
> waste of time.  There are myriad design decisions in the language that
> might want such things, and the result of actually using such a style
> would be too gross to look at.  

Agreed.
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3212605764038030@naggum.net>
* Erik Naggum
| Since you could wrap the entire function body in a return-from, I think
| this would not be helpful.  Figuring out whether a function call is in a
| tail-call-mergable "position" is not helped by such use of this form.

* Frode Vatvedt Fjeld
| I think you misunderstood me.  I'll try to spell out what I meant more
| clearly.
| 
|   (defun foo (x y)
|     (when x
|       (return-from foo (bar)))
|     (if y (baz)
|       (return-from foo (bax))))

  Well, _I_ think I understand your proposal better than you do yourself. :)
  Your proposal changes and improves exactly nothing, but just adds a false
  impression of something that still has be determined the same way it was
  before it was introduced.

(defun foo (x y)
  (return-from foo
    (if x
      (bar)
      (if y
        (baz)
        (bax))))))

| Here, all of the forms (bar), (baz) and (bax) are in a tail-call-mergable
| position, but only (bar) and (bax) explicitly so.

  I do not agree with this.  Figuring out whether a function call is
  tail-call-mergeable is generally not possible through a trivial visual
  inspection.  In the above form, all of the function calls are pretty
  obviously the last call the function will make, and if you cannot see
  whether a function call will yield the return value or not, it certainly
  will not help to have a piece of wrapper code around it that might _lie_.

| Now, my suggestion was that one can rewrite your typical (tail-)
| recursive function: [...] to this: [...] ..and be sure your member
| function either runs in constant space or compilation will fail.

  My argument against your proposal is that you could wrap the entire
  function in this newfangled return-from form and not reduce the problem
  one iota.

| Or the compiler will emit a warning if that is what you specify. Of
| course, if the result-form is anything besides a function call,
| tail-call-merging will fail.

  Now, this is getting so specific it looks like a _real_ kluge.  How about
  if someone provided a compiler-macro for that function?  How about a real
  macro?  Would you really want so much of your code to fail compilation
  even though nothing had materially changed?  In other words, if you had a
  form like you propose, the only place I can see it being really useful is
  at the _outermost_ level, like I have demonstrated above, _not_ at the
  innermost, terminal function call that it _might_ apply to.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2hwv1p7t4a.fsf@dslab7.cs.uit.no>
Erik Naggum <····@naggum.net> writes:

> Well, _I_ think I understand your proposal better than you do
> yourself. :)

Maybe so.. :) but please consider it an "idea" rather than "proposal".

> Your proposal changes and improves exactly nothing, but just adds a
> false impression of something that still has be determined the same
> way it was before it was introduced.
>
> (defun foo (x y)
>   (return-from foo
>     (if x
>       (bar)
>       (if y
>         (baz)
>         (bax))))))

I actually don't see how this example relates to my .. uhm.. idea in
any way. What is it meant to show? I wasn't aiming to change how
compilers determine what are tail-call-mergable function calls, but to
provide a mechanisms for programmers to express "I want this function
call to be 'merged'".

> [..] it certainly will not help to have a piece of wrapper code
> around it that might _lie_.

I don't understand. How can return-from lie? It's guaranteed to
terminate the block/function, so long as no sub-form of the
result-form performs a non-local exit (in which case control never
reaches the function application anyway, so it's not a problem).

> My argument against your proposal is that you could wrap the entire
> function in this newfangled return-from form and not reduce the
> problem one iota.

You must be thinking of some other problem than I am.

> Now, this is getting so specific it looks like a _real_ kluge. 

It's certainly kludgy.

> How about if someone provided a compiler-macro for that function?
> How about a real macro?

What if? The compiler must deal with macros and compiler-macros
regardless, and I don't see how they come into play here.

-- 
Frode Vatvedt Fjeld
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3212616172232851@naggum.net>
* Frode Vatvedt Fjeld <······@acm.org>
| What if? The compiler must deal with macros and compiler-macros
| regardless, and I don't see how they come into play here.

  The questions I have tried in vain to raise are: How much knowledge do
  you require to be able to use your idea productively?  What does it mean
  to employ your suggested form around another form?

  Common Lisp does not, in fact, provide you with any information how a
  particular form will be compiled.  A function call may be inlined, which
  you may defeat with your desire for a tail-call-merging request, it may
  be turned into several function calls, most anything can happen between
  source and machine code.  Therefore, if your idea is not able to
  encompass an entire function's body, it is completely useless, because it
  may _have_ to encompass an entire function's body even if you think you
  are giving it a function call.  If you really want to tell your compiler
  that an _actual_ function call should be tail-call merged, you first have
  to know if your compiler will actually generate a non-merged function
  call that _could_ be a merged tail call.

  In other words, it is an idea that fits languages that do not do anything
  "interesting" with the code they compile.  Instead of providing something
  useful, such as important information for your compiler, you are instead
  limiting your compiler's ability to do interesting things with your code.

  If you want to do something useful by changing the language, I suggest
  finding/inventing ways to tell your compiler that you promise never to
  change the definition of some functions, classes, methods, whatever.
  That kind of information would be tremendously useful to the compiler,
  because it can make assumptions that it cannot make today and which
  necessarily means less opportunity for some kinds of optimization.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-0954C4.15150521102001@news.paradise.net.nz>
In article <················@naggum.net>, Erik Naggum <····@naggum.net> 
wrote:

> Common Lisp does not, in fact, provide you with any information
> how a particular form will be compiled.  A function call may be
> inlined, which you may defeat with your desire for a tail-call-
> merging request

But inlining is one method of implementing the goals of tail-call 
merging.


> If you want to do something useful by changing the language, I
> suggest finding/inventing ways to tell your compiler that you
> promise never to change the definition of some functions,
> classes, methods, whatever.  That kind of information would be
> tremendously useful to the compiler, because it can make
> assumptions that it cannot make today and which necessarily
> means less opportunity for some kinds of optimization.

I agree.  That is exactly what Dylan does with its "sealing" 
declarations.

-- Bruce
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwitd991c9.fsf@world.std.com>
Frode Vatvedt Fjeld <······@acm.org> writes:

> 
> Erik Naggum <····@naggum.net> writes:
> 
> > Well, _I_ think I understand your proposal better than you do
> > yourself. :)
> 
> Maybe so.. :) but please consider it an "idea" rather than "proposal".
> 
> > Your proposal changes and improves exactly nothing, but just adds a
> > false impression of something that still has be determined the same
> > way it was before it was introduced.
> >
> > (defun foo (x y)
> >   (return-from foo
> >     (if x
> >       (bar)
> >       (if y
> >         (baz)
> >         (bax))))))
> 
> I actually don't see how this example relates to my .. uhm.. idea in
> any way. What is it meant to show? I wasn't aiming to change how
> compilers determine what are tail-call-mergable function calls, but to
> provide a mechanisms for programmers to express "I want this function
> call to be 'merged'".

But it doesn't say that.


 (defun bar (x y) (funcall x (1+ y)))
 (defun foo (y)
   (let ((x #'(lambda (x) (return-from foo x)))) ;<-- first RETURN-FROM
     (return-from foo ;<-- second RETURN-FROM
       (bar x y))))

You might think the second RETURN-FROM was requesting tail-calling, but
it simply isn't.  First, it's in no more of a tail call position than it
was if you omitted the RETURN-FROM.  Second, it cannot be tail called 
because there is pending state between the RETURN-FROM and the BLOCK 
that cannot be disposed of until BAR has finished executing.

If all you mean by "request" is that it should be done "if possible",
then I would say that to an advocate of aggressive tail calling, EVERY
form is a request for tail calling "if possible".  Some things are not
able to be tail called, and if saying that's enough to undo the request,
then you're asking the same of every form.


> > [..] it certainly will not help to have a piece of wrapper code
> > around it that might _lie_.
> 
> I don't understand. How can return-from lie? It's guaranteed to
> terminate the block/function,

It's not a guarantee that there is no intervening state inhibiting
correct and immediate return.  See example above.
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2hsncd7lsw.fsf@dslab7.cs.uit.no>
Kent M Pitman <······@world.std.com> writes:

> If all you mean by "request" is that it should be done "if
> possible", then I would say that to an advocate of aggressive tail
> calling, EVERY form is a request for tail calling "if possible".
> Some things are not able to be tail called, and if saying that's
> enough to undo the request, then you're asking the same of every
> form.

My perspective is that I ordinarily don't want tail-call merging
(because of the reduced debuggability), with the exception that I
sometimes write (mutually) recursive functions in which case I'm
usually acutely aware what is and isn't tail-calls. And it could be
nice to be able to communicate this explicitly in the code.

-- 
Frode Vatvedt Fjeld
From: Francois-Rene Rideau
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87n12lckt5.fsf@Samaris.tunes.org>
Kent M Pitman <······@world.std.com> writes:
>  (defun bar (x y) (funcall x (1+ y)))
>  (defun foo (y)
>    (let ((x #'(lambda (x) (return-from foo x)))) ;<-- first RETURN-FROM
>      (return-from foo ;<-- second RETURN-FROM
>        (bar x y))))
> 
> You might think the second RETURN-FROM was requesting tail-calling, but
> it simply isn't.  First, it's in no more of a tail call position than it
> was if you omitted the RETURN-FROM.  Second, it cannot be tail called
> because there is pending state between the RETURN-FROM and the BLOCK
> that cannot be disposed of until BAR has finished executing.

Why would BLOCK require any dynamic state?
It can be fully resolved at compile-time, thanks to lexical scoping.
The return-from need only remember the continuation of foo;
it will just unwind the stack up to foo's caller.
No need to keep a call frame for foo or any data on either stack or heap.

The only reason I can imagine for keeping dynamic state would be for
adding a simple error checking in method x: keep a hopefully unique tag
(e.g. 32 bit counter) to compare when you return-from within
a nested function, or equivalently, a flag set by each return point,
to determine if the function has already returned.
Per-call-frame error-checking data cannot be explicitly kept
without breaking the tail-call-merging semantics.
But you only need per-return-frame error-checking data - i.e. only one
tag/flag per arbitrary long series of tail-calls. All you need is
a way to distinguish whether your return frame already has a special tag
or not, and if not, make such a special return frame -- and you only need
test it when you setup a return-from a nested scope -- pay as you go.

More generally, if you consider that the continuation is only one special
variable among others, the same implementation trick can be extended to
handling all special bindings, and even special classes of protected
resources (you could extend unwind-protect to specify a protected resource
or list thereof). You need only one special frame per restoration point,
not one special frame per local modification point - a long series
of tail-calling local modifications can be fixed up by only one restoration.

Tail-call-merging means you cannot implement BLOCK/RETURN naively
with CATCH/THROW. Uh, so what? Actually, I imagine you could still share
the same basic mechanism for both pairs of constructs -
you'd just have to be a bit clever about the call-frame you return to,
so as to merge such constructs instead of stacking them.
Of course it means work for the implementer, especially with error-checking
- nobody denies it. But it's definitely possible. Even with error-checking.

You can argue that the cost to the implementer is not worth it,
but please stop arguing that it is impossible to implement.

As for me, I appreciate it when features are standardized, even when they
are optional rather than mandated -- it means that if I need the feature,
I can use the same API on all implementations that support the feature,
instead of having a fat and stupid compatibility layer.
Same goes with multithreading, sockets, etc. - I don't require that
every vendor implement every feature, but I hate it when every vendor
has his own gratuitously incompatible API to the same concepts.
CL lacks standard optional extensions.

Yours freely,

[ Fran�ois-Ren� �VB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
[  TUNES project for a Free Reflective Computing System  | http://tunes.org  ]
If you call a tail a leg, how many legs has a dog? Five?
No! Calling a tail a leg doesn't make it a leg.
	-- Abraham Lincoln, explaining the difference between
				lexical scoping and dynamic scoping
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwofn1osbt.fsf@world.std.com>
Francois-Rene Rideau <···········@tunes.org> writes:

> Kent M Pitman <······@world.std.com> writes:
> >  (defun bar (x y) (funcall x (1+ y)))
> >  (defun foo (y)
> >    (let ((x #'(lambda (x) (return-from foo x)))) ;<-- first RETURN-FROM
> >      (return-from foo ;<-- second RETURN-FROM
> >        (bar x y))))
> > 
> > You might think the second RETURN-FROM was requesting tail-calling, but
> > it simply isn't.  First, it's in no more of a tail call position than it
> > was if you omitted the RETURN-FROM.  Second, it cannot be tail called
> > because there is pending state between the RETURN-FROM and the BLOCK
> > that cannot be disposed of until BAR has finished executing.
> 
> Why would BLOCK require any dynamic state?
> It can be fully resolved at compile-time, thanks to lexical scoping.
> The return-from need only remember the continuation of foo;
> it will just unwind the stack up to foo's caller.
> No need to keep a call frame for foo or any data on either stack or heap.

Then assume it's

(defun foo (y)
  (block bar
    (let ((x #'(lambda (x) (return-from bar x))))
      (return-from foo
        (bar x y))))
  (zing))
From: Francois-Rene Rideau
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87wv1orgyr.fsf@Samaris.tunes.org>
>>: Francois-Rene Rideau
>: Kent M Pitman

>> Why would BLOCK require any dynamic state?
>> It can be fully resolved at compile-time, thanks to lexical scoping.
>> The return-from need only remember the continuation of foo;
>> it will just unwind the stack up to foo's caller.
>> No need to keep a call frame for foo or any data on either stack or heap.

(KMP's example alpha-converted to avoid confusion)

> (defun foo (y)
>   (block bar
>     (let ((x #'(lambda (z) (return-from bar t))))
>       (return-from foo
>         (baz x u))))
>   (zing))

OK, I see what you mean.
The issue is not with BLOCK in particular, but with escaping continuations
(or more precisely, escaping intermediate activation frames).

The (return-from foo ...) points to foo's continuation,
while the (return-from bar ...) points to bar's continuation.
However, bar's continuation must be pushed down the continuation PDL,
and this continuation escapes in the call to baz, which may call it.
Hence, the return-from foo cannot just pop bar's continuation from the PDL
before to call baz - although it's arguably a conceptual tail-call
(i.e. baz's continuation is foo's, short-circuiting bar's),
bar's continuation cannot be proactively recycled.

The immediate conclusion is that the issue of tail-call-merging is difficult,
and less obvious than it might appear at first hand. Solutions include:
1- not trying to do tail-call-merging, avoiding the whole complexity.
2- restricting tail-call-merging to simple cases, and issueing warnings
 or errors when tail-call-merging is explicitly specified by the user
 on a call that the compiler cannot prove is safe (because of escaping
 continuations - and when passed a function that can be dynamically modified,
 about any reified continuation can be considered escaping).
3- require that when the user specifies tail-call-merging, he promises
 that the intermediate frames on the continuation PDL be never needed:
 in your example, when you (return-with-merged-tail-from foo (baz x u)), you
 promise that x won't be used.
4- having slightly costlier representations for the continuation PDL.
 The "stack" can be considered mere CDR-coding of the continuation PDL,
 and more expensive representations (made compatible through trampolines)
 could being available, that allow for a tree of linear continuations
 (tail-sharing lists of activation records) instead of a list.
 Full tail-call-merging then means that some GC mechanism be available for
 the continuation arena. Linearity and dynamic extent of continuations
 (if guaranteed) allows optimization of this GC as compared to generic GC.
 According to Jrm's article, the K-machine did just that - the continuation
 PDL was not a CDR-coded stack, but a chained structure of activation frames,
 new frames being allocated from a free list rather than by bumping a pointer.
 If you are ready to pay more, you can even have first-class continuations
 (frames either being directly allocated on heap, or being heapifyable).

[ Fran�ois-Ren� �VB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
[  TUNES project for a Free Reflective Computing System  | http://tunes.org  ]
Opportunity is missed by most people because it comes dressed in overalls
and looks like work.	-- T. A. Edison
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3d4b21vt.fsf@itasoftware.com>
Francois-Rene Rideau <···········@tunes.org> writes:

> >>: Francois-Rene Rideau
> >: Kent M Pitman
> 
> >> Why would BLOCK require any dynamic state?
> >> It can be fully resolved at compile-time, thanks to lexical scoping.
> >> The return-from need only remember the continuation of foo;
> >> it will just unwind the stack up to foo's caller.
> >> No need to keep a call frame for foo or any data on either stack or heap.
> 
> (KMP's example alpha-converted to avoid confusion)
> 
> > (defun foo (y)
> >   (block bar
> >     (let ((x #'(lambda (z) (return-from bar t))))
> >       (return-from foo
> >         (baz x u))))
> >   (zing))
> 
> OK, I see what you mean.
> The issue is not with BLOCK in particular, but with escaping continuations
> (or more precisely, escaping intermediate activation frames).
> 
> The (return-from foo ...) points to foo's continuation,
> while the (return-from bar ...) points to bar's continuation.
> However, bar's continuation must be pushed down the continuation PDL,
> and this continuation escapes in the call to baz, which may call it.
> Hence, the return-from foo cannot just pop bar's continuation from the PDL
> before to call baz - although it's arguably a conceptual tail-call
> (i.e. baz's continuation is foo's, short-circuiting bar's),
> bar's continuation cannot be proactively recycled.

Yes.  On the other hand, the (BLOCK BAR ...) is not in a tail
position, so you wouldn't expect it to be tail-called anyway.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <878ze2s785.fsf@pps.jussieu.fr>
FR> 2- restricting tail-call-merging to simple cases, and issueing
FR> warnings or errors when tail-call-merging is explicitly specified
FR> by the user on a call that the compiler cannot prove is safe
FR> (because of escaping continuations - and when passed a function
FR> that can be dynamically modified, about any reified continuation
FR> can be considered escaping).

That was my initial idea: only requiring implementations to perform
tail-call elimination in a set of highly stylised ``easy'' situations.

As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
Thus, a specification of tail call elimination that doesn't do
anything within a BLOCK would be perfectly useless.

Hence the idea of only requiring the implementation to eliminate tail
calls within a ``useless'' block, one the tag of which never appears
in its scope.  While this is fine for the compiler, it cannot be
implemented in an interpreter without walking (and therefore
macroexpanding) every the code of every closure.  Is such an
interpretation still an intepreter?

FR> 3- require that when the user specifies tail-call-merging, he
FR> promises that the intermediate frames on the continuation PDL be
FR> never needed: in your example, when you
FR> (return-with-merged-tail-from foo (baz x u)), you promise that x
FR> won't be used.

Yuck.

FR> 4- having slightly costlier representations for the continuation PDL.

How does this help?  The problem is not made any easier by allocating
the dynamic chain on the heap.

                                        Juliusz
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <n12ht0n8.fsf@itasoftware.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> FR> 2- restricting tail-call-merging to simple cases, and issueing
> FR> warnings or errors when tail-call-merging is explicitly specified
> FR> by the user on a call that the compiler cannot prove is safe
> FR> (because of escaping continuations - and when passed a function
> FR> that can be dynamically modified, about any reified continuation
> FR> can be considered escaping).
> 
> That was my initial idea: only requiring implementations to perform
> tail-call elimination in a set of highly stylised ``easy'' situations.
> 
> As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> Thus, a specification of tail call elimination that doesn't do
> anything within a BLOCK would be perfectly useless.

This is not true, a BLOCK form does need to allocate storage on the
stack. 
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwhesolib6.fsf@world.std.com>
···@itasoftware.com writes:

> Juliusz Chroboczek <···@pps.jussieu.fr> writes:
>
> > As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> > Thus, a specification of tail call elimination that doesn't do
> > anything within a BLOCK would be perfectly useless.
> 
> This is not true, a BLOCK form does need to allocate storage on the
> stack. 

Yes it does.  Unless you are prepared to disallow interpreted
implementations.  And the important thing to understand is that the
"you" is not you personally, but you with the weight of design
responsibility upon your shoulders and the understanding that there
are others who may not share your (jrm's) value system.

The only way to not allocate that block is to do a full code-walk to
assure that the code does not use the block, and at that point we have
already done the hard work of macroexpanding each and every macro in
the whole tree, we might as well just call it minimal compilation.

So unless you explain better, my feeling is that your statement is 
in error given the constraints of the language.

Mind you, had this issue been raised at the time of the language's
design, I myself (even not much of a fan of tail recursion), would
have seen it as sufficient reason not to have DEFUN include an
implicit block.  However, we don't have the decision to re-make unless
the language comes up for wholesale change (unlikely in the near
future), so we work with what we have and what we can add compatibly.
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bsivq0p5.fsf@itasoftware.com>
Kent M Pitman <······@world.std.com> writes:

> ···@itasoftware.com writes:
> 
> > Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> >
> > > As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> > > Thus, a specification of tail call elimination that doesn't do
> > > anything within a BLOCK would be perfectly useless.
> > 
> > This is not true, a BLOCK form does need to allocate storage on the
> > stack. 
> 
> Yes it does.  

At the risk of sounding like John Cleese, ``No, it doesn't.''

A BLOCK does two things:  
  1.  It captures the active continuation at the point the block is
      entered. 

  2.  It binds the block name to that continuation in `block name'
      space. 

Step 1 requires no storage at all, it is simply identifying where the
block will return to.  There are two cases:  the block is either in
`tail position' (a reduction) or it isn't (a subproblem).  In either
case, there will be a pending continuation, and this continuation will
be resumed when the block is exited.

Step 2 is more problematic.  Once you know the continuation, you have
to construct some sort of binding between the block name and that
continuation.  In many cases, the compiler can delete the binding
because the block name is unused, or the binding can be deduced from
where the return-from is called.  In the general case, however, the
return-from could be closed over and thus the binding to block-space
would have to be part of the environment of that closure.

Because blocks have dynamic extent, you *could* allocate the block
binding on the stack, but you *don't* have to.  The heap is a fine
place for it.

> Unless you are prepared to disallow interpreted implementations.  

This technique will still work under an interpreter, but it will cons
quite a bit more than the compiled version.

> And the important thing to understand is that the
> "you" is not you personally, but you with the weight of design
> responsibility upon your shoulders and the understanding that there
> are others who may not share your (jrm's) value system.

Although I always seem to share my own value system, in this
particular case I am talking about a general solution. 

> The only way to not allocate that block is to do a full code-walk to
> assure that the code does not use the block, and at that point we have
> already done the hard work of macroexpanding each and every macro in
> the whole tree, we might as well just call it minimal compilation.
> 
> So unless you explain better, my feeling is that your statement is 
> in error given the constraints of the language.

Yes, you always need to allocate a binding for the block (unless you
can prove otherwise).  However, you can allocate that binding in the
heap, and it *will* be garbage collected, so there is no reason the
BLOCK form would prevent tail recursion.
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3213009062053491@naggum.net>
* Kent M Pitman
| Yes it does.  

* ···@itasoftware.com
| At the risk of sounding like John Cleese, ``No, it doesn't.''

  I think Kent means "there is a case when it does" and that you mean
  "there is a case when it doesn't".  The former is far more interesting
  than the latter, because the former has far more implications that the
  latter.  That some problem vanish when block does not allocate is quite
  unimportant if those problems do not vanish if block does allocate.

  That it is possible to implement something a particular way that will
  remove a particular set of problems, does not mean that that particular
  way is usable within the framework that needs to cater to more than just
  this particular problem.

  This line of argument is the core of my objection to _requiring_ Common
  Lisp systems to do tail-call merging, because I can foresee several
  negative consequences of this requirement for several implementations
  that will impact other useful things that I want much more than I want
  tail-call merging.  Other people have other priorities, but since there
  conflicts, and people can actually make do with _implementations_ that
  offer something they want (except for Juliusz Chroboczek, who does not
  want to use it, anyway), and since changing the language to force
  everybody to do tail-call merging carries a cost in terms of what other
  things become easier and harder to do -- such as an attendant reluctance
  to do things that make tail-call merging impossible -- the status quo is
  _politically_ satisfactory: Making changes will be _unsatisfactory_.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <y9lzeou7.fsf@itasoftware.com>
Erik Naggum <····@naggum.net> writes:

> * Kent M Pitman
> | Yes it does.  
> 
> * ···@itasoftware.com
> | At the risk of sounding like John Cleese, ``No, it doesn't.''
> 
>   I think Kent means "there is a case when it does" and that you mean
>   "there is a case when it doesn't".  

Actually, I mean that there is no case where it does.  (`It' meaning
having a block in a tail-recursive position causing tail-recursion to
not be possible)

>   The former is far more interesting than the latter, because the
>   former has far more implications that the latter.  That some
>   problem vanish when block does not allocate is quite unimportant
>   if those problems do not vanish if block does allocate.
> 
>   That it is possible to implement something a particular way that
>   will remove a particular set of problems, does not mean that that
>   particular way is usable within the framework that needs to cater
>   to more than just this particular problem.

Agreed.

>   This line of argument is the core of my objection to _requiring_ Common
>   Lisp systems to do tail-call merging, because I can foresee several
>   negative consequences of this requirement for several implementations
>   that will impact other useful things that I want much more than I want
>   tail-call merging.  Other people have other priorities, but since there
>   conflicts, and people can actually make do with _implementations_ that
>   offer something they want (except for Juliusz Chroboczek, who does not
>   want to use it, anyway), and since changing the language to force
>   everybody to do tail-call merging carries a cost in terms of what other
>   things become easier and harder to do -- such as an attendant reluctance
>   to do things that make tail-call merging impossible -- the status quo is
>   _politically_ satisfactory: Making changes will be _unsatisfactory_.

I happen to *like* tail-recursion, but since every decent Common Lisp
implementation provides *some* mechanism for tail recursion in the
most common and useful cases, I don't see a good reason to add it to
the CL standard.  (I also don't see a good reason to avoid relying on
it either, because if your Common Lisp can't optimize it, you ought to
get one that can.)
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwwv1jsoit.fsf@world.std.com>
···@itasoftware.com writes:

> Kent M Pitman <······@world.std.com> writes:
> 
> > ···@itasoftware.com writes:
> > 
> > > Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> > >
> > > > As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> > > > Thus, a specification of tail call elimination that doesn't do
> > > > anything within a BLOCK would be perfectly useless.
> > > 
> > > This is not true, a BLOCK form does need to allocate storage on the
> > > stack. 
> > 
> > Yes it does.  
> [discussion of how a heap can implement a stack elided]

But nothing of what you said was specific to tail call elimination.
The same technique suffices to implement specials, unwind-protects, etc.

To me, all you've done is described a gc-able stack, you haven't described
the heap.

Scheme likes to talk about how it needs have no stack, but I think this
is mere gaming.  The fact is that all a linear stack is is a "fast-gc heap"
that goes away without gc expense when you exit a dynamic contour.  If you
eliminate the "fast-gc" issue from it, then everything can be done on the
heap.

I (perhaps overgenerously) took your remarks to mean "does not need to 
allocate storage "in service of stack issues" and did not think you meant
literally "does not need to allocate storage here at the big red X", which
seemed a pointless remark.

Often a proponent of tail call elimination will say "I don't want stack
allocated on every function call".  That person isn't going to be any happier
with heap being allocated on every function call if that heap is not getting
de-allocated until the final return happens, and that's the problem you
have here.

The nature of an interpreter in this context seems to be that you
haven't done total lexical analysis.  So I assume you can't gc parts
of the function until you know they won't be used.  [Unless you keep
some special bookkeeping that tallies something sort of like a weird
mark-sweep of the code where parts check in saying "i've been
analyzed" and you hierarchically do an extra semantic gc in coroutine
with the rest of the execution,] the general case seems to me to be
that you have to lock the tag against GC until the end of dynamic
execution in case a new subform comes along that requires it.  If
that's so, then the code either sits in the heap or sits on the stack
for the same duration, the entire dynamic execution of the form, and
so the material issue is how fast it gets gc'd at the end.

Tail call elimination is meaningful not because it occurs in the stack or
the heap but because when you finally decide to do it, you de-link it from
the chain in the heap or you de-link+fast-gc it in the stack.  If you
explain only the mechanism for putting the thing on the stack or putting it
in the heap, but not the mechanism to de-link it, then it's still there.

Scheme people see a stack as an evil to be shunned and so like to refer to
Scheme as stackless.   I see a stack as a useful abstraction so I like to
refer to scheme's heap/continuation allocation as implementing a stack.
That part is all in the mind.

But back to your original remark, I think my sense is that you can't (or
I don't offhand know a way to) execute a CL BLOCK in an interpreter without
either allocating stack or dynamically locking equivalent heap for the 
duration of the dynamic extent call.  
From: Duane Rettig
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4elnrwsrr.fsf@beta.franz.com>
Kent M Pitman <······@world.std.com> writes:

> ···@itasoftware.com writes:
> 
> > Kent M Pitman <······@world.std.com> writes:
> > 
> > > ···@itasoftware.com writes:
> > > 
> > > > Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> > > >
> > > > > As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> > > > > Thus, a specification of tail call elimination that doesn't do
> > > > > anything within a BLOCK would be perfectly useless.
> > > > 
> > > > This is not true, a BLOCK form does need to allocate storage on the
> > > > stack. 
> > > 
> > > Yes it does.  
> > [discussion of how a heap can implement a stack elided]
> 
> But nothing of what you said was specific to tail call elimination.
> The same technique suffices to implement specials, unwind-protects, etc.
> 
> To me, all you've done is described a gc-able stack, you haven't described
> the heap.

 [ ... ]

> Tail call elimination is meaningful not because it occurs in the stack or
> the heap but because when you finally decide to do it, you de-link it from
> the chain in the heap or you de-link+fast-gc it in the stack.  If you
> explain only the mechanism for putting the thing on the stack or putting it
> in the heap, but not the mechanism to de-link it, then it's still there.

 [ ... ]

> But back to your original remark, I think my sense is that you can't (or
> I don't offhand know a way to) execute a CL BLOCK in an interpreter without
> either allocating stack or dynamically locking equivalent heap for the 
> duration of the dynamic extent call.  

The third alternative is to not allocate space at all.  All architectures
that I know of - even register-starved ones - have at least enough
non-memory resources to complete a tail-call within the register set,
without the need for any memory allocation at all for a stack frame.

This example is on the sparc, because its stack allocation and
de-allocation instructions are easy to recognize (i.e. save and
restore).  Note that the first example allocates the stack and does
the jump-subroutine (jmpl) to bar.  The second example, the
tail-merging one, allocates no stack at all and just jumps to bar.

CL-USER(1): (disassemble (compile (defun foo (x) (bar x))))
Warning: While compiling these undefined functions were referenced: BAR.
;; disassembly of #<Function FOO>
;; formals: X
;; constant vector:
0:	BAR

;; code start: #x4572394:
   0: 9de3bf98     save	%o6, #x-68, %o6
   4: 80a0e001     cmp	%g3, #x1
   8: 93d02010     tne	%g0, #x10
  12: 81100001     taddcctv	%g0, %g1, %g0
  16: c4076022     ld	[%i5 + 34], %g2	; BAR
  20: 90100018     mov	%i0, %o0
  24: 9fc1200b     jmpl	%g4 + 11, %o7
  28: 86182001     xor	%g0, #x1, %g3
  32: 81c7e008     jmp	%i7 + 8
  36: 91ea0000     restore	%o0, %g0, %o0
CL-USER(2): (disassemble (compile (defun foo (x)
                                    (declare (optimize speed (debug 0)))
                                    (bar x))))
Warning: While compiling these undefined functions were referenced: BAR.
;; disassembly of #<Function FOO>
;; formals: X
;; constant vector:
0:	BAR

;; code start: #x4584d9c:
   0: 80a0e001     cmp	%g3, #x1
   4: 93d02013     tne	%g0, #x13
   8: 81100001     taddcctv	%g0, %g1, %g0
  12: c4036022     ld	[%o5 + 34], %g2	; BAR
  16: 81c1200b     jmp	%g4 + 11
  20: 86182001     xor	%g0, #x1, %g3
CL-USER(3): 


Of course, one might respond with something like "This is a trivial
example, and will not happen very often in real code".  But you might
be surprised to learn how many times this kind of "jumping-leaf" function
actually does occur in real life.  It is sometimes fun to look at a
tail-merging function (which is however allocating stack and then
de-allocating it before the jump) and figuring out what is preventing
the work from being done all in registers.  This sometimes results in
a tweak to the compiler to use just one less pseudo-register, so that
the whole algorithm fits into registers and the tail-merging function
can be then compiled without allocating/de-allocating stack.

In fact, this (the non-allocating tail-merge) is the specific case where
tail-merging is _more_ efficient time-wise than non-tail-merging.  All
other tail merges are either a wash or slightly less efficient time-wise
than their non-merging equivalents.  (but I am only discussing stack
allocations here; heap allocations would tend to be significantly less
efficient than stack-based allocations, and thus there is a larger time
cost to tail-merging in those situations than their respective non-merging
counterparts - this is the major reason why we don't subscribe to the
tail-merging-always philosophy).

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Duane Rettig
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4adyfws11.fsf@beta.franz.com>
To respond to my own article:

Duane Rettig <·····@franz.com> writes:

> Kent M Pitman <······@world.std.com> writes:
> 
> > But back to your original remark, I think my sense is that you can't (or
> > I don't offhand know a way to) execute a CL BLOCK in an interpreter without
======================================================^^^^^^^^^^^^^^^^^
> > either allocating stack or dynamically locking equivalent heap for the 
> > duration of the dynamic extent call.  
> 
> The third alternative is to not allocate space at all.  All architectures
> that I know of - even register-starved ones - have at least enough
> non-memory resources to complete a tail-call within the register set,
> without the need for any memory allocation at all for a stack frame.

I realize now that at least the paragraph above was considering the
interpreter, and my reply focussed on the compiler.  Perhaps the whole
part of this conversation was only about the interpreter, but reading
these articles over days I tend to lose that context.

And I have to admit, that it is much harder to implement an interpreter
to allocate block space only in registers than it is to compile a single
function to do so :-)

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <u1wnek7m.fsf@itasoftware.com>
Kent M Pitman <······@world.std.com> writes:

> ···@itasoftware.com writes:
> 
> > Kent M Pitman <······@world.std.com> writes:
> > 
> > > ···@itasoftware.com writes:
> > > 
> > > > Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> > > >
> > > > > As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> > > > > Thus, a specification of tail call elimination that doesn't do
> > > > > anything within a BLOCK would be perfectly useless.
> > > > 
> > > > This is not true, a BLOCK form does need to allocate storage on the
> > > > stack. 
> > > 
> > > Yes it does.  
> > [discussion of how a heap can implement a stack elided]
> 
> But nothing of what you said was specific to tail call elimination.
> The same technique suffices to implement specials, unwind-protects, etc.

No, because things like specials and unwind-protects will last for the
entire dynamic extent of the called function.  You *could* allocate
special-variable bindings in the heap, but they will persist until the
function that allocates them returns.  This defeats tail recursion.
However, if you allocate BLOCK bindings in the heap, the GC can reap
them as soon as they go out of scope.

The key here is that an unwind-protect or a special binding affects
the current stack frame, but a BLOCK simply creates a `pointer' into
the previous stack frame.  The current frame can be eliminated for
tail recursion without disturbing the frame that the BLOCK points to.
This is not the case for special variables or unwind-protects.

> I (perhaps overgenerously) took your remarks to mean "does not need to 
> allocate storage "in service of stack issues" and did not think you meant
> literally "does not need to allocate storage here at the big red X", which
> seemed a pointless remark.

It does not need to allocate storage that *must* persist until the
dynamic extent of the block is exited.

> Often a proponent of tail call elimination will say "I don't want stack
> allocated on every function call".  That person isn't going to be any happier
> with heap being allocated on every function call if that heap is not getting
> de-allocated until the final return happens, and that's the problem you
> have here.

No, the block can be reaped *before* the final return happens (unless,
of course, you have retained the binding until that point).  For
instance, let's suppose we write:

   (defun foo (x)
     (if (zerop x)
         nil
         (foo (-1+ x))))

And let us suppose that we aren't going to check to see that the block
FOO is referred to.  We allocate a binding in `block space' for FOO.
When we call FOO tail-recursively, we discard the current environment
(or evacuate it to the heap where it will be GC'd as there are no
longer any pointers to it), and simply jump to the beginning of FOO.

> The nature of an interpreter in this context seems to be that you
> haven't done total lexical analysis.  So I assume you can't gc parts
> of the function until you know they won't be used.  

Right, but unless you explicitly close over an environment, it can be
discarded as soon as the function exits. 

> Tail call elimination is meaningful not because it occurs in the stack or
> the heap but because when you finally decide to do it, you de-link it from
> the chain in the heap or you de-link+fast-gc it in the stack.  If you
> explain only the mechanism for putting the thing on the stack or putting it
> in the heap, but not the mechanism to de-link it, then it's still there.

I understand this.  It isn't a question of where the stuff is
allocated, it is a question of how much stuff needs to be allocated
*and retained* until the function exits.  In the case of a BLOCK,
there is nothing that *must* be retained.

> But back to your original remark, I think my sense is that you can't (or
> I don't offhand know a way to) execute a CL BLOCK in an interpreter without
> either allocating stack or dynamically locking equivalent heap for the 
> duration of the dynamic extent call.  

It won't last the entire duration, so the constant-space invariant is
preserved. 
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <pu7begez.fsf@itasoftware.com>
Kent M Pitman <······@world.std.com> writes:

> This is a bad way to explain this, I think, and that's part of the problem
> I was having.  

Sorry about that, I'll try to be clearer in the future.
From: Stephan H.M.J. Houben
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <slrn9tg64i.k24.stephan@pcrm.win.tue.nl>
On Wed, 24 Oct 2001 23:38:53 GMT, Kent M Pitman <······@world.std.com> wrote:
>···@itasoftware.com writes:
>
>> Juliusz Chroboczek <···@pps.jussieu.fr> writes:
>>
>> > As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
>> > Thus, a specification of tail call elimination that doesn't do
>> > anything within a BLOCK would be perfectly useless.
>> 
>> This is not true, a BLOCK form does need to allocate storage on the
>> stack. 
>
>Yes it does.  Unless you are prepared to disallow interpreted
>implementations.  And the important thing to understand is that the
>"you" is not you personally, but you with the weight of design
>responsibility upon your shoulders and the understanding that there
>are others who may not share your (jrm's) value system.

There is another way, although I don't think people will like it...
Key is the following sentence from CLHS.

  The block named name has lexical scope and dynamic extent.

The problem is the dynamic extent. Note that a similar construct as
`block' can be expressed in Scheme with call/cc, but the resulting
exit point has in Scheme lexical scope and indefinite extent. 
That's the key difference.

If we allowed the name to have lexical scope and indefinite extent,
then we could just go ahead on entry of every (implicit) block and
let the GC clean up the exit points. But in fact, CLHS says that:

  The consequences are undefined if an attempt is made to transfer
  control to an exit point whose dynamic extent has ended.

So apparently it is legal for an implementation to have blocks with
exit points with lexical scope and indefinite extent. In this case,
tail-call optimisation becomes possible even in an interpreter.

Of course, this probably means that all continuation for at least
the interpreter have to be heap-allocated, so it is arguably an
unreasonable burden to expect from implementations in general.
But it *can* be done.

Stephan
From: Francois-Rene Rideau
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87snc9dk6c.fsf@Samaris.tunes.org>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> That was my initial idea: only requiring implementations to perform
> tail-call elimination in a set of highly stylised ``easy'' situations.

> As Kent pointed out, however, in CL every DEFUN contains a BLOCK.
> Thus, a specification of tail call elimination that doesn't do
> anything within a BLOCK would be perfectly useless.
BLOCKs are absolutely no harm to merging calls in syntactic tail-position,
since, by definition, it's the last position in the block, whose value(s)
will be the one(s) returned by the block. Blocks do prevent some
(return-from) statements from being mergeable when they are in a
syntactic non-tail-position within the returned block, and the
continuation captured by an *intermediate* BLOCK escapes within
the called computation - for which a syntactic criterion also exists
(that does require full macro-expansion to determine, though).

> FR> 4- having slightly costlier representations for the continuation PDL.
> How does this help?  The problem is not made any easier by allocating
> the dynamic chain on the heap.
Sure it is. Because the usual (or specialized) GC mechanisms can now
handle the activation frames short-circuited by the tail-merging operation
that escaped into the tail computation - these frames exist as long
as needed, but no more - proper tail recursion.

Yours freely,

[ Fran�ois-Ren� �VB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
[  TUNES project for a Free Reflective Computing System  | http://tunes.org  ]
To people who ask what is the business model of Free Software, I reply:
"what is the business model of Free Trade?". -- Far�
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <873d40hmok.fsf@pps.jussieu.fr>
Francois-Rene Rideau:

FR> 4- having slightly costlier representations for the continuation PDL.

>> How does this help?  The problem is not made any easier by allocating
>> the dynamic chain on the heap.

FR> Sure it is. Because the usual (or specialized) GC mechanisms can now
FR> handle the activation frames short-circuited by the tail-merging operation
FR> that escaped into the tail computation - these frames exist as long
FR> as needed, but no more - proper tail recursion.

Aha -- I got it.  Sorry for being so slow.

Still, it doesn't help much.  A definition of tail-recursion
elimination that is not easily implementable in most stock CL
implementations is of little or no use.

By the way, it appears like X3J13 did consider the issue, and
deliberately decided to break things.  See the discussion in the
writeup for issue FLET-IMPLICIT-BLOCK.

                                        Juliusz
From: Francois-Rene Rideau
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87r8rjzr8r.fsf@Samaris.tunes.org>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> Francois-Rene Rideau:
> FR> 4- having slightly costlier representations for the continuation PDL.
>
> Still, it doesn't help much.  A definition of tail-recursion
> elimination that is not easily implementable in most stock CL
> implementations is of little or no use.
>
Sure, that's why I think the second option was best:
support tail-call-merging only when there isn't any escaping frame
in the tail computation - i.e. when the call is in syntactic tail position,
and/or is a return-from in non-tail position but without intermediate
continuation syntactically escaping into the called tail computation.

> By the way, it appears like X3J13 did consider the issue, and
> deliberately decided to break things.  See the discussion in the
> writeup for issue FLET-IMPLICIT-BLOCK.
This is an independent issue.
Once again, unless an *intermediate* block (the outermost is ok)
escapes in the tail computation of a particular tail-call,
which can be syntactically determined (since BLOCK is lexically scoped),
there is no reason why block should prevent tail-call-merging.
Admittedly, combining error-detection of RETURN-FROM to a dead
continuation with tail-call-merging can be slightly tricky,
but well, so is combining error-detection with dynamic-extent declarations.

Bis repetita docent.

[ Fran�ois-Ren� �VB Rideau | Reflection&Cybernethics | http://fare.tunes.org ]
[  TUNES project for a Free Reflective Computing System  | http://tunes.org  ]
Arbitrary limits to programs are evil,
particularly when they go either enforced or unenforced.
From: Pekka P. Pirinen
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <uhesneo3q.fsf@globalgraphics.com>
> While this is fine for the compiler, it cannot be
> implemented in an interpreter without walking (and therefore
> macroexpanding) every the code of every closure.  Is such an
> interpretation still an intepreter?

On the contrary, it's the best way to implement an efficient
interpreter (although efficiency is not that important in systems that
also have an incremental compiler).  Preprocessing the code (not just
the closures) allows the interpreter to resolve all scope issues at
definition time, rather than at each dereference.  Furthermore, since
the set of closed-over names is now known for each closure, one can
use shallow binding, instead of passing an environment list and doing
deep binding.

The interpreter can store the macroexpanded form of the code, instead
reexpanding each time, which can make an enormous difference.  Indeed,
one could preprocess the code into some more efficient form, and there
are Lisp and Basic interpreters that do this, but here we approach
byte-compilation territory, and you might well ask if it is an
interpreter still.  Nevertheless, if the language system also supports
a native-code compiler, people tend to call the other thing an
interpreter.
-- 
Pekka P. Pirinen
If it's spam, it's a scam.  Don't do business with net abusers.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwvgh3snji.fsf@world.std.com>
···············@globalgraphics.com (Pekka P. Pirinen) writes:

> > While this is fine for the compiler, it cannot be
> > implemented in an interpreter without walking (and therefore
> > macroexpanding) every the code of every closure.  Is such an
> > interpretation still an intepreter?
> 
> On the contrary, it's the best way to implement an efficient
> interpreter (although efficiency is not that important in systems that
> also have an incremental compiler).  Preprocessing the code (not just
> the closures) allows the interpreter to resolve all scope issues at
> definition time, rather than at each dereference.  Furthermore, since
> the set of closed-over names is now known for each closure, one can
> use shallow binding, instead of passing an environment list and doing
> deep binding.

You're entitled to implement execution however you like, and the spec
does not require you to justify the technique you use for EVAL or COMPILE.
HOWEVER,...

Could you consult the definition of "minimal compilation" in 3.2.2.2 and
tell me why this process you describe is not "compilation"?

My belief is that CL intends to permit an interpreter, that is, a
device which does not do the thing you suggest, for whatever reason.

I do not define interpretation, nor do I believe the implementors
meant to, as "execution by traversal of a tree datastructure" nor
compilation by "execution of machine instructions".  Rather, I believe
the terminological divide is "early (and definitionally consistent)
codewalking" vs "late and (possibly definitionally inconsistent)
codewalking". 

[The (in)consistent issue has to do with the fact that a dynamic
language can redefine itself, so an interpreted piece of code may not
be syntaxed all at one time, thus having some parts of it see the
effect of some runtime definitions that others do not, depending on
execution patterns.  I'm not advocating programs that depend on this;
I more observe it because programs need to be sensitive to assumptions
that this won't happen.  But what I am saying is that late decision making
is the very essence of interpretation.]
From: David Feuer
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3C1B5A51.9F3A8F2@brown.edu>
"Pekka P. Pirinen" wrote:

> The interpreter can store the macroexpanded form of the code, instead
> reexpanding each time, which can make an enormous difference.  Indeed,
> one could preprocess the code into some more efficient form, and there
> are Lisp and Basic interpreters that do this, but here we approach
> byte-compilation territory, and you might well ask if it is an
> interpreter still.  Nevertheless, if the language system also supports
> a native-code compiler, people tend to call the other thing an
> interpreter.

Don't know what the standard view of this is, but according to Shriram
(see c.l.s), a compiler is a function from a program to a program and an
interpreter is a function from a program to an "answer". If an
interpreter internally compiles the code, that doesn't mean it's not an
interpreter.
-- 
/Times-Bold 40 selectfont/n{moveto}def/m{gsave true charpath clip 72
400 n 300 -4 1{dup 160 300 3 -1 roll 0 360 arc 300 div 1 1 sethsbcolor
fill}for grestore 0 -60 rmoveto}def 72 500 n(This message has been)m
(brought to you by the)m(letter alpha and the number pi.)m(David Feuer)
m(···········@brown.edu)m showpage
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvzo4ki2dv.fsf@conquest.OCF.Berkeley.EDU>
David Feuer <···········@brown.edu> writes:

> "Pekka P. Pirinen" wrote:
 [ Wow, this was 2 months ago! ]
> > The interpreter can store the macroexpanded form of the code, instead
> > reexpanding each time, which can make an enormous difference.  Indeed,
> > one could preprocess the code into some more efficient form, and there
> > are Lisp and Basic interpreters that do this, but here we approach
> > byte-compilation territory, and you might well ask if it is an
> > interpreter still.  Nevertheless, if the language system also supports
> > a native-code compiler, people tend to call the other thing an
> > interpreter.
> 
> Don't know what the standard view of this is, but according to Shriram
> (see c.l.s), a compiler is a function from a program to a program and an
> interpreter is a function from a program to an "answer". If an
> interpreter internally compiles the code, that doesn't mean it's not an
> interpreter.

That sounds like a definition of "evaluator".  I think there's enough
of a difference between doing

  (eval some-form)

and

  (funcall (compile nil `(lambda () ,some-form)))

in CLISP, for example, that it's useful to distinguish between the two
cases.  They both evaluate the program, but they do it in different ways.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <7ktn2210.fsf@itasoftware.com>
Kent M Pitman <······@world.std.com> writes:

> But it doesn't say that.
> 
> 
>  (defun bar (x y) (funcall x (1+ y)))
>  (defun foo (y)
>    (let ((x #'(lambda (x) (return-from foo x)))) ;<-- first RETURN-FROM
>      (return-from foo ;<-- second RETURN-FROM
>        (bar x y))))
> 
> You might think the second RETURN-FROM was requesting tail-calling, but
> it simply isn't.  First, it's in no more of a tail call position than it
> was if you omitted the RETURN-FROM.  Second, it cannot be tail called 
> because there is pending state between the RETURN-FROM and the BLOCK 
> that cannot be disposed of until BAR has finished executing.

What state would that be?  It looks like a tail-call to me.
From: Jeff Mincy
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <snccr5xa.fsf@antarres.muniversal.com>
Erik Naggum <····@naggum.net> writes:

> * Erik Naggum
> | Since you could wrap the entire function body in a return-from, I think
> | this would not be helpful.  Figuring out whether a function call is in a
> | tail-call-mergable "position" is not helped by such use of this form.
> 
> * Frode Vatvedt Fjeld
> | I think you misunderstood me.  I'll try to spell out what I meant more
> | clearly.
> | 
> |   (defun foo (x y)
> |     (when x
> |       (return-from foo (bar)))
> |     (if y (baz)
> |       (return-from foo (bax))))
> 
>   Well, _I_ think I understand your proposal better ...


There seems to be a certain amount of cross communication here - 

Frode's point is that (return-from fn <form>) can be used to find tail
calls because <form> is in tail call position.

Eric's point is more of a comment that that doesn't really help
because you still have to walk down <form> to find the real code in
tail call position.

Note that when looking at return-from, you also need to make sure that
the block 'foo' being returned from is the outermost block of the
function 'foo'.  You could have something goofy like nested blocks:

(defun foo ()
  ..
  (if (block foo  ... (return-from foo ...) ...)
      (yes-foo)
      (no-foo)))


BTW, I found some notes about tail call transforms.  Obviously, once
upon a time I must have cared about this, at some point...
I think I cared about tail calls only to the extent that it could be
used to make silly benchmarks like tak run faster.

;; With tak
(defun tak (i j k)
  (if (< x y)
      z
      (tak (tak (1- i) j k)
	   (tak (1- j) k i)
	   (tak (1- k) i j))))

;; The outer call to tak is in self tail call.
;; Transform this to the following, which gets rid of 1/4 of the function calls to tak.

(defun tak (i j k)
  (block tak
    (tagbody
    TOP
      (if (< x y)
	  (return-from tak z)
	  (progn
	    (psetq i (tak (1- i) j k)
		   j (tak (1- j) k i)
		   k (tak (1- k) i j))
	    (go TOP))))))

-jeff
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3212695116153606@naggum.net>
* Jeff Mincy
| Note that when looking at return-from, you also need to make sure that
| the block 'foo' being returned from is the outermost block of the
| function 'foo'.

  Good point.  As we investigate this, more and more context turns out to
  to be required to determine whether we have a "final" function call.  I
  think this shows that the complexities of real life programming, which is
  what Common Lisp is all about making less complex, are such that you end
  up with a lot fewer opportunities for tail-call merging than in languages
  made for simpler worlds.  It is actually quite interesting to see how
  simple the functions have to be in order that, e.g., Allegro CL, manages
  to perform tail-call merging and how little of the useful stuff in Common
  Lisp can be used without making it too hard for the compiler to do that.

| I think I cared about tail calls only to the extent that it could be used
| to make silly benchmarks like tak run faster.

  :)

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-788976.13482522102001@news.paradise.net.nz>
In article <················@naggum.net>, Erik Naggum <····@naggum.net> 
wrote:

> * Jeff Mincy
> | Note that when looking at return-from, you also need to make sure that
> | the block 'foo' being returned from is the outermost block of the
> | function 'foo'.
> 
>   Good point.  As we investigate this, more and more context turns out to
>   to be required to determine whether we have a "final" function call.

This is hardly surprising.  At heart, the idea of tail-call merging 
comes from the observation that if the compiler generates code like...

  jsr foo
  ret

... then it can be replaced with:

  jmp foo

It's fundamentally a low level implementation thing, similar to deciding 
which value should go into which register, or how to lay out the stack 
frame, or when to CONS or to garbage collect.  The user should be able 
to trust the compiler to do it when it can, but you can't always know 
exactly when that is without either a deep understanding of both the 
language and your compiler, or by trial and error.

I guess that's not pleasing to purists, but it's good enough for me.

-- Bruce
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3212706829871008@naggum.net>
* Bruce Hoult
| This is hardly surprising.

  But it obviously is, to _so_ many people.

| At heart, the idea of tail-call merging  comes from the observation that
| if the compiler generates code like...
| 
|   jsr foo
|   ret
| 
| ... then it can be replaced with:
| 
|   jmp foo

  I think this is a very good summary of the _actual_ issue.  However, if
  the language mandates that it be "properly tail-recursive", there will be
  a lot more cases like that than if it does not mandate it, and lots of
  things that would make it impossible to do it, will be taken out of the
  language.  Given that we find cases like this in our compiled code and
  can exploit the existing stack framework, wanting tail-call merging is
  harmless to the language.  Given that requesting more tail-call merging
  on an abstract level has far-reaching consequences for the rest of the
  language, this means that the request is not just a desire to change
  these two instructions into one where they occur, but an implicit desire
  for all the other changes that come with it.

| I guess that's not pleasing to purists, but it's good enough for me.

  Me too.  I am perfectly happy with Allegro CL's way of separating the
  mergeability of tail-calls to self and to non-self.  Since a tail-call to
  self can easily be made into a jump to a _different_ location than one
  would normally jump to enter a function, not the least because you know
  the exact argument list and can do everything you may need to do arrange
  for optional and keyword arguments and what-not before you jump to the
  real code, effectively only setting the lambda variables to new values,
  the complexity of unwinding the stack and all that evaporates, the stack
  frame can be reused, and (almost) all the complexity of a non-self tail-
  call is simply not there.  Nonetheless, Allegro CL does an impressive job
  of merging non-self calls, too.  It turns out to be quite useful in CLOS.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <y9m3zrbm.fsf@itasoftware.com>
Bruce Hoult <·····@hoult.org> writes:

> In article <················@naggum.net>, Erik Naggum <····@naggum.net> 
> wrote:
> 
> > * Jeff Mincy
> > | Note that when looking at return-from, you also need to make sure that
> > | the block 'foo' being returned from is the outermost block of the
> > | function 'foo'.
> > 
> >   Good point.  As we investigate this, more and more context turns out to
> >   to be required to determine whether we have a "final" function call.
> 
> This is hardly surprising.  At heart, the idea of tail-call merging 
> comes from the observation that if the compiler generates code like...
> 
>   jsr foo
>   ret

This only works if the return address is stored on the top of the
stack.  If it is pushed *before* the arguments (as it is in many
implementations), then you have to be clever.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <874roqs6fs.fsf@pps.jussieu.fr>
Bruce Hoult:

BH> This is hardly surprising.  At heart, the idea of tail-call merging 
BH> comes from the observation that if the compiler...

BH> It's fundamentally a low level implementation thing [...]

However, unlike the other examples you give, *reliable* tail-call
elimination enables certain programming techniques that are otherwise
impossible to use.

I don't care whether something can be described as a ``low-level
implementation thing''.  The only test I'm interested in is whether or
not it increases the expressiveness of the language.

The keyword I'd like you to note is /reliable/.  This should not be
taken as a statement of value (which is why I avoid using /proper/),
but rather as the statement that the implementation guarantees
tail-call elimination in a well-defined set of situations, so that the
programmer can /rely/ on it.

BH> The user should be able to trust the compiler to do it when it
BH> can, but you can't always know exactly when that is without either
BH> a deep understanding of both the language and your compiler, or by
BH> trial and error.

I must disagree.  Either the programmer can write his code in a (pos-
sibly highly stylised) way that enables him to rely on tail-call eli-
mination being performed, or else he cannot use the said techniques.
Call me a purist if you will, but I really don't see a middle way.

Erik has cited the example of ACL.  Last time I checked, ACL did not
document the circumstances under which it performs tail-call elimi-
nation.  Thus, from the programmer's point of view, ACL's tail-call
elimination is anything but reliable.

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3212936997655473@naggum.net>
* Juliusz Chroboczek
| The keyword I'd like you to note is /reliable/.  This should not be taken
| as a statement of value (which is why I avoid using /proper/), but rather
| as the statement that the implementation guarantees tail-call elimination
| in a well-defined set of situations, so that the programmer can /rely/ on
| it.

  You really need a language definition that provides what you want.  There
  is at least one language definition that provides it: Scheme.  I think
  you should now go and bother the Scheme people with all the things you
  miss in their language from Common Lisp and leave people here alone.

  There is unfortunately no way you can get what you want in Common Lisp.
  It is politically impossible.

| Erik has cited the example of ACL.  Last time I checked, ACL did not
| document the circumstances under which it performs tail-call elimination.
| Thus, from the programmer's point of view, ACL's tail-call elimination is
| anything but reliable.

  Oh, come on.  There is no way you will be satisfied, anyway, so there is
  no value to be gained from catering to your needs.  Just use Scheme and
  reach out and bug someone in that community about their single namespace
  and numerous other mistakes.  It will be about as fruitless as your bogus
  desires here, but at least you will have that tail-call merging you want.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  The purpose of computing is insight, not numbers.   -- Richard Hamming
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87u1wgg7kv.fsf@pps.jussieu.fr>
> The keyword I'd like you to note is /reliable/.  This should not be taken
> as a statement of value (which is why I avoid using /proper/), but rather
> as the statement that the implementation guarantees tail-call elimination
> in a well-defined set of situations, so that the programmer can /rely/ on
> it.

[...]

> Erik has cited the example of ACL.  Last time I checked, ACL did not
> document the circumstances under which it performs tail-call elimination.
> Thus, from the programmer's point of view, ACL's tail-call elimination is
> anything but reliable.

EN>   You really need a language definition that provides what you want.

No.  I need a document that the ACL implementors can refer to, and
write in their documentation ``In compiled code, ACL performs
tail-call elimination in all the situations mandated by document FOO.
Additionally, ACL will perform tail-call elimination in the following
situations: ...''

The Common Lisp standard does not define a way for the user to extend
the behaviour of streams; does that mean that the Gray Streams
proposal is useless?  Or would you agree with me that it provides a
useful starting-point for vendors, and possibly even a useful starting
point for a future standard?

                                        Juliusz

P.S. By the way, you're wrong.  It's not Scheme exposure I'm suffering
     of, it's my ML background showing.
From: Duane Rettig
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4hesfg1i4.fsf@beta.franz.com>
[I've avoided posting very many articles on this thread, primarily
because I've been neck-deep in our 6.1 release, which is imminent,
and secondarily because my wife and I disappear this Saturday for
an almost-month-long vacation, and I haven't wanted to start
something I couldn't finish.  Besides, most of the issues regarding
tail-merging have been touched on in this thread by able users and
implementors or ex-implementors.  So please forgive this posting only
three days before I duck out for the month; I'll be back... ]


Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> > The keyword I'd like you to note is /reliable/.  This should not be taken
> > as a statement of value (which is why I avoid using /proper/), but rather
> > as the statement that the implementation guarantees tail-call elimination
> > in a well-defined set of situations, so that the programmer can /rely/ on
> > it.
> 
> [...]
> 
> > Erik has cited the example of ACL.  Last time I checked, ACL did not
> > document the circumstances under which it performs tail-call elimination.
> > Thus, from the programmer's point of view, ACL's tail-call elimination is
> > anything but reliable.
> 
> EN>   You really need a language definition that provides what you want.
> 
> No.  I need a document that the ACL implementors can refer to, and
> write in their documentation ``In compiled code, ACL performs
> tail-call elimination in all the situations mandated by document FOO.
> Additionally, ACL will perform tail-call elimination in the following
> situations: ...''

The best way for you to get such a document, if you're supported, is
to email ····@franz.com and ask.  I must warn you, though, that the
response you receive is likely to start with our standard internals
disclaimer, like "<blah> is internal functionality which is undocumented,
its use is unsupported, and may change in a future release or with
patches ..." so unfortunately this will not help you as you have clearly
stated several times that you want _reliable_ behavior.  (I don't like
the word reliable as used here, even though I understand your usage,
because traditionally it is used as a qualitative term, and I'd prefer
either the term "tied down" or simply "specified" - I'd much rather
associate the name Allegro CL as being "unspecified" or "not tied down"
in a particular area, than to associate it with being "unreliable" in
that area).

Now, why would we want not to be "tied down" to a particular set of
implementation decisions?  Consider the following function:

(defun foo ()
  (declare (optimize speed (debug 0)))
  (let ((temp-buffer (make-array 1000)))
     (bar temp-buffer)))

The call to bar is obviously a candidate for tail merging.  And indeed,
when compiled in Allegro CL, the stack is deallocated and a simple
jump to bar is performed, rather than a jump-subroutine construct.

Now, consider the following modification:

(defun foo ()
  (declare (optimize speed (debug 0)))
  (let ((temp-buffer (make-array 1000)))
     (declare (dynamic-extent temp-buffer))
     (bar temp-buffer)))

In the simplest of implementation strategies, there is a potential
conflict here.  Both tail merging and the dynamic-extent declaration
are optional to the implementor (the compiler is allowed to ignore
all declarations except for special and notinline) and thus there are
four compilations which can be done:
 1. Do neither stack allocation of the array nor tail merging.
 2. Do stack allocation but not tail merging.
 3. Do tail merging but not stack allocation.
 4. Do both stack-allocation and tail merging.

It is #4 which poses the problem here.  If both optimizations are done,
then the stack frame which housed the array is deallocated before the
call to bar is made, and so bar's argument is garbage.

A tail merge optimization makes the delineation of scope a little fuzzy.
Does the array really have a scope only within foo?  Or is the very fact
that it is used as an argument to a tail-merged function cause it to be
effectively "returned", thus requiring that it be evacuated to the heap,
or otherwise preserved, possibly on the stack?  Or should strict
adherence to the spirit of the declarations be made, and one or the other
optimizations be ignored, due to a conflict?  If so, which one?

These are all very valid questions which each implementation must
continually answer.  And because implementation decisions may change over
the life of a product, it may be that one or more of these implementations
are in effect within different versions of the same product.  Thus, if
a user were to count on such implementational details for the operation
of his program, his program would break when the impolementation changes.

Thus, if we as a vendor were to officially document when we do such
tail-merging, we would then have to provide compatibility modes whenever
we were to switch implementation decisions, because making the change
incompatibly is not acceptable - we always try to minimize incompatibilities
when changing implementations.

Note that I think that almost any tail merging implementation is
possible, based on particular prioritizations and compromises of
various optimizations, and we are _always_ open to contracts - if
you care enough about a particular guaranteed mode of compilation
to lay down enough money for a contract, we would be happy to
accomodate you if we agree that it is a useful optimization.  I would
have put a smiley here, because I'm not really asking you to pay extra
money for a pet feature, however that is often _precisely_ how other
customers get particular changes to and/or guarantees from our
implementation, as we mutually agree to them.

> The Common Lisp standard does not define a way for the user to extend
> the behaviour of streams; does that mean that the Gray Streams
> proposal is useless?  Or would you agree with me that it provides a
> useful starting-point for vendors, and possibly even a useful starting
> point for a future standard?

This is a great example of how we went the other way.  We implemented Gray
Streams almost 10 years ago, and documented it well (including a few of
its weaknesses).  But because we had exported, documented, and supported
it, and since users might have thus used it (in this case, _many_ users
have taken advantage of Gray streams extensions) we had no choice (based
on our own policy of backward compatibility) than to continue to offer
Gray streams as compatibly as possible, when we moved to a new streams
implementation, even though we were not required by the Ansi standard
to retain Gray Streams.

So, bottom line, based on our practice of supporting what we document,
you are asking us to tie ourselves down to an implementation, and to
thus force compatibility modes if the implementation ever changes.

> P.S. By the way, you're wrong.  It's not Scheme exposure I'm suffering
>      of, it's my ML background showing.

I suffer from Common Lisp exposure, which is not a Bad Thing in this
newsgroup, since it is the common language we speak here.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3213536963677093@naggum.net>
* Erik Naggum
| You really need a language definition that provides what you want.

* Juliusz Chroboczek
| No.  I need a document that the ACL implementors can refer to, and write
| in their documentation ``In compiled code, ACL performs tail-call
| elimination in all the situations mandated by document FOO.
| Additionally, ACL will perform tail-call elimination in the following
| situations: ...''

  Have you offered to pay Franz Inc to provide you with this document?
  Have you in fact done _anything_ to motivate anyone to do what you think
  is a good idea?  You only demand, and offer nothing in return, not even
  the hope of not hearing more of your stupid whining.

| P.S. By the way, you're wrong.  It's not Scheme exposure I'm suffering
|      of, it's my ML background showing.

  You mistake me for caring where you go when you leave c.l.l alone.  All I
  suggest is that you go over to comp.lang.scheme, which I do not read,
  where you will have much more "success" with your tail-call trolling, but
  then I expect that you will bother them with numerous things you "want"
  after you get what you say you want here.

///
-- 
  Norway is now run by a priest from the fundamentalist Christian People's
  Party, the fifth largest party representing one eighth of the electorate.
-- 
  Carrying a Swiss Army pocket knife in Oslo, Norway, is a criminal offense.
From: Tim Bradshaw
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <ey3lmi0ay6m.fsf@cley.com>
* Juliusz Chroboczek wrote:

> The keyword I'd like you to note is /reliable/.  This should not be
> taken as a statement of value (which is why I avoid using /proper/),
> but rather as the statement that the implementation guarantees
> tail-call elimination in a well-defined set of situations, so that the
> programmer can /rely/ on it.

Somewhere way back in this thread I posted an article about this, I'll
repeat what I said.  If you want reliable, portable tail-call
elimination in CL you need to do two things.  Firstly you need a
standards-quality description of what a conforming implementation
should provide.  Secondly you need to get implementors to agree to
adhere to this description.  Ideally you need to do this through some
kind of standards-like process because it's important that you don't
end up in a situation where you `standardise' in a way that
disenfranchises one or more implementations, so you need those
implementors to be able to veto such a proposal.

I encourage people who want this to stop wasting time posting on
usenet and *do it*.  Maybe it's hard (I think it is, very hard), but
you won't know unless you put your money where your mouth is and try
and get what you want to happen to happen, and you can't expect other
people to do that for you.

Note in the above I have tried hard not to imply that this is
something that has to happen through some formal standardisation
process like ANSI.  That's one way, but I think there are probably
others which are less heavyweight and painful, and thus more likely to
succeed.

--tim
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-F85230.13380925102001@news.paradise.net.nz>
In article <··············@pps.jussieu.fr>, Juliusz Chroboczek 
<···@pps.jussieu.fr> wrote:

> Bruce Hoult:
> 
> BH> This is hardly surprising.  At heart, the idea of tail-call merging 
> BH> comes from the observation that if the compiler...
> 
> BH> It's fundamentally a low level implementation thing [...]
> 
> However, unlike the other examples you give, *reliable* tail-call
> elimination enables certain programming techniques that are otherwise
> impossible to use.

No, that's not so.  Garbage collection was also on the list and is a 
prime example of something that allows programming techniques not 
otherwise viable.


> BH> The user should be able to trust the compiler to do it when it
> BH> can, but you can't always know exactly when that is without either
> BH> a deep understanding of both the language and your compiler, or by
> BH> trial and error.
> 
> I must disagree.  Either the programmer can write his code in a (pos-
> sibly highly stylised) way that enables him to rely on tail-call eli-
> mination being performed, or else he cannot use the said techniques.
> Call me a purist if you will, but I really don't see a middle way.

I'm all for the existence of "highly stylised" situations in which tail 
call optimization is guaranteed to be done.  I think that's very 
desirable, even if the conditions for the guarantee are *very* tight.

But then there is a very broad range of situations in which a good 
compiler might do tail-call optimization while a poorer one might not.  
I don't have a problem with that.  There is no guarantee in a C++ 
compiler about which variables will go into registers and which won't 
but every good programmer knows that your variable is a lot more likely 
to end up in a register if it's declared as locally as possible, is of 
the same size as a register, is not a structured value, and if you never 
take the address of it.

-- Bruce
From: Rob Warnock
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9rgpc7$3p18j$1@fido.engr.sgi.com>
About a week ago, Bruce Hoult <·····@hoult.org> wrote:
+---------------
| Erik Naggum <····@naggum.net> wrote:
| > As we investigate this, more and more context turns out to
| > to be required to determine whether we have a "final" function call.
| 
| This is hardly surprising.  At heart, the idea of tail-call merging 
| comes from the observation that if the compiler generates code like...
|   jsr foo
|   ret
| ... then it can be replaced with:
|   jmp foo
+---------------

Ooh!! Severe PDP-10 assembly-language flashback! (Or trivia attack,
whatever.) The DEC PDP-10 had several different subroutine call
instructions (more than any other machien I've worked on, actually),
but the one used most commonly for deep call chains was "PUSHJ P,FOO"
(push the current PC onto the stack P and jump to FOO). The corresponding
return was "POPJ P,0". The PDP-10 also had many different jump instructions
(way too many!), but the fastest was "JRST 0, FOO" (jump and restore,
but "JRST 0," didn't actually restore anything!). Assembly-language
programmers [note: almost all systems software on the PDP-10 was written
in assembler!] would routinely do tail-call merging manually, but to
avoid confusing other people they would document it in the code by using
a macro named "PJRST" (which just expanded to "JRST"). So the idiom for
the above merge in MACRO-10 syntax was:

	PUSHJ	P,FOO
	POPJ	P,0

becomes:

	PJRST	FOO

More complex forms were common as well, such as statically curried
functions [although nobody called it that back then]. Suppose you
had a function FOO that took three arguments in registers T0, T1,
and T2, but there were a lot of calls for which the third arg had a
particular value, say, "34" or "53". Then you might see specialized
routines such as these [note that the second PJRST is commented out!]:

FOO34:	MOVEI	T2, 34	; T0, T1 already set
	PJRST	FOO

FOO53:	MOVEI	T2, 53	; T0, T1 already set
	;PJRST	FOO	; (fall through)

FOO:	...
	...
	POPJ	P,0

When passing args on the stack, though, you had the same problems
merging tail calls as discussed in the rest of this thread. Yeah,
there were occasionally some attempts to do it, but they were mostly
unsatisfying. [That is, "PUSH P,(P)" does a "dup" of the top of the stack,
and you could store the third arg where the return PC used to be, but
then how do you get the original caller to remove the extra arg when
FOO returns directly to him?]


-Rob

-----
Rob Warnock, 30-3-510		<····@sgi.com>
SGI Network Engineering		<http://www.meer.net/~rpw3/>
1600 Amphitheatre Pkwy.		Phone: 650-933-1673
Mountain View, CA  94043	PP-ASEL-IA

[Note: ·········@sgi.com and ········@sgi.com aren't for humans ]  
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2helnw6qp7.fsf@dslab7.cs.uit.no>
Jeff Mincy <····@delphioutpost.com> writes:

> Note that when looking at return-from, you also need to make sure
> that the block 'foo' being returned from is the outermost block of
> the function 'foo'.

Yes this would be an issue, but no big challenge for the compiler to
figure out.

Revised idea: Merge operators return-from and funcall into tailcall,
something like this:

  (defmacro tailcall (function &rest arguments)
    (return-from <defun-block> (funcall ,function ,@arguments)))

Maybe the block-name should be an explicit argument too. A tailcall
compiler-macro would now be able to insert some lower-level special
operator for tail-call merging.

-- 
Frode Vatvedt Fjeld
From: Jeff Mincy
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bsizres8.fsf@antarres.muniversal.com>
Frode Vatvedt Fjeld <······@acm.org> writes:

> Jeff Mincy <····@delphioutpost.com> writes:
> 
> > Note that when looking at return-from, you also need to make sure
> > that the block 'foo' being returned from is the outermost block of
> > the function 'foo'.
> 
> Yes this would be an issue, but no big challenge for the compiler to
> figure out.

Well, if the compiler can figure out what forms are inner or outer
then the compiler can probably also figure out that forms are in
tail call position.  Both require a full code walker that passes
information down.

> Revised idea: Merge operators return-from and funcall into tailcall,
> something like this:
> 
>   (defmacro tailcall (function &rest arguments)
>     (return-from <defun-block> (funcall ,function ,@arguments)))
> 
> Maybe the block-name should be an explicit argument too. A tailcall
> compiler-macro would now be able to insert some lower-level special
> operator for tail-call merging.

There is no way that you can ensure that the return-from <defun-block>
is returning from the correct block.  You can have multiple blocks
with the same name.

I suppose that you could do it with some sort of gensym block tag and
a macrolet definition of your 'tailcall' macro that uses the correct
gensym tag.  You would have to do this for every defun.  Yuck.

-jeff
From: Frode Vatvedt Fjeld
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <2hadyj7pgs.fsf@dslab7.cs.uit.no>
Jeff Mincy <····@delphioutpost.com> writes:

> Well, if the compiler can figure out what forms are inner or outer
> then the compiler can probably also figure out that forms are in
> tail call position.

But that is (if I'm not much mistaken) a relatively easy problem, and
not the problem I'm trying to solve (to the extent there is an actual
problem, I'm not quite sure..:) The problem is letting the programmer
express "I don't care what the compiler usually does for
tail-call-merging, but this particular function call I really, really
want the compiler to tail-call-merge".

> There is no way that you can ensure that the return-from
> <defun-block> is returning from the correct block.  You can have
> multiple blocks with the same name.

It would (most likely) be easy for the compiler to figure out
<defun-block> (not the block-name, but the block per se). I did not
intend for tailcall to actually be implemented as a macro, I just
wrote it that way to illustrate the syntax.

Or, if the block-name was to be explicitly passed, it would be the
programmers task to figure out which block is bound to which
block-name.

-- 
Frode Vatvedt Fjeld
From: Duane Rettig
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <4wv1n1w1l.fsf@beta.franz.com>
Frode Vatvedt Fjeld <······@acm.org> writes:

> Jeff Mincy <····@delphioutpost.com> writes:
> 
> > Well, if the compiler can figure out what forms are inner or outer
> > then the compiler can probably also figure out that forms are in
> > tail call position.
> 
> But that is (if I'm not much mistaken) a relatively easy problem, and
> not the problem I'm trying to solve (to the extent there is an actual
> problem, I'm not quite sure..:) The problem is letting the programmer
> express "I don't care what the compiler usually does for
> tail-call-merging, but this particular function call I really, really
> want the compiler to tail-call-merge".

The way we do it internally in Allegro CL is something like

(defun foo ( ... )

    ...

    (excl::compiler-let ((comp:tail-call-non-self-merge-switch t))
      (bar ...))

    ...
    
    )

[Of course, compiler-let is available externally, exported from
the cltl1 package, but I always stay away from referencing that
package so as not to force the cltl1 module into the lisp, since
it changes some character semantics when it is loaded]

The above form tells the compiler just what you are suggesting, but
it changes no program structure, so that if the call to bar is not
in tail-position, it will not be merged (and more importantly, it will
not change the call by implying a return-from if there should be
more to do in foo after bar returns).

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Jens Mander
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9r8jm0$v4$1@stu1id2.ip.tesion.net>
test - delme
From: felix
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bd31493$0$5411$9b622d9e@news.freenet.de>
Roger Corman wrote in message <···················@news.callatg.com>...
>
>I would think of Chicken as a stack-less architecture, which takes advantage of
>C stack management code generation to implement the first generation of the
>memory manager.  I understand ML is also stack-less (is that right?). I think
>that is very interesting, and understand from Baker's papers that you can get
>very good performance with such a system. I especially like how it so blows the
>minds of most programmers. 


There are ML implementations (most notably SML/NJ) that use a similar
approach (CPS + pure heap-allocation), but generate machine-code instead
of C.

>Although to be entirely platform-independent the stack checks are required.
>However, I would think on a particular platform you could get rid of them by
>instead using the virtual memory system. You set the page above the stack (or
>nursery, in this case) to read-only, and then catch and handle the exception.
>This should help things, shouldn't it? The Win32 platform does this with the
>stack automatically, so code never needs to check for stack overflow.


This is right. The problem one faces is that the stack-overflow exception is
only allowed at certain points in the control-flow, because we have to
save the current state of the program somewhere before we initiate
a minor (stack -> heap) garbage collection. One could scan the stack
for live data, but this would be inaccurate and we would get the same
problems as conservative GCs have. Mostly it is known how much is to
be allocated, so the stack-check boils down to at most 2 machine-instructions.
Another point is C code called from Scheme:
it should be allowed to grow the stack by more than the nursery-size.
For example if we set the nursery to 12k, then foreign C code called should
still be able to exceed that amount.


cheers,
felix
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87elnus7zm.fsf@pps.jussieu.fr>
Roger Corman:

RC> I understand ML is also stack-less (is that right?).

To be accurate, one implementation (SML/NJ) of one of the dominant ML
dialects (SML) allocates activation frames on the heap.

There is nothing in either of the ML dialects (SML and Caml) that
mandates or even encourages heap allocation of call frames (call/cc is
an SML/NJ extension, and not part of the language).  Both Caml
implementations rely heavily on the stack, although they don't use the
familiar call/return model (or at least didn't the last time I checked).

                                        Juliusz
From: Fred Gilham
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <u73d487vgu.fsf@snapdragon.csl.sri.com>
This thread has been going on a long time and nobody has mentioned CMU
Lisp, which has been doing tail recursion optimization for a long
time.  I'm wondering what people think of its implementation.

CMU Lisp will also use a `local call' mechanism to (among other
things) optimize recursive calls that aren't tail recursive.  I'm not
sure whether or not the tail recursion optimization is a special case
of local call.


The documentation mentions the following cases where tail recursion
optimization isn't done:

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

Tail Recursion Exceptions

Although Python is claimed to be "properly" tail-recursive, some might dispute
this, since there are situations where tail recursion is inhibited:
     
   * When the call is enclosed by a special binding, or
     
   * When the call is enclosed by a `catch' or `unwind-protect', or
     
   * When the call is enclosed by a `block' or `tagbody' and the block
     name or `go' tag has been closed over.

These dynamic extent binding forms inhibit tail recursion because they
allocate stack space to represent the binding.  Shallow-binding
implementations of dynamic scoping also require cleanup code to be
evaluated when the scope is exited.

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


-- 
Fred Gilham                                      ······@csl.sri.com
Ah, the 20th century, when the flight from reason crash-landed into
the slaughterhouse.  --- James Ostrowski
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87y9lsg7vr.fsf@pps.jussieu.fr>
FG> This thread has been going on a long time and nobody has mentioned CMU
FG> Lisp, which has been doing tail recursion optimization for a long
FG> time.

As far as I am aware, CMU Common Lisp eliminates tail calls in all the
situations I've described in my pre-proposal (modulo the bug with
BLOCK), with the following exceptions:

 - when safety > 0, if the return values of a function are known,
   CMUCL will introduce a check around the last form, thus disabling
   tail call elimination;

 - when safety > 0, THE will always generate a check, thus possibly
   disabling tail-call elimination.

CMUCL also has an EXT:TRULY-THE special form which behaves like THE
does in my pre-proposal.

BH-CLISP does behave exactly as my pre-proposal mandates.  Therefore,
in BH-CLISP the THE special form does not always generate a check;
there is an EXT:ETHE special form that does always generate a check.

                                        Juliusz
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-10B6B5.16094720102001@news.paradise.net.nz>
In article <·························@news.freenet.de>, "felix" 
<············@freenet.de> wrote:

> Direct-style translation into C (as systems like Bigloo, Stalin or KCL 
> and it's
> children) will normally generate more efficient code, but of course have 
> awful problems
> with call/cc and/or tail-rec. The trampoline (driver-loop) approach
> that is used for example in Gambit is somehow in between those two
> extremes: I made the experience that it is sometimes able to outperform
> direct style compilers in fixnum-/unsafe-mode, but is often slower than 
> Chicken when the declarations are removed.

Hi Felix,

Something I meant to mention before but didn't is the ability to use 
standard C tools such as gdb and gprof (debugging and code profiling, 
for the non-Un*x types).

Direct-style translation to C makes it pretty straightforward to use 
standard tools.  Trampoline-style such as in Gambit will surely totally 
distroy the usefulness of gprof -- the time in each function might not 
be too bad, but the call tree stuff will be useless.

How do you get on with Chicken?  I imagine it's somewhere in between.

-- Bruce
From: felix
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bd31492$0$5411$9b622d9e@news.freenet.de>
Bruce Hoult wrote in message ...
>
>Hi Felix,

Howdy!

>
>Something I meant to mention before but didn't is the ability to use 
>standard C tools such as gdb and gprof (debugging and code profiling, 
>for the non-Un*x types).
>
>Direct-style translation to C makes it pretty straightforward to use 
>standard tools.  Trampoline-style such as in Gambit will surely totally 
>distroy the usefulness of gprof -- the time in each function might not 
>be too bad, but the call tree stuff will be useless.
>
>How do you get on with Chicken?  I imagine it's somewhere in between.
>


As can be seen from the generated code, mapping the source file
to C makes it very hard for someone not thoroughly acquainted with
the compilation process to see what's going on. I use gdb regularly
but it's hard to use it if you are not used to reading CPS converted C code.

Chicken doesn't use function names derived from user-names, but
gensyms names for the created code (This looks actually like something
to be improved - one could take a name-prefix taken from
the user-symbol for each function).

So I would say the this translation process contorts the code just as much
as trampoline-style.


cheers,
felix
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <g08b22ba.fsf@itasoftware.com>
"felix" <············@freenet.de> writes:

> Roger Corman wrote in message <···················@news.callatg.com>...
> >
> >>One Scheme-to-C compiler, "Chicken", uses a different technique, due to 
> >>Cheney, which also translates Scheme function calls into ordinary C 
> >>function calls, but checks for stack overflow at the start of each 
> >>function.  When the stack overflows the code does a longjump() to a top 
> >>level driver function, thus clearing the stack.  In Chicken the C stack 
> >>also serves as the nursery generation for the garbage collector.
> >
> >Chicken sounds very interesting. I am interested to find out more about that.
> >It sounds like a very creative implementation.
> >
> 
> 
> Well, thanks! But the idea is entirely Henry Baker's. Chicken is just the
> first implementation that uses this approach, AFAIK.

The first Scheme implementation that uses this approach.  I used this
approach on another language back in 1997.
From: Rob Warnock
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9rgr6o$3p1nk$1@fido.engr.sgi.com>
<···@itasoftware.com> wrote:
+---------------
| "felix" <············@freenet.de> writes:
| > Well, thanks! But the idea is entirely Henry Baker's. Chicken is just the
| > first implementation that uses this approach, AFAIK.
| 
| The first Scheme implementation that uses this approach.  I used this
| approach on another language back in 1997.
+---------------

And of course Baker himself used it in 1994 to code Gabriel's "Boyer
Benchmark" in C (hand-compiling/CPS'ing the Lisp).

   Baker, H.G.  "CONS Should Not CONS Its Arguments, Part II: Cheney
   on the M.T.A."  Draft Memorandum, Jan., 1994.  Available from
   ftp://ftp.netcom.com/pub/hb/hbaker/CheneyMTA.ps.Z.

[Archived at <URL:http://linux.rice.edu/~rahul/hbaker/CheneyMTA.html>
or <URL:http://linux.rice.edu/~rahul/hbaker/CheneyMTA.ps.Z>, code at 
<URL:http://linux.rice.edu/~rahul/hbaker/cboyer13.c>.]


-Rob

-----
Rob Warnock, 30-3-510		<····@sgi.com>
SGI Network Engineering		<http://www.meer.net/~rpw3/>
1600 Amphitheatre Pkwy.		Phone: 650-933-1673
Mountain View, CA  94043	PP-ASEL-IA

[Note: ·········@sgi.com and ········@sgi.com aren't for humans ]  
From: T. Kurt Bond
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <a3db6b24.0110290636.14cb5be@posting.google.com>
····@rigden.engr.sgi.com (Rob Warnock) wrote in message news:<··············@fido.engr.sgi.com>...
> <···@itasoftware.com> wrote:
> +---------------
> | "felix" <············@freenet.de> writes:
> | > Well, thanks! But the idea is entirely Henry Baker's. Chicken is just the
> | > first implementation that uses this approach, AFAIK.
> | 
> | The first Scheme implementation that uses this approach.  I used this
> | approach on another language back in 1997.
> +---------------
> 
> And of course Baker himself used it in 1994 to code Gabriel's "Boyer
> Benchmark" in C (hand-compiling/CPS'ing the Lisp).
> 
>    Baker, H.G.  "CONS Should Not CONS Its Arguments, Part II: Cheney
>    on the M.T.A."  Draft Memorandum, Jan., 1994.  Available from
>    ftp://ftp.netcom.com/pub/hb/hbaker/CheneyMTA.ps.Z.
> 
> [Archived at <URL:http://linux.rice.edu/~rahul/hbaker/CheneyMTA.html>
> or <URL:http://linux.rice.edu/~rahul/hbaker/CheneyMTA.ps.Z>, code at 
> <URL:http://linux.rice.edu/~rahul/hbaker/cboyer13.c>.]

As far as I can tell, 
    http://home.pipeline.com/~hbaker1/
seems to be Baker's current home page.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw6677zngo.fsf@shell01.TheWorld.com>
···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> David Feuer <···········@brown.edu> writes:
> 
> > "Pekka P. Pirinen" wrote:
>  [ Wow, this was 2 months ago! ]
> > > The interpreter can store the macroexpanded form of the code, instead
> > > reexpanding each time, which can make an enormous difference.  Indeed,
> > > one could preprocess the code into some more efficient form, and there
> > > are Lisp and Basic interpreters that do this, but here we approach
> > > byte-compilation territory, and you might well ask if it is an
> > > interpreter still.  Nevertheless, if the language system also supports
> > > a native-code compiler, people tend to call the other thing an
> > > interpreter.
> > 
> > Don't know what the standard view of this is, but according to Shriram
> > (see c.l.s), a compiler is a function from a program to a program and an
> > interpreter is a function from a program to an "answer". If an
> > interpreter internally compiles the code, that doesn't mean it's not an
> > interpreter.
> 
> That sounds like a definition of "evaluator".  I think there's enough
> of a difference between doing
> 
>   (eval some-form)
> 
> and
> 
>   (funcall (compile nil `(lambda () ,some-form)))
> 
> in CLISP, for example, that it's useful to distinguish between the two
> cases.  They both evaluate the program, but they do it in different ways.

Well, COMPILE yields a program and FUNCALL yields a value.  EVAL
implicitly prepares the text (possibly lazily) and yields a value, so
is like COMPILE+FUNCALL packaged together.  Whether the
COMPILE+FUNCALL happens in different ways is an implementational
detail; I think in MCL, for example, this is almost exactly how EVAL
is implemented.  While other implementations do have quite separable
interpreters.  The point of CL's definition in terms of "evaluation"
is to abstract away from interpretation or compilation and merely talk
about the required effect.
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvn12zndf3.fsf@apocalypse.OCF.Berkeley.EDU>
Erik Naggum <····@naggum.net> writes:

> * Thomas F. Burdick
> | Oh, come on.  Imagine it's pre-ANSI CL and he's asking for an object
> | system to be included in the standard.
> 
>   Oh, come on.  We are simply not in that position, anymore.

That's not much of an argument against the analogy.

> | It would be very nice to have some *portable* way of ensuring that (1)
> | certain important tail-calls are eliminated; or (2) an error is raised.
> 
>   I keep rewriting it "tail-call merging" because I think _elimination_ is
>   just plain wrong terminology.  Is that just me?

You're right; I usually say "tail-call optimization", which I think is
fine terminology, but I do think "merging" is better.

>   What you seem to want is a portable way to query a function at the source
>   level for the ability of all compilers to unwind its call stack to the
>   point where it can redirect a particular function call to return to its
>   caller.

Yes.  In other words: "I want this tail call merged; tell me if you
cannot do that".

>   What I keep arguing is that this requires a large set of other
>   requirements that are simply not in the Common Lisp spirit.  The
>   various conditions under which you can actually get tail-call
>   merging in today's implementations of Common Lisp are what they
>   are because of the freedom of these implementations to do their
>   work in various smart ways.  If you _require_ a specific set of
>   tail-call merging conditions,

But that's not what I'm proposing.

>   you will also mandate a particular set of implementation
>   techniques for function calls of _all_ kinds, and you may actually
>   find that a particular vendor is unable to comply with the new
>   standard because function calls in Common Lisp are incredibly
>   hairy things at the machine level, especially when you take CLOS
>   into consideration.

Sure they can.  If a requested tail-call merge can't be performed,
raise a condition.

>   [I have had the good fortune to work with Duane Rettig's code in
>   Allegro CL, and I am quite simply in _awe_ of the attention to
>   detail and efficiency and the cleverness of both the code
>   transformations and the resulting machine code on such a weird and
>   varied bunch of hardware archictures.  My own training and set of
>   skills predisposes me towards strong hesitation towards the kinds
>   of claims that Scheme makes, and I have studied Scheme
>   implementations sufficiently to understand how this "properly
>   tail-recursive" requirement has serious repercussions for the
>   design and implementation of the whole language.]

I would not want to argue for making CL "properly tail-recursive"

> | This is obviously something the vendors thought their user base wanted:
> | CMUCL, CLISP, ECLS, Allegro, LispWorks, and MCL all have some form of
> | tail-call elimination.  This sounds to me like a good candidate for
> | standardization.
> 
>   It may indeed sound like that until you look beneath the hood and see how
>   _differently_ they need to implement this seemingly simple concept
>   because of their other design choices in implementing the function call.

Once again, I'm not proposing that CL become "properly tail-recursive"
nor that we mandate the conditions under which tail-call merging
occurs.  Nor am I proposing something that would require a massive
change to implement (I don't think).

>   Contrary to popular belief in certain circles, it is not _sufficient_ to
>   specify something in a standard to actually get it.

Of course it's not.  And people will sometimes claim to conform to
standards they don't; that doesn't make standards worthless.

>   My own struggles with trusting people who claim publicly to be in
>   favor of the standard while they sneer at it and ridicule it in
>   private communication cause me to believe that if you require
>   something that people are only politically motivated to back, they
>   will find ways to implement it that are not worth much to you.  It
>   is somewhat like implementing a relational database with SQL
>   "support" that does not _actually_ support transactions and
>   rollback, like MySQL, because of "efficiency" reasons.  This is
>   fine as long as I do not need it, but the day I do, I may have to
>   invest serious effort in recoding my application and move to a
>   real relational database.  Now, why do people not implement
>   something that is so fundamental to the task at hand to begin
>   with?  Misguided ideas of their ability to judge the
>   appropriateness of standard requirements is most often to blame,
>   but irrational personal hangups can be just as powerful -- the
>   upper-case nature of Common Lisp symbols is one of them, where one
>   vendor argues strongly for a "modern" mode in lower-case, but does
>   _not_ default the case-insensitive argument to their apropos to
>   true so their users can at least give lower-case strings to
>   apropos, and neither do they offer a way to use modern and
>   standard mode side-by-side, which would have made it so much
>   easier to experiment with modern mode.  This kind of
>   "anti-standard attitude problem" is not at all specific to our
>   community -- I have worked with standards of many kinds for 15
>   years and every single one of the areas were I have experience,
>   there has been rogue vendors who think they know better or (more
>   often than not) who simply fail to care enough to understand what
>   they are doing.  Microsoft is the archetypical enemy of
>   specifications because they have this holy mission of supporting
>   what they claim are "customer needs", one such need being trust in
>   conformance obviously ignored.
> 
> | > | (Another thing I would like to see are stronger guarantees about what
> | > | conditions are signalled in exceptional situations; again, I do not have
> | > | problems with my vendor here, as I can always test my implementation's
> | > | behaviour, but with the standard itself.)
> | > 
> | >   What do these stronger guarantees actually entail?  Is it the ability to
> | >   sue vendors if they decide to claim to purport to conform to a voluntary
> | >   standard?  If so, nobody will volunteer to conform to it.
> | 
> | Uhm, I'm pretty sure he meant changing the wording to say that certain
> | conditions *will* be signalled in certain situations, instead of
> | *may*.  I see no reason why this would be an unreasonable request for
> | a future version of the standard.  Perhaps it wouldn't make it in, but
> | it sounds like a normal user request for portability.
> 
>   Sigh.  If you write something down in a law or a specification, you need
>   a way to enforce it.  How do you envision that you would enforce the
>   "guarantees" that you have gotten rolled into in the standard if you do?

You're right.  The entire CL standard is worthless.  Not only should
we not think about what we'd like in a future standard, we should
throw the whole thing we have now, out.  Because, after all, how to we
enforce it?
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211803188592640@naggum.net>
* Thomas F. Burdick
| That's not much of an argument against the analogy.

  That someone can imagine something does not make it an analogy worth
  arguing against.  It is incumbent on the analogy-producer to cough up
  something worth considering.  Lacking that, _you_ have no argument, and
  it is counterproductive and _indecent_ to pretend that someone who
  rejects your analogy is at fault for doing so, and I do not generally
  reward silliness.

| Yes.  In other words: "I want this tail call merged; tell me if you
| cannot do that".

  So we can get that today by defining something that always yields a
  warning or even error that it cannot tail-merge the call.  That seems
  like _such_ a great thing to add to the standard!  The moment you go
  beyond this silliness, however, you run into problems.  I second Tim
  Bradshaw's request that you (or that formal theory dude) actually come
  up with something that can be subjected to careful scrutiny.  As long as
  you only "want" something and make up silly imaginary analogies, there
  is absolutely no reason to believe that you _understand_ what you want.

| If a requested tail-call merge can't be performed, raise a condition.

  At compile-time?  Should the interpreter raise a condition, too?

| You're right.  The entire CL standard is worthless.

  Please turn your brain back on.

| Not only should we not think about what we'd like in a future standard,
| we should throw the whole thing we have now, out.  Because, after all,
| how to we enforce it?

  If you fail to appreciate that standards are specifications and tha
  clauses in standards are requirements, I can certainly fully understand
  why you "want" useless tail-call merging-requesting special forms and
  have no idea what you are embarking on.

  Some astonishingly unintelligent politicians always suggest "good ideas"
  that cannot be enforced, in the fantastically irrational belief that
  people will sort of follow the spirit of the law when nothing bad happens
  to those who do not, and the actually believe that a law is the proper
  place to make feeble suggestions about people's "good behavior".  Every
  now and then, this feeble-minded moralism passing for law-making is not
  obvious to sufficiently many politicians in time to avoid embarrassing
  laws to pass.  If you do not understand that if you write it into law,
  there _will_ be a court of law facing those who disobey it and you _will_
  impose punishment on those who "disagree" with you, you should not be
  messing with laws in the first place.  The same goes for standards,
  although we have weaker means of dealing with the criminally-minded (i.e,
  those who believe themselves elevated far above the community consensus
  and not at all bound by its decisions) in our community than in the legal
  and law enforcement community.  However, do not expect that those who
  profess a criminal mind's arrogance towards community consensus to be
  treated nicely.  When you are in fact trying to change the community
  consensus through a change to a specification that requires and survives
  _only_ because it has the respect of law-abiding citizens, the people who
  will most likely be affected by an undesirable change are those who want
  to fuel their arrogance towards the specification.  You have to recognize
  that some people in our community already harbor _massively_ irrational
  arrogance towards the standard and go out of their way to publish code
  that actively diminishes the value of adhering to the standard where the
  standard has spoken.  These are people who are so hell-bent on their own
  views that _they_ know better than the whole community consensus, that it
  has no merit whatsoever to respect those who desire a community consensus
  over some personal likes and dislikes.  Search for "religious zealot" in
  the archives of this newsgroup if you need to find a person who has shown
  us that opening up the standard to changes will be _very_ dangerous and
  that it should not be done for irrelevant and petty changes like getting
  a silly form to get an error if you cannot get tail-call merging.

  Those of us who want tail-call merging already _have_ the guarantees we
  need because we have read the fine _documentation_ of our Common Lisp
  environments and recognize that this will _have_ to be an implementation-
  dependent quality.  It has absolutely nothing to do with the semantics of
  the code, misguided formal theories to the contrary notwithstanding, and
  there is absolutely nothing in the language _today_ to suggest that you
  _can_ guarantee this feature, any more than you can "add" it to other
  languages that have not had the forethought to decide on "properly tail-
  recursive" _early_ in the design process.

  Let me make an analogy.  It is sometimes possible to turn a regular
  double-action pistol into an automatic pistol by breaking parts inside.
  Now, automatic firing of up to 19 rounds from a handgun can be a really
  good thing in approximately one situation, and we have wisely chosen not
  to include that situation in _lawful_ shooting activities, but some are
  not bound by the law so they want this feature.  With the kind of guns
  that you can achieve this feature, it is hard enough to hit the target in
  its normal single-shot mode and the temperature of the gun rises so fast
  that you normally want to restrict your firing to five rounds at a time.
  The effect of automatic firing in this hand-gun is thus to cause the
  muzzle to rise with every shot, usually by a significiant angle, and to
  raise the temperature of the barrel and thus _decrease_ its diameter, not
  to mention the angle and temperature of the ejected cartridge.  The
  hazardous nature of the modification should be fairly obvious even to
  people who think guns are only used to kill people.  Still, there are
  automatic pistols on the market that are _designed_ with this feature and
  they are quite safe, at least for the guy pulling the trigger.  I have no
  desire to own such a gun at all -- I shoot because it is a sport
  requiring strength, concentration, body control, etc, not because I like
  the actually _annoying_ sound effects, so when somebody who wants such
  guns looks at my guns and argue for the merits of automatic firing, I do
  think they are genuinely interested in automatic pistols, I think they
  are fucking nuts and would feel a lot safer if they left the range.  This
  is approximately how I feel about Scheme freaks in the Common Lisp world.

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Tim Bradshaw
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <ey3het9kul9.fsf@cley.com>
* Thomas F Burdick wrote:
> It would be very nice to have some *portable* way of
> ensuring that (1) certain important tail-calls are eliminated; or (2)
> an error is raised.  This is obviously something the vendors thought
> their user base wanted: CMUCL, CLISP, ECLS, Allegro, LispWorks, and
> MCL all have some form of tail-call elimination.  This sounds to me
> like a good candidate for standardization.

I think that the pro-tail-call-elimination-in-the-standard people
should come up with a description, in language suitable for a standard
of just what it is they want. 

Judging by the experience of Scheme, where, despite Scheme being a
much simpler language than CL from the point of view of
tail-call-elimination, it has taken a *long* time to tie down what is
meant by the requirement (witness article
<··············@localhost.localdomain> in this thread), and it may
actually not be tied down yet, I'm not sure.

In order for a CL *standard* to mandate tail-call-elimination in a way
that is actually useful to programmers using the language it has to be
described in a fairly precise way, and I haven't seen such a precise
description in this thread, while I *have* seen a number of arguments
from the anti-tail-call-elimination-in-the-standard people that such a
precise description might be very hard.  Rather we've had a lot of
vague handwaving as the above quote (I'm not trying to pick on this
article or its author in particular, it was just the closest to hand).

So I think it behooves the PTCEITS people to come up with a form of
wording which is precise enough that it is suitable for use in a
standard.  At that point this whole discussion might have more
purpose.

--tim
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcvu1x7nh62.fsf@apocalypse.OCF.Berkeley.EDU>
Tim Bradshaw <···@cley.com> writes:

> * Thomas F Burdick wrote:
> > It would be very nice to have some *portable* way of
> > ensuring that (1) certain important tail-calls are eliminated; or (2)
> > an error is raised.  This is obviously something the vendors thought
> > their user base wanted: CMUCL, CLISP, ECLS, Allegro, LispWorks, and
> > MCL all have some form of tail-call elimination.  This sounds to me
> > like a good candidate for standardization.
> 
> I think that the pro-tail-call-elimination-in-the-standard people
> should come up with a description, in language suitable for a standard
> of just what it is they want. 

That's not a bad idea.

> Judging by the experience of Scheme, where, despite Scheme being a
> much simpler language than CL from the point of view of
> tail-call-elimination, it has taken a *long* time to tie down what is
> meant by the requirement (witness article
> <··············@localhost.localdomain> in this thread), and it may
> actually not be tied down yet, I'm not sure.

But Scheme claims to be "properly tail recursive", which is not
something that I, for one, am advocating.  I'd just like some portable
way of requesting tail-call merging, and of finding out if it was
accomplished.  These are *very* different goals.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw7ku3hsrj.fsf@world.std.com>
···@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> But Scheme claims to be "properly tail recursive", which is not
> something that I, for one, am advocating.  I'd just like some portable
> way of requesting tail-call merging, and of finding out if it was
> accomplished.  These are *very* different goals.

It depends on your purpose.  Some people want to do things like:

 (defun count-odd-numbers (upto n-so-far)
   (if (= upto 0) 
       n-so-far
       (count-odd-numbers (1- upto) 
                          (if (oddp upto) (+ n-so-far 1) n-so-far))))


Merely "requesting" merging is not going to make this a correct program,
even with 80,000 stack being ok (as Corman says he can do), if upto 
is given initially as most-positive-fixnum.

In one of the first meetings of ANSI CL standardization, we (X3J13) 
wrote a charter.  One goal was "establish normative Common Lisp
programming practice."  See
 http://world.std.com/~pitman/CL/x3j13-86-020.html
for context.

We first agreed this was a good phrase and then talked for a while 
about what it might mean. :-)  Someone (my memory says it was McCarthy,
actually, who came to some of the first meetings, but I'm not 100% sure), 
suggested that it meant that we would try not to include things that 
encouraged people to do stuff they shouldn't.

Curiously, one cited example of that in the discussion was the inclusion 
of APPEND, though we decided to include it anyway.  But as I recall it was
used to illustrate the point that we don't want people calling APPEND all
the time because it will encourage people to write O(n^2) programs where
O(n) programs are better written either by CONS+NREVERSE paradigms or by
something similar hidden in functionality/macrology such as LOOP.  

I guess the idea was not just to say "this is what this operator does"
but to also give some guidance about where it was and was not good to
use it.

In Structure and Interpretation of Computer Programs (S&ICP), which is the
approximate equivalent in the Scheme world to CLTL in our world, in terms
of user reverence anyway, tail recursion is presented as, at minimum, 
"just another way to write a loop"... it's ALMOST presented as if "writing
a loop is buggy unless you express it this way".  A whole virtual religion
has been spawned on the worship of the tail recursive call as a mechanism
for iterating.

But in CLTL, the fact is that it is simply NOT a synonym for looping because
looping has a well-defined storage usage and tail calling does not.  
Specifying that you WANT it to be otherwise does not change the fact. 
There are reliable ways to write loops, and tail recursion is not one of 
them in either present-day CL or in a CL where you can request this without
being told the request will be granted.

It would work to have the request include programmer supplied information
that could distinguish between "necessary" and "optional" as we have
discussed for an optimize setting where (OPTIMIZE (TAIL-CALLS 3)) would
mean "must have", (OPTIMIZE (TAIL-CALLS 0)) would mean "must not have",
and anywhere in between would mean "optional".  It's not like 
DYNAMIC-EXTENT where it's safe to ignore; it's more like SAFETY where if
the programmer  makes the request and the implementation can't oblige 
him/her, the implementation must signal an error rather than just do the
wrong thing.

(Maybe this is what you meant by "and finding out if it was accomplished".
But doing a request and not having it imperatively stop the program if 
it failed would be more like C (with error codes you can forget to check).
The style of CL is to signal errors actively, not passively, so that the
default is that if you fail to arrange to handle something, you know an
error has not been detected because it would have been signaled if it HAD
been detected.)
From: Tim Bradshaw
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <ey3n12yjzaw.fsf@cley.com>
* Thomas F Burdick wrote:

> But Scheme claims to be "properly tail recursive", which is not
> something that I, for one, am advocating.  I'd just like some portable
> way of requesting tail-call merging, and of finding out if it was
> accomplished.  These are *very* different goals.

Well, come up with a design, and publish it.  Probably the best group
of people to ask if it is reasonable are implementors right now, since
they'll bear all the cost of it (well, they'll pass it on in license
fees of course).  You should also probably start of with at least one
non-trivial implementation (by `non-trivial' I mean one that doesn't
answer `don't know' in all cases).  If you can tie it down precisely,
get vendors to agree to implement it and users like it, you're
basically done.  Duane Rettig's simple streams specification and
implementation is an example of how to do this reasonably well, I
think (although I'm not sure the simple streams spec is `standards
quality' yet - I haven't read it recently - and it may be the case
that it's hard to implement in other CLs).

Just do it!

--tim
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <wv22np6g.fsf@itasoftware.com>
Tim Bradshaw <···@cley.com> writes:

> Judging by the experience of Scheme, where, despite Scheme being a
> much simpler language than CL from the point of view of
> tail-call-elimination, it has taken a *long* time to tie down what
> is meant by the requirement (witness article
> <··············@localhost.localdomain> in this thread), and it may
> actually not be tied down yet, I'm not sure.

It is difficult to tie down.  You cannot describe it with a semantics
that does not address the issues of a finite store (an infinite stack
is indistinguishable from tail recursion, or alternatively, an
implementation that heap-allocates frames but has no GC is
indistinguishable from non-tail-recursive), which few semantics do.

One way might be to describe it in terms of a CPS conversion of the
program.  The CPS conversion will make the continuations explicit as
lambda expressions.  Many of these lambda expressions will be of the
trivial form (LAMBDA (VALUE) (K VALUE)).  If these lambda expressions
are eta-reduced to simply `K', then the program will be `properly tail
recursive'.

Another way to describe it is to enumerate the cases where proper tail
recursion would be expected to occur.  Fortunately, these are a subset
of the places where multiple value returns can occur.  (You could not
tail-recurse from the first subform in a PROG1, from the body form of
an UNWIND-PROTECT, or when the set of special-bound variables differs
between the function entry and the call site.  Those are the major
exceptions, and I imagine I missed a few.)
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87adyy5btd.fsf@pps.jussieu.fr>
TB> I think that the pro-tail-call-elimination-in-the-standard people
TB> should come up with a description, in language suitable for a standard
TB> of just what it is they want. 

Okay, I'll try to give a rough idea of what I would like to see.

There are three things that need specified:

1. what is a tail-call;
2. which tail-calls are to be merged; and
3. what does this merging involve.

1 is easy; 2 is probably not difficult; 3 I don't really know how to
specify.

Of course, what I'm trying to specify is a minimum set of requirements
that all conforming implementations should satisfy; tail call merging
beyond that is left to the implementation.


1. What is a tail call
**********************

A tail call is a call that is in tail position.  In the following
forms, ``*'' is used to mark the set of tail positions:

  (defun (...) ... *)            (when none of the bound variables are SPECIAL)
  (let (...) ... *)              (when none of the bound variables are SPECIAL)
  (multiple-variable-bind (...) (...) ... *)
                                 (when none of the bound variables are SPECIAL)
  (progn ... *)
  (if ... * *)                   (both branches are in tail position)
  (cond (... *) (... *) ... (... *))
                  (and so on, WHEN, UNLESS, CASE, TYPECASE, ETYPECASE...)
  (tagbody ... *)
  (do (...) (... *) ...)         (when none of the bound variables are SPECIAL)
                                 (what about LOOP?)
  (the *)                        (merging may lead to the THE not performing
                                  runtime typechecking; tough, mate.)

and of course the structural rule

  if (A *) and (B *),
     then (A (B *))

This last rule may be applied multiple times, thus leading to an
inductive definition.
    
The above rules are sufficient for the particular set of idioms that I
use.  Some people may like to add other contexts; note for example the
absence of PROG, which does not belong in *my* *personal* set of
idioms.

In addition, it is not clear to me whether a call immediately followed
by RETURN or RETURN-FROM the current function should be treated as a
tail-call.  It is not important for me (but, again, other people may
code differently).

(The absence of DEFMETHOD above is due to the fact that it is
impossible, in general, to determine statically whether there are
:AFTER, :AROUND, etc. methods to be run.  There is therefore no choice
but to put the burden on the programmer to wrap the relevant GF's
within ordinary functions.  There's no denying that this is an
unpleasant state of affairs, and a further extension may define a new
:SINGLE type of method combination that ensures that exactly one
method is run for a single GF invocation, and guarantees that
tail-call merging is performed within :SINGLE methods.)


2. Which tail calls are to be merged
************************************

This needs to be negociated with implementors; Roger Corman has given
a good explanation of why not all calls in tail position should be
required to be merged.

I would like to enable the callee to have &OPTIONAL and &KEY
arguments, but only in the case where all optional arguments are
provided by the caller[1][2].

I definitely do need tail-call merging to be performed between
distinct source files.  I am also keen on having tail-call merging
performed at all optimisation levels, and both in compiled and
interpreted code (and in mixed code).

On the other hand, I don't think that allowing the callee to have
&REST or &WHOLE arguments is reasonable.


3. What does tail call merging involve
**************************************

This is the very difficult part.  It is easy to specify what tail call
merging does in any particular implementation (I once did it for a
graph reduction machine, where it is slightly counter-intuitive but
definitely doable); on the other hand, it is difficult to specify with
no reference to a particular implementation.  I think that a suitable
statement would be to say that

  A piece of code consisting of merged tail-calls only runs in bounded
  space in addition to any explicit consing it may perform.

Of course, it is not clear at all whether this statement can be made
precise w.r.t. the Common Lisp language (as opposed to any particular
implementation).

Regards,

                                        Juliusz

[1] I am thinking of the common case where the publicly visible
interface of a package has optional arguments but the internal
(potentially recursive) calls specify all arguments.  Here's an
artificial example (I'd use LOOP instead of tail recursion for
something that simple):

  (defun foo (l &optional (counter 0))
    (if (null l)
      counter
      (foo (cdr l) (+ counter 1))))

      
[2] Is it reasonable to require that a declaration providing the
callee's argument list needs to be provided for non-self tail-call
merging to happen?  I dislike this option, as I don't like decla-
rations having semantic meaning, but there may very well be no choice.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw7ku1vnul.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> A tail call is a call that is in tail position.  In the following
> forms, ``*'' is used to mark the set of tail positions:
> 
>   (defun (...) ... *)         (when none of the bound variables are SPECIAL)

I think addressing "syntax" is a bad plan.  Syntax can hide detail and it's
the detail that matters.  The issue is the same as with top-level form-ness,
and you should read how that is specified.

An example of where the above breaks down, for example, is:

 (defun foo1 ()
   (bar #'(lambda (x) (return-from foo1 x))))

I *believe* the call to BAR above should not be a tail call.
I also *believe* that the call to BAR in this next one is 
not a tail call, though maybe it just takes more thought than I've
given it.  Still, things like this worry me:

 (defun foo2 ()
   (setf (get 'foo 'bar) #'(lambda (x) (return-from foo2 x)))
   (bar))

Since all DEFUN's have a block tag corresponding to their name, any attempt
to close over a return-from to that tag is a red flag.  How to express
that elegantly is another matter.
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bc69a0b.568787733@news.callatg.com>
On Thu, 11 Oct 2001 22:00:34 GMT, Kent M Pitman <······@world.std.com> wrote:
>I think addressing "syntax" is a bad plan.  Syntax can hide detail and it's
>the detail that matters.  The issue is the same as with top-level form-ness,
>and you should read how that is specified.
>
>An example of where the above breaks down, for example, is:
>
> (defun foo1 ()
>   (bar #'(lambda (x) (return-from foo1 x))))
>
>I *believe* the call to BAR above should not be a tail call.
>I also *believe* that the call to BAR in this next one is 
>not a tail call, though maybe it just takes more thought than I've
>given it.  Still, things like this worry me:
>
> (defun foo2 ()
>   (setf (get 'foo 'bar) #'(lambda (x) (return-from foo2 x)))
>   (bar))
>
>Since all DEFUN's have a block tag corresponding to their name, any attempt
>to close over a return-from to that tag is a red flag.  How to express
>that elegantly is another matter.

Yes, I agree that you probably have to bail on tail optimizing these cases. This
part of Common Lisp is one of the trickiest areas to implement, because what
looks like a simple branch is actually a jump from one executing function to
another (at least depending how you implement it...). I assume a goal of tail
optimization is to not change the semantics of the function, in a user visible
way (unless they use trace or a debugger). The cases Kent mentioned possibly do
visibly change the behavior if optimized away.

This brings up another issue, and just the tip of the iceberg. A CATCH form, for
example, binds a catcher to some sort of runtime stack. Same for condition
handlers. These have to be removed prior to making a tail call. Obviously this
changes the semantics of the called function i.e. the catcher or handler is
gone. I guess you can say that elements in the body of a catch form or handler
are not considered tail calls (even though they seem to be).

Roger
From: Thomas F. Burdick
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <xcv7ku0boa1.fsf@apocalypse.OCF.Berkeley.EDU>
·····@corman.net (Roger Corman) writes:

> This brings up another issue, and just the tip of the iceberg. A
> CATCH form, for example, binds a catcher to some sort of runtime
> stack. Same for condition handlers. These have to be removed prior
> to making a tail call. Obviously this changes the semantics of the
> called function i.e. the catcher or handler is gone. I guess you can
> say that elements in the body of a catch form or handler are not
> considered tail calls (even though they seem to be).

Right.  This is part of the reason I'd like to have some way of
ensuring that tail call merging happened.  For CATCH, I can't think of
any way that tail calls could be merged, but there may be some cases
where implementation details prevent some otherwise-mergeable calls to
not be.  I would be nice if something like

 (defun foo ()
   (declare (optimize (saftey 3) (tail-calls 3)))
   (without-tail-call-merging
     (tail-value (bar))))

could signal an error.  I like the idea of marking the lexical area
where tail call merging is requested, and of marking the actual tail
calls that should be merged.  (declare (optimize (tail-calls ...)))
seems nice enough to me, but I don't like (tail-value ...) or any
other way I can think of marking it.  I suppose an alternative could
be:

 (defun foo ()
   (declare (tail-call bar))
   (without-tail-call-merging
     (bar)))

which I like better, but it would force you to name anything you want
to tail call.  Maybe that's not such a bad thing, though.  If I have
time this weekend, I think I'll try to start adding something like the
last one to SBCL (I have more completel ideas of semantics for it in
my head, but not the time to write them up right now).
From: Bruce Hoult
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <bruce-F60431.21202512102001@news.paradise.net.nz>
In article <··················@news.callatg.com>, ·····@corman.net 
(Roger Corman) wrote:

> This brings up another issue, and just the tip of the iceberg. A
> CATCH form, for example, binds a catcher to some sort of runtime
> stack. Same for condition handlers. These have to be removed prior
> to making a tail call. Obviously this changes the semantics of the
> called function i.e. the catcher or handler is gone. I guess you
> can say that elements in the body of a catch form or handler
> are not considered tail calls (even though they seem to be).

Absolutely!  If you need to do *any* work after the function returns 
then, trivially, the function *must* return to you and therefore, by 
definition, that call is not a tail call.

If the user wants something to be a tail call then it needs to be 
outside of any exception handler, with-open-file, or anything of the 
sort.  It is the *programmer's* responsibility -- if they want tail 
calls --  to make sure that moving the call outside of such constructs 
retains the semantics they want.

If a really advanced compiler can in some cases prove that the semantics 
are the same if it moves the last function call outside of an exception 
handler (which would require major inter-function analysis) then that's 
great, but you sure wouldn't expect every compiler to do that!

-- Bruce
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bc68f46.566031149@news.callatg.com>
I like your message--your issues are well presented. I want to point out a
couple things.

On 11 Oct 2001 19:25:02 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:

>TB> I think that the pro-tail-call-elimination-in-the-standard people
>TB> should come up with a description, in language suitable for a standard
>TB> of just what it is they want. 
>
>Okay, I'll try to give a rough idea of what I would like to see.
>
>There are three things that need specified:
>
>1. what is a tail-call;
>2. which tail-calls are to be merged; and
>3. what does this merging involve.
>
>1 is easy; 2 is probably not difficult; 3 I don't really know how to
>specify.

1 is not quite as easy as you think.  ;-)

>
>1. What is a tail call
>**********************
>
>A tail call is a call that is in tail position.  In the following
>forms, ``*'' is used to mark the set of tail positions:
>
>  (defun (...) ... *)            (when none of the bound variables are SPECIAL)
>  (let (...) ... *)              (when none of the bound variables are SPECIAL)
>  (multiple-variable-bind (...) (...) ... *)
>                                 (when none of the bound variables are SPECIAL)
>  (progn ... *)
>  (if ... * *)                   (both branches are in tail position)
>  (cond (... *) (... *) ... (... *))
>                  (and so on, WHEN, UNLESS, CASE, TYPECASE, ETYPECASE...)
>  (tagbody ... *)

tagbody always returns NIL, so unless the tail function is guaranteed to return
NIL you have a problem.

>    
>The above rules are sufficient for the particular set of idioms that I
>use.  Some people may like to add other contexts; note for example the
>absence of PROG, which does not belong in *my* *personal* set of
>idioms.
PROG is a macro and you shouldn't even consider it. By the time the compiler
gets to it all the macros should have been expanded. Same for cond, defun, etc.
You need to define in terms of lambda, rather than defun. All you need to worry
about is special operators and lambdas.

>
>In addition, it is not clear to me whether a call immediately followed
>by RETURN or RETURN-FROM the current function should be treated as a
>tail-call.  It is not important for me (but, again, other people may
>code differently).
Since the RETURN or RETURN-FROM provide the returned value, a preceding call
would have to be guaranteed to return the same thing as they returned, and that
their return forms did not have any side effects, to treat a preceding call as a
tail call. Rather, add
(return-from label *)
to the list above.



>  A piece of code consisting of merged tail-calls only runs in bounded
>  space in addition to any explicit consing it may perform.
>
>Of course, it is not clear at all whether this statement can be made
>precise w.r.t. the Common Lisp language (as opposed to any particular
>implementation).

Well, it seems you are saying that not only do tail calls need to be merged, but
that all *implicit* consing needs to be removed as well. This is where it gets
tricky. Many people think they know when a system conses, and when it doesn't,
but the language doesn't in general specify this very much and people are often
wrong.

As a side note, I often see posts from people trying to eliminate consing, under
the impression that this will greatly improve performance and avoid garbage
collection. Well, of course it avoids garbage collection, but I believe you just
need to give them a fast collector. The Corman Lisp collector usually takes 2 or
3 milliseconds to run (on my box anyway), well within most real-time needs (of
course I know there are exceptions). Because it is so fast, the implementation
doesn't hesitate to do some implicit consing when it seems useful. This
certainly is not forbidden by the standard.

Roger
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw3d4pl3nw.fsf@world.std.com>
·····@corman.net (Roger Corman) writes:

> On 11 Oct 2001 19:25:02 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:
> 
> >The above rules are sufficient for the particular set of idioms
> >that I use.  Some people may like to add other contexts; note for
> >example the absence of PROG, which does not belong in *my*
> >*personal* set of idioms.
>
> PROG is a macro and you shouldn't even consider it. By the time the
> compiler gets to it all the macros should have been expanded. Same
> for cond, defun, etc.  You need to define in terms of lambda, rather
> than defun. All you need to worry about is special operators and
> lambdas.

What you say is strictly true, but since DEFUN wraps its body in a 
named BLOCK, this is probably confusing because every DEFUN is not
a LAMBDA (which is comparatively simple) but a BLOCK (which seems to
me messy, unless someone thinks I'm just confused, which is always
possible).

BLOCK was not treated in the list Juliusz gave, but I'd be interested
to see it described, especially in light of the examples I gave in a
prior post.

Also, there are some complexities like that LOOP presently doesn't expose
whether it has a block whose name you don't know or whether it has no block.

Even in the case of RETURN-FROM, I'm not sure.  Consider:

 (defun baz ()
   (block bar
     (block foo
       (blah)
       (return-from bar (zing (zap))))
     (quux)))

which makes it look like (ZING (ZAP)) can be tail called, but what if
(ZAP) is a macro that expands to #'(LAMBDA (X) (RETURN-FROM FOO X))))?
Can ZING still be tail-called?  I need a better explanation of what tail
calling is in order to understand this.
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bc6a798.572257402@news.callatg.com>
On Fri, 12 Oct 2001 07:26:43 GMT, Kent M Pitman <······@world.std.com> wrote:

>·····@corman.net (Roger Corman) writes:
>
>> On 11 Oct 2001 19:25:02 +0200, Juliusz Chroboczek <···@pps.jussieu.fr> wrote:
>> 
>> >The above rules are sufficient for the particular set of idioms
>> >that I use.  Some people may like to add other contexts; note for
>> >example the absence of PROG, which does not belong in *my*
>> >*personal* set of idioms.
>>
>> PROG is a macro and you shouldn't even consider it. By the time the
>> compiler gets to it all the macros should have been expanded. Same
>> for cond, defun, etc.  You need to define in terms of lambda, rather
>> than defun. All you need to worry about is special operators and
>> lambdas.
>
>What you say is strictly true, but since DEFUN wraps its body in a 
>named BLOCK, this is probably confusing because every DEFUN is not
>a LAMBDA (which is comparatively simple) but a BLOCK (which seems to
>me messy, unless someone thinks I'm just confused, which is always
>possible).

Well, it's both a lambda and a block. It has to create a lambda to produce a
closure, to store as a side effect. Here is my macroexpansion for 
(defun foo (x) (list x))

(PROGN (SETF (SYMBOL-FUNCTION 'FOO)
              #'(LAMBDA (X) (BLOCK FOO (LIST X))))
        'FOO)

>
>BLOCK was not treated in the list Juliusz gave, but I'd be interested
>to see it described, especially in light of the examples I gave in a
>prior post.
Yes, it needs to be on the list, but should  behave basically as a progn
(skipping the label of course).

>
>Also, there are some complexities like that LOOP presently doesn't expose
>whether it has a block whose name you don't know or whether it has no block.
It certainly exposes it when you expand the macro.

>Even in the case of RETURN-FROM, I'm not sure.  Consider:
>
> (defun baz ()
>   (block bar
>     (block foo
>       (blah)
>       (return-from bar (zing (zap))))
>     (quux)))
>
>which makes it look like (ZING (ZAP)) can be tail called, but what if
>(ZAP) is a macro that expands to #'(LAMBDA (X) (RETURN-FROM FOO X))))?
>Can ZING still be tail-called?  I need a better explanation of what tail
>calling is in order to understand this.

As discussed in another posting, these non-local return-from forms present a
challenge. My solution would be to not tail-optimize any form that had an
embedded return-from form which returned to a scope in an outer lambda.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87sncolwkp.fsf@pps.jussieu.fr>
KMP> BLOCK was not treated in the list Juliusz gave, but I'd be
KMP> interested to see it described, especially in light of the
KMP> examples I gave in a prior post.

I realised that last night.  Bummer.

I am not quite sure whether BLOCK should belong in my list of tail
contexts.  (See below about DEFUN.)

KMP>  (defun baz ()
KMP>    (block bar
KMP>      (block foo
KMP>        (blah)
KMP>        (return-from bar (zing (zap))))
KMP>      (quux)))

KMP> which makes it look like (ZING (ZAP)) can be tail called, but what if
KMP> (ZAP) is a macro that expands to #'(LAMBDA (X) (RETURN-FROM FOO X))))?

Of course, if BLOCK is not in the list of tail contexts, the issue
doesn't arise (in which case neither do those of RETURN-FROM and
PROG).  I think this issue requires more thought.

As to DEFUN, it poses no problem to specify that it generates an
implicit block only in cases when the block is actually RETURNed to.
As this can be checked statically, it is a perfectly valid requirement
to make, and specifies an optimisation that reasonable implementations
already do perform.

(Comments about falling trees and empty forrests not included for now.)

Regards,

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw4rp4zah4.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> KMP>  (defun baz ()
> KMP>    (block bar
> KMP>      (block foo
> KMP>        (blah)
> KMP>        (return-from bar (zing (zap))))
> KMP>      (quux)))
> 
> KMP> which makes it look like (ZING (ZAP)) can be tail called, but what if
> KMP> (ZAP) is a macro that expands to #'(LAMBDA (X) (RETURN-FROM FOO X))))?
> 
> Of course, if BLOCK is not in the list of tail contexts, the issue
> doesn't arise (in which case neither do those of RETURN-FROM and
> PROG).  I think this issue requires more thought.
> 
> As to DEFUN, it poses no problem to specify that it generates an
> implicit block only in cases when the block is actually RETURNed to.
> As this can be checked statically, it is a perfectly valid requirement
> to make, and specifies an optimisation that reasonable implementations
> already do perform.

But making the requirement that the entire tree be walked is pushing
dangerously close to making a requirement that the code not be
interpreted.  The whole point of an interpreter is not to macroexpand
branches of code that are not being immediately used.  Getting rid 
of interpreters is no small price to pay.
From: Roger Corman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3bc7d38a.649103691@news.callatg.com>
On Fri, 12 Oct 2001 23:48:07 GMT, Kent M Pitman <······@world.std.com> wrote:

>Juliusz Chroboczek <···@pps.jussieu.fr> writes:
>> As to DEFUN, it poses no problem to specify that it generates an
>> implicit block only in cases when the block is actually RETURNed to.
>> As this can be checked statically, it is a perfectly valid requirement
>> to make, and specifies an optimisation that reasonable implementations
>> already do perform.
>
>But making the requirement that the entire tree be walked is pushing
>dangerously close to making a requirement that the code not be
>interpreted.  The whole point of an interpreter is not to macroexpand
>branches of code that are not being immediately used.  Getting rid 
>of interpreters is no small price to pay.

I thought that the topic was whether the *compiler* should optimize tail calls.
I never thought that the issue of the interpreter behavior was under discussion.

Although as we both know Common Lisp does not provide (nor should it) clear
specification regarding the difference between compiled and interpreted code.

Roger
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwwv20q9sx.fsf@world.std.com>
·····@corman.net (Roger Corman) writes:

> On Fri, 12 Oct 2001 23:48:07 GMT, Kent M Pitman <······@world.std.com> wrote:
> 
> >Juliusz Chroboczek <···@pps.jussieu.fr> writes:
> >> As to DEFUN, it poses no problem to specify that it generates an
> >> implicit block only in cases when the block is actually RETURNed to.
> >> As this can be checked statically, it is a perfectly valid requirement
> >> to make, and specifies an optimisation that reasonable implementations
> >> already do perform.
> >
> >But making the requirement that the entire tree be walked is pushing
> >dangerously close to making a requirement that the code not be
> >interpreted.  The whole point of an interpreter is not to macroexpand
> >branches of code that are not being immediately used.  Getting rid 
> >of interpreters is no small price to pay.
> 
> I thought that the topic was whether the *compiler* should optimize
> tail calls.

Scheme interpreters optimize tail calls.

> I never thought that the issue of the interpreter behavior was under
> discussion.  Although as we both know Common Lisp does not provide
> (nor should it) clear specification regarding the difference between
> compiled and interpreted code.

Right.  This is purely an implementation choice and the meaning of programs
is defined without direct reference to either.  However, the key notion of
an interpreter is not having to have global knowledge of what the program
does.  The notion of "tail call position" seems to me to imply "position",
not "global knowledge".  So when we start to talk about tail call position
as meaning "if blah blah doesn't happen elsewhere, then this position is
a tail call", it's not a "positional" issue any more.  

I think the right thing is not to look at "does anyone USE this tag" but
rather "does anyone BIND this tag" since mostly if people bind something,
they plan to use it.  The problem then becomes, "all CL functions spuriously
bind the tag by the name of a function around a function definition".  I
think that design decision is marginal, but it is perhaps enough to make
the difference where CL runs into a problem that Scheem does not.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87r8s4cxuw.fsf@pps.jussieu.fr>
RC> I thought that the topic was whether the *compiler* should optimize
RC> tail calls.  I never thought that the issue of the interpreter
RC> behavior was under discussion.

I would tend to agree with Kent that we should be aiming for a
definition of ``reliable tail-call elimination'' that can be
efficiently implemented in a classical interpreter.

KMP> But making the requirement that the entire tree be walked is pushing
KMP> dangerously close to making a requirement that the code not be
KMP> interpreted.  The whole point of an interpreter is not to macroexpand
KMP> branches of code that are not being immediately used.  Getting rid 
KMP> of interpreters is no small price to pay.

I won't deny that the realisation that every DEFUN is a BLOCK has
diminished my enthusiasm somewhat.  But I'm still trying.

Define a ``useless BLOCK'' as one whose tag never appears within its
lexical scope.  The idea would be to only *require* tail-call
elimination within useless blocks (but still leave an implementation
free to do tail-call elimination beyond that).

We agree that this is no problem for a compiler.  An interpreter, when
it encounters a block, will put some sort of data structure on its
lexical chain, call it a ``block'' (lowercase), and would mark it as
``unused''.  Whenever it builds a closure, the interpreter should
check whether it is closing over a BLOCK's tag; if it is, it should
mark the associated block as ``used''.

When encountering a call which is potentially in tail position, the
interpreter should check all the blocks that would be overwritten; if
any of those are ``used'', the call proceeds normally; if not, the
call is a tail call, and the ``unused'' blocks are overwritten.

The correctness of this scheme comes from the observation that a
unused block cannot be invoked without returning to the current
lexical scope, and, conversely, that a useless block can never be
marked as used.

This approach does not require walking the whole body of the BLOCK;
however, it does require walking (and macroexpanding) the body of
every single funarg.  I won't deny that this still feels like a very
high price to pay in an interpreter.

(Back to the drawing board.)

                                        Juliusz
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <1yk34r5d.fsf@itasoftware.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> I won't deny that the realisation that every DEFUN is a BLOCK has
> diminished my enthusiasm somewhat.  But I'm still trying.
> 
> Define a ``useless BLOCK'' as one whose tag never appears within its
> lexical scope....
> [lazy block discussion elided]

There is no reason BLOCK cannot be tail-recursive.

BLOCK/RETURN-FROM is essentially a CATCH and THROW with the catch tag
being bound to a name in `block space'.  The catch tag points to the
stack frame active when the BLOCK was entered.  In a `normal'
CATCH/THROW pair, the catch tag is bound to a name in `catch-tag
space'.  `Catch-tag space' is dynamically scoped, so it makes sense to
implement it as a push-down list and thread this through the stack.
The `catch frame' pushed on the stack can therefore be understood as
being an artifact of binding the catch-tag to a name, rather than as
an artifact of the non-local transfer mechanism.   

BLOCK/RETURN-FROM is often implemented via the same mechanism as
CATCH/THROW, so such implementations will push things on the stack
when a BLOCK is entered.  However, this is not strictly necessary
because the block-name is not bound in the same namespace as the
catch-tag.  This means that an implementation need not push a `catch
frame' when entering a block, and thus block-entry does not break
tail-recursion.
   
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87wv20lx3h.fsf@pps.jussieu.fr>
[I'm reordering your points again.]

RC> tagbody always returns NIL, so unless the tail function is
RC> guaranteed to return NIL you have a problem.

[...]

RC> Since the RETURN or RETURN-FROM provide the returned value, a
RC> preceding call would have to be guaranteed to return the same
RC> thing as they returned, and that their return forms did not have
RC> any side effects, to treat a preceding call as a tail
RC> call. Rather, add

RC>   (return-from label *)

RC> to the list above.

Quite correct.  So it looks like TAGBODY is out, but RETURN-FROM is
*unconditionally* in (even in cases when it is not in tail position
itself).  I'll try to get my head around it.

RC> PROG is a macro and you shouldn't even consider it.

Yes, but its expansion is not specified by The Book, so I still need
to do something about it.  Of course, you, the implementor, know what
it expands to in your implementation, so you may very well know that
it will generate a tail context with no intervention on your part.

>> A piece of code consisting of merged tail-calls only runs in bounded
>> space in addition to any explicit consing it may perform.

>> Of course, it is not clear at all whether this statement can be made
>> precise w.r.t. the Common Lisp language (as opposed to any particular
>> implementation).

RC> Well, it seems you are saying that not only do tail calls need to
RC> be merged, but that all *implicit* consing needs to be removed as
RC> well.

Only implicit consing that remains live.  I am happy to grant you the
right to cons any temporary space you may desire; what I don't want to
allow you is to have unbounded amounts of *live* space that I don't
know about.

I am fully aware that this is the tricky part, and I really don't know
how to formulate it within the framework of CL.

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfw669kzajx.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> Quite correct.  So it looks like TAGBODY is out, but RETURN-FROM is
> *unconditionally* in (even in cases when it is not in tail position
> itself).  I'll try to get my head around it.

No.  I showed an example where that was not so.  

  (return-from foo (zap #'(lambda (x) (return-from bar x))))

The call to ZAP is not a tail call.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87lmilt006.fsf@pps.jussieu.fr>
>> | (Another thing I would like to see are stronger guarantees about what
>> | conditions are signalled in exceptional situations;
>> 
>> What do these stronger guarantees actually entail?

TB> Uhm, I'm pretty sure he meant changing the wording to say that certain
TB> conditions *will* be signalled in certain situations, instead of
TB> *may*.

I was actually being much more modest, and thinking of changing a
number of requirements that ``an error is signalled[1]'' to a precise
specification of which proper subclass of ERROR should be signalled.
I have sometimes found myself establishing handlers for ERROR in
distributed code, which always makes me nervous, and in my personal
opinion should never be necessary.

(As far as I can tell, there should be no cost associated to such a
change other than the presence of a few extra condition classes in the
Lisp runtime.  In addition, I would like to mention that I do fully
realise that the current wording does allow implementations to signal
a more precise condition than ERROR -- but I hope you'll agree that
this is of little comfort to portable code.)

A case in point is that of DESTRUCTURING-BIND, which in its current
state requires handling ERROR whenever applying it to data that has
not been pre-validated -- in which case I usually end up doing the
destructuring by hand instead.

When I complained about that (IMHO) misdesign a few years ago on this
respectable forum, Kent replied that X3J13 didn't have enough manpower
to specify the exceptional situations more precisely.  I fully accept
this point, which I hope is clear from the wording I've used.

Regards,

                                        Juliusz

[1] er... that is ``signaled.''
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwu1x8vlmj.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> >> | (Another thing I would like to see are stronger guarantees about what
> >> | conditions are signalled in exceptional situations;
> >> 
> >> What do these stronger guarantees actually entail?
> 
> TB> Uhm, I'm pretty sure he meant changing the wording to say that certain
> TB> conditions *will* be signalled in certain situations, instead of
> TB> *may*.
> 
> I was actually being much more modest, and thinking of changing a
> number of requirements that ``an error is signalled[1]'' to a precise
> specification of which proper subclass of ERROR should be signalled.

Oh, this is a lot more complex than you imagine.  It's not that we
don't understand your need, it's that it's very tricky for the
language to specify this (and involved more personpower than we had
available to do even specificationally, not even worrying about the
burden it imposes on implementors).

> I have sometimes found myself establishing handlers for ERROR in
> distributed code, which always makes me nervous, and in my personal
> opinion should never be necessary.

People do understand this issue, but...

Let's work historically to show you how it came to be as it is.

First there was the non-typed condition system.  ERROR had a message.
ERRSET trapped things.  "Sophisticated" systems could see the error
message in limited cases and on the basis of the string could do certain
very specialized actions.

Then there was the Symbolics "New Error System" (NES) [based on long
experience with Multics PL/1 condition system] allowing object-oriented
error handling, separating the "error kind" from the message.  The very
first roll-out was hugely detailed with a tree of error classes.

I did the "port" of NES to a proposal I felt was appropriate to CL.
In so doing, I asked Dave Moon at Symbolics what depth of the error class
tree I thought we should include in CL.  He said that if he had it to 
do over (which is effectively what CL was, a doing over), he would not
specify nearly as many classes.

He observed first that it isn't clear that it's good style, for
example, for people to be making decisions on the basis of "unseen go
tag" vs "unseen catch tag"; programs relying on this level of detail
have serious style/design problems beyond the actual error that is
manifesting.  So we did some cutbacks based on what we thought
reasonable programs should do, and I think those cutbacks were
well-advised.  I will defend those cutbacks to this day, and I think
even you and others who want more elaboration would agree.

There is a second class where it would be better style to have a bit
more elaboration.  An example might be "file not found" and 
"directory not found" instead of just "file error".  I don't doubt this
would be radically useful.

In the historical context, these were not added for two reasons.  One
was that CLOS was new and untrusted, and it was a design constraint
which youc an see if you look that none of the system have multiple
inheritance in primitive types (so we could back out of multiple
inheritance if it was a disaster, which some (mostly those who had
never used it or who had stock hardware and worried the performance
would not be as good as lisp-specialized hardware) felt was an
essential option to keep open).  (I believe the only exceptions are
type NULL, which is handled in some bizarre primitive way by all
systems, and the types SIMPLE-CONDITION, SIMPLE-ERROR, etc. which are
specified in a way that you might think had to involve multiple
inheritance because you have used a reasonable language that allowed
this, but that actually can be done by stupid other ways like Java
does (replicating functionality, etc.) to cover for lack of multiple
inheritance, where that happens in an implementation.)  Anyway, the
second reason was somewhat independent, but had some bad play-in from
the single-inheritance restriction: we couldn't agree on the type
tree because it would be different for different vendors.  (And, 
related to this, even if we could, it might bind us to a particular
implementation that we later decided we needed to grow out of.  You'll
see how that plays out in my example below.)

Consider something like directory-not-found.  In something like
LispWorks or Allegro, I believe the Lisp file system can only open the
local file system.  Well, maybe except for NFS, so the issue probably
still comes up.  Anyway, mostly when you do OPEN a file is either
there or it is not.  Same with a directory.  But the only "current
practice" we had was the Lisp Machine, and its model was more
elaborate.  OPEN could open any file on the internet by saying (open
"hostname:filename").  [You could manipulate independently what
protocols, network paths, etc. were used to get to that hostname, so
you might be using FTP to one host while using TFTP or SNA or
something to another host.]  But a file not found might be temporary
(host down or not responding) or permanent (i got to the host but it
says there's no file there).  It might be a network problem or not.
We couldn't easily make DIRECTORY-NOT-FOUND a subclass of
NETWORK-ERROR, nor could be make it not a subclass of NETWORK-ERROR.
We could have left the classes free-floating but that could have led
to other confusions we hoped to avoid, making it hard to order the
cases correctly when discriminating several error types, for example.

We ultimately decided to leave this area blank and to see what error 
classes ended up springing up and then try to repair the language later
by adding specificity based on experience.

We did not know at the time that there would not be a near-term "later"
and might never be a "later".

When we got done doing the standard, it had cost a lot of money to make
and even more in the community to adopt (change code to be ANSI compliant).
It cost just shy of a half million in salaries to produce, and I'm sure
at least as much to adopt.  That's money not spent producing product.
We met after 5 years to see whether we should open the standard for
change or just leave it laying, and the result was that it was thought to
be not broken enough to be worth the risk/cost of tinkering.  Yes, there
are things people would want to change and this probably among them.

Here I won't use the "we wanted to leave it to the marketplace" argument
but rather the "we were willing to leave it to the marketplace" argument. :-)
That's less strong, I think all would agree.  With multiple inheritance
firmly in place, I'm confident that if the spec were open to tinkering,
we could fix this. But opening to tinkering might break 180 other things
that you didn't want broken, too, because democratic organizations are
strange and often vote in things that are inconsistent with any single
member's world view.  Democracy optimizes worst case design better  than
it optimizes best case design, which it really doesn't address at all.

So you've got what you've got for the nearterm and that's pretty much that.

Personally, I think the right thing is to have community consensus and I
wish the ALU would play a bigger role in that.  Properly acting as a block,
users could probably get some vendors to take note.  Vendors don't want
to make a random change for a random user whose personal sense of order is
offended by what they offer, but if they had reasonable confidence they were
talking to a group that represented enough users that they could make a change
once and then stand by it as "what the community wanted", ESPECIALLY if, as 
here, it was a compatible change and did not disturb the spec, I think
they would probably do it.  Certainly there'd be a better chance.

I hope that helps you understand and accept the status quo as the best
that could have been done then, and still for different reasons close
to the best we can do now, modulo the question of whether smaller
layered standards or just "conventions" could help.  That last is
still an open question, but numerous attempts by various people to
make progress in this area have failed, so it's not like it hasn't
been tried.  Ultimately, people speak with their wallets as to what
they really need, and I sense most people would rather write a few
#+/#- conditionals than spend real dollars trying to solve this issue
in an aesthetic way.  They may be just saying they have better uses
for their money in this struggling economy, and speaking practically,
I can't really say 100% that that's a bad thing...  CL is and always has
been a language that caters to pragmatic folks.
From: Juliusz Chroboczek
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <871yk9ix6v.fsf_-_@pps.jussieu.fr>
I don't really feel strongly on this issue; I'm almost sorry I've
mentioned it.  Just a quick comment to clarify.

KMP> [Moon] observed first that it isn't clear that it's good style,
KMP> for example, for people to be making decisions on the basis of
KMP> "unseen go tag" vs "unseen catch tag";

On the other hand, suppose I write code that needs to catch ``unseen
catch tag'' (for example, I'm throwing to code that may be invoked
from multiple places, and some of the callers are not trusted).
Should I, in this case, be prevented from getting into the debugger if
I mistyped a GO?

Basically, my point is that there are a number of exceptional
situations that may very well happen in correct code which ANSI CL
requires to be trapped (thus already putting the main burden on
implementations), but for which it doesn't specify anything beyond
ERROR.  Thus, making use of the requirement to signal an error
requires me to disable the debugger.

KMP> I will defend those cutbacks to this day, and I think even you
KMP> and others who want more elaboration would agree.

It's difficult to say without knowing what NES was like (the only Lisp
Machine manual I've ever had access to was the original MIT one).
Still, I wouldn't argue for a deep condition tree; just strict
subclasses of ERROR for every reliably signalled exceptional condition
that is not possible to check statically (and locally).

I agree with you that issues linked to the outside environment are
more tricky, and I won't comment on the rest of your message.

                                        Juliusz
From: Tim Bradshaw
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <nkjd73tq6mo.fsf@davros.tardis.ed.ac.uk>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> 
> On the other hand, suppose I write code that needs to catch ``unseen
> catch tag'' (for example, I'm throwing to code that may be invoked
> from multiple places, and some of the callers are not trusted).
> Should I, in this case, be prevented from getting into the debugger if
> I mistyped a GO?
> 
> [...]
> 
> It's difficult to say without knowing what NES was like (the only Lisp
> Machine manual I've ever had access to was the original MIT one).
> Still, I wouldn't argue for a deep condition tree; just strict
> subclasses of ERROR for every reliably signalled exceptional condition
> that is not possible to check statically (and locally).
> 

Mistyping a GO is exactly a condition that can be checked statically,
of course.

--tim
From: Juliusz Chroboczek
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <87669kncg1.fsf@pps.jussieu.fr>
TB> Mistyping a GO is exactly a condition that can be checked statically,
TB> of course.

Och aye.  Which is why there is no legitimate reason to want to trap
it, and I'm quite happy with it signalling ERROR.

On the other hand, there may arguably be legitimate reasons to want to
trap an uncaught throw, and it would be nice to be able to trap those
without disabling the debugger in case of a mistyped GO.

As I've said before, though, I really don't feel strongly about this
issue, and I would suggest that we should drop this thread.

                                        Juliusz
From: Tim Bradshaw
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <nkj4rp4ri4t.fsf@davros.tardis.ed.ac.uk>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> Och aye.  Which is why there is no legitimate reason to want to trap
> it, and I'm quite happy with it signalling ERROR.

Well, what I emant really was that this is some kind of conceptual
mistake.  Errors in GO targets should be caught at some time which is
(at least conceptually) before the code is actually executed - compile
time or .`interpreter-semantic-checking' time or something.  I definitely think that it is not safe to assume that for

(handler-case 
  (tagbody grib (go bar))
  (error () (format *debug-io* "~&here~%")))

`here' will ever be printed, even in `interpreted' code.


> As I've said before, though, I really don't feel strongly about this
> issue, and I would suggest that we should drop this thread.

Yes, neither do I, I'm just beign pedantic.

--tim
From: Raymond Wiker
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <86n12wn7j7.fsf@raw.grenland.fast.no>
Tim Bradshaw <···@tfeb.org> writes:

> Yes, neither do I, I'm just beign pedantic.
                              ^^^^^
                              being

        Sorry :-)

-- 
Raymond Wiker                        Mail:  ·············@fast.no
Senior Software Engineer             Web:   http://www.fast.no/
Fast Search & Transfer ASA           Phone: +47 23 01 11 60
P.O. Box 1677 Vika                   Fax:   +47 35 54 87 99
NO-0120 Oslo, NORWAY                 Mob:   +47 48 01 11 60

Try FAST Search: http://alltheweb.com/
From: Kent M Pitman
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <sfw7ku0zisc.fsf@world.std.com>
Raymond Wiker <·············@fast.no> writes:

> Tim Bradshaw <···@tfeb.org> writes:
> 
> > Yes, neither do I, I'm just beign pedantic.
>                               ^^^^^
>                               being
                               ^^^^^^^^
                               pedantic
From: Kent M Pitman
Subject: Re: Precise condition classes [was: Tail recursion & CL]
Date: 
Message-ID: <sfw8zegzivg.fsf@world.std.com>
Tim Bradshaw <···@tfeb.org> writes:

> Well, what I emant really was that this is some kind of conceptual
> mistake.  Errors in GO targets should be caught at some time which is
> (at least conceptually) before the code is actually executed - compile
> time or .`interpreter-semantic-checking' time or something.
>  I definitely think that it is not safe to assume that for
> 
> (handler-case 
>   (tagbody grib (go bar))
>   (error () (format *debug-io* "~&here~%")))
> 
> `here' will ever be printed, even in `interpreted' code.

This is the difference between control-error and program-error.
It's ill-defined which will get signaled, but the general 
case should be that program-error, when it IS signaled, will
happen at syntax time, not at runtime; control error (e.g., missing
catch tag) will happen at runtime.  There are situations where some things
you'd think might be program erros will be control errors, but I think not
vice versa.
From: Coby Beck
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <oxlw7.314880$8c3.57213905@typhoon.tampabay.rr.com>
"Erik Naggum" <····@naggum.net> wrote in message
·····················@naggum.net...
> * Juliusz Chroboczek
[reinserted]
KMP> It is specifically left to the market to sort out ...
> |
> | With all due respect, Kent, you overuse this argument.  Pushing it to
> | its logical extreme, we have no use for a carefully drafted Common
> | Lisp definition, because the market will choose right anyhow.
>
>   Since the argument _only_ makes sense within the framework of a standard,
>   I fail to see how "logical" it could possibly be to drop the context and
>   still believe you have the same argument.
>

Excellent point, I knew something was wrong with that argument, but couldn't
pinpoint it.  Letting the community come to a consensus and formalizing it is
not the same as not formalizing anything, obviously.

I don't think characterizing that as an "over used argument" is even close to
accurate.  It is a position Kent has stated was used in making design decisions
and as such can only be stated and explained, it is not an argument to be
debunked or proven.  We are free to disagree and/or discuss the merits of such
a position, but you can't refute it logically.

It seems to me to be a very reasonable and productive approach.

As an onlooker to this thread, unfamiliar with the theory of tail call
elimination, I have yet to see any convincing efforts to:
a. define what it is *exactly* pro-tail callers are proposing is added to the
standard.
b. answer any of the specific questions/objections/complications from those
opposed.

> | Alas, the said definition does not include guarantees about tail-call
> | elimination are not included in that definition; I would like to see such
> | guarantees included in a future version.
>
>   You will not see such guarantees in any future version.  If you want
>   this, there is Scheme.  So much of the rest of what you argue for are
>   signs of a Scheme freak that I also fail to see why you do not just work
>   happily within the Scheme community instead of unhappily in the Common
>   Lisp community?  What does it do for you to whine about tail-calls and
>   never get it?
>

This smacks so much of an "America, love it or leave it" kind of argument.

Not being familiar with Scheme outside of discussions in this forum, I am
blissfully unaware of the signs that give away a "Scheme Freak" and in my
ignorance I still find their arguments educational.  So there is at least some
good that comes out of "unhappy" lispers "whining" away.....

Coby

--
(remove #\space "coby . beck @ opentechgroup . com")
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwn132cff9.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> KMP> It is specifically left to the market to sort out ...
> 
> With all due respect, Kent, you overuse this argument.

This is not an argument.  It is, to the best of understanding, a fact.

Two major points follow.  If you find yourself hopelessly mired in the
first, skip to the second (there are separator markers) so you don't
lose it.

- - - - -

My personal technical position has generally been to add a great deal
more to the language than got in.  In CLHS, I've published the cleanup
issues that got accepted; you should see the heaps of things I have
proposed that did NOT get in, and you should hear the arguments
against them.

My position on this issue has been that tail recursion should be
something people could enable on a lexical basis with declarations.  I
don't plan to use it, but I understand that others might.  As long as
it's not invasive, I'm all for it.

The truth is that I spent a lot of time taking ideas from the Scheme
community and trying to get them into CL to narrow the gratuitous
distance between the two languages.  But that hasn't been seen as 
a priority.

The reasons for rejection vary. But two of the most common were "there's
no current practice" (i.e., let a vendor implement it and we'll see how
successful it was in practice) or "this might be painful for some vendor
in a way we don't understand" (i.e., let a vendor implement it and prove
to us that it doesn't get us in trouble).  The other most common reasons
for rejecting otherwise well-formed proposals were "it's too late, we
feature-froze in 1988" or "gee, the spec is getting kind of big, do we
really need this?" or "if you require that, it will cost me a lot of money
to implement, can't we leave it to vendors to decide if it's a priority?".

I try to fairly represent the reasons for various things getting
rejected, to the best of my recollection.  Steve Haflich and I were
there for most of it.  Barry was there for quite a bit of it, though
not quite as much.  Barry and Steve and I are pretty quick to jump on
each other if there's a mis-remembered element, and I think that keeps
the discussion pretty fair and as true to the record as one can be.

But the fact is that it was a major theme of the design to leave
vendors a fair amount of autonomy, especially in the transition from
CLTL to ANSI CL both because the first book had tried to overdefine
some things it shouldn't have, and also because we were already doing
a fair amount of new design with the condition system, clos, etc. and
we didn't want to take more risks than we had to.  One of Larry
Masinter's invaluable contributions to the standard was the creation
of the cleanup form and the requirement to talk about current
practice, among other things.  So the absence of current practice was
conspicuous both for the possibility that it might not be something
any customer was clamoring for (as judged by "enough to make a vendor
fear it was influencing sales", not as judged by "number of newsgroup
posts") and/or it might not be something well enough understood to know
the exact right way to do it or whether it had any ill effects.

This issue WAS discussed.  This issue may or may not have had a
specific proposal--I'm busy bringing my personal notes back online
from backup tape and I may soon be able to answer such questions more
reliably.  But this issue had no action taken for exactly the reason I
cited.

So the frequency with which I use that argument is really irrelevant.
It's like asking why things fall when you drop them and being annoyed
taht "gravity" is used too often to explain the answer.  Truth is truth.

- - - - -

Then again, maybe your real gripe is that "leave it to vendors" is too
vendor-centric an answer.  But to understand why THAT happened, you have
only to note that users were (and are) entitled to be (X3)J13 members,
but over the period of the design, nearly all user members decided it
wasn't worth the economic bother (that is, they decided to stop attending
and trust who was left to vote in their favor).  That left only vendors.
And vendors act in their own interests, so of COURSE the votes were
vendor-centric.  When you voluntarily give up your right to vote, you
deserve what you get.

Even as a vendor employee, and at some risk of annoying my employers,
I regarded this as a war between users and vendors and was angry at
the users for dropping out and apparently expecting vendors to vote
all the right ways.  I spoke out at public forums about this,
blatantly berating users at ALU meetings saying "why don't you stand
up to these vendors and operate as a block to get what you want?",
"why don't you show up and outvote these vendors?".  Honestly, I was
surprised I was not directly fired for my remarks, which in some
companies would have constituted insubordination and would have
merited immediate dismissal.  It is to the credit of both Symbolics
and Harlequin that they quietly permitted me this degree of public
disagreement with their own policies without being angry at me. But
there was a limit to what I could do, and the users didn't exactly
start streaming into meetings.  It was all I could do to get the CLIM
folks to show up at one meeting to argue for compiler optimizers when
vendors were of a mind not to include those in the language because
they each already had their own way of doing things and were not
motivated to impose a burden on themselves to add yet another facility
just for the purpose of satisfying people who didn't care to represent
their own concerns.

We went from about 60 meeting attendees (about 15-20 organizations plus some
alternates, more users than vendors) at the outset to about 8-10 meeting
attendees at the end, representing about 5 or 6 organizations, almost all
vendors.  I think Kim Barrett and Sandra Loosemore, key contributors to the
process, might have been individual members.  That change affected things.

In the end, fairness does not exist.  ANSI "defines" fairness.  Fairness
under their definition means "you had the right to be there, and you
weren't there, so your opinion doesn't matter".  That may not be your
personal defintiion of fair, but it is sufficient to overcome the anti-trust
issues which are the reasons for the creation of ANSI in the first place.
(Ordinarily, companies cannot conspire to control an industry, but allowing
others to freely enter and vote nullifies that concern; if no one wants to
come, the vendors aren't required to make them.  This detail transforms an
involuntary control situation to a voluntary one.)
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87y9mlt23z.fsf@pps.jussieu.fr>
KMP> It is specifically left to the market to sort out ...

[...]

KMP> This is not an argument.  It is, to the best of understanding, a fact.

Okay, I read you now.  You were not arguing why this sort of thing
should not get into a hypothetical future standard, but why it didn't
get in.

KMP> Two major points follow.  If you find yourself hopelessly mired in the
KMP> first [....]

Actually, your first point makes a lot of sense.  I think I understand
why X3J13 was not the right time to push tail-call elimination: it's
very difficult to specify right (and you didn't have a lot of manpo-
wer), and may require reengineering of already present functionality
on the part of vendors.  These seem like excellent reasons to me.

I am glad that you should agree that tail-call elimination is some-
thing that deserves at least serious consideration (I hope I am not
abusively putting words into your mouth here).  Please count me as a
happy CL user that would be even happier with guarantees about
tail-call elimination.  As Thomas Burdick noted, the fact that most
current CL implementations do perform tail-call elimination in some
form seems to indicate that I am not alone in this situation.

Regards,

                                        Juliusz
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwsncsvlfr.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> I am glad that you should agree that tail-call elimination is some-
> thing that deserves at least serious consideration (I hope I am not
> abusively putting words into your mouth here).

No, that's fine.  But see my other post of just a few moments ago on the
issue of specializing condition types, where I discuss the problem that
the community doesn't have a forum right now to consider such changes, and
doesn't seem likely to.

I'm not an official representative of any committee, btw.  Even when I was
I couldn't speak for them.  (X3)J13 speaks through its votes, not through
a publicist.  Members always just express their own opinions, nothing more.
I've resigned my membership of the committee because I think it's done
as much as it can do and I now support "other ways of moving ahead".
The committee still exists, though, and it may or may not have another
agenda--I don't know.  Maybe Steve Haflich of Franz, who was chair of it
last I heard, will join in with some remarks on what the status of the
committee's work or non-work is.  I'd heard some rumor of a vote to put
the committee into official hibernation, but I don't know if that vote was
taken or, if so, what the outcome might have been.
From: Jochen Schmidt
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9p35kl$btb$1@rznews2.rrze.uni-erlangen.de>
If you run your (loop) example on a laptop running on batteries
you will see in not so long time that even (loop) is doomed to
"terminate" because some resource is exhausted.

bye,
Jochen
 
--
http://www.dataheaven.de
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87itdrfq79.fsf@pps.jussieu.fr>
JS> If you run your (loop) example on a laptop running on batteries
JS> you will see in not so long time that even (loop) is doomed to
JS> "terminate" because some resource is exhausted.

I see.  It's actually useless to try to write reliable programs,
because my machine might very well run out of batteries.

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211570436970544@naggum.net>
* Juliusz Chroboczek <···@pps.jussieu.fr>
| JS> If you run your (loop) example on a laptop running on batteries
| JS> you will see in not so long time that even (loop) is doomed to
| JS> "terminate" because some resource is exhausted.
| 
| I see.  It's actually useless to try to write reliable programs,
| because my machine might very well run out of batteries.

  Juliusz, I find your approach in this thread to be irrational, and I
  suspect that it has some philosophical underpinnings that I cannot quite
  get a grip on.  You have removed the real world from your reasoning, and
  when people reintroduce it, they fall apart like they depend on a reality
  different from what is normally experienced.  There is something wrong
  with a philosophy when that happens.  In this case, your probably half-
  facetious response betrays, I think, a failure to differentiate between
  the normal and the exceptional.  This is akin to what we find in politics
  when people make no distinction between normal and emergency ethics --
  under normal circumstances, no person's life is in danger and endangering
  a person's life is wrong, but in an emergency, a person's life is in
  danger and it may be ethical to endanger another person's life, such as
  the attacker of the first person.  If you attempt to apply normal ethics
  to the emergency situation, you _will_ endanger someone's life and that
  is wrong whichever way you look at it, so there is no right answer,
  anymore, meaning your ethics has failed, but you cannot argue that your
  normal ethics has failed across the board -- it has only failed in an
  emergency.  Likewise with software: If your theory assumes a normal
  course of execution and termination, the theory _fails_ if apply it to
  exceptional termination, but that does not mean the theory is wrong apart
  from being abused for exceptional situations.

  This issue is of course getting more complex in software where the normal
  course of execution includes a large number of exceptional situations,
  but running out of resources is clearly such an emergency that no theory
  of reliable _software_ can deal with it.  The hardware exists.  It cannot
  be abstracted away while the theories are still expected to have their
  usual predictive power.  Regardless of how reliable your software is, if
  you are running it on a laptop computer in, say, Kabul, it just becomes
  so incredibly stupid to argue that the _software_ failed if the hardware
  evaporates and that it is useless to write reliable software when it
  might as well blow up on you, literally.  On the other hand, we do have
  people from vendors in this newsgroup who argue that just because you
  need sockets, which are not standardized, you might as well disobey some
  other parts of the standard because the standard is not perfect in its
  power to encompass the needs of the world.  I think it is the same bogus
  ideas that underlie the attitude to perfection and normalcy of both of
  you guys, that if you define a perfection that is inherently unattainable
  and impossible, you can go on griping about irrelevant flaws forever.

  As far as I can tell, Common Lisp is good enough that it attracts people
  who can make up a desire for an irrationally perfect world and thus never
  actually be satisfied with what they are able to get in any real life.
  For instance, you will never get a guarantee for tail-call merging in the
  standard, and it is _completely_ useless to ask for it or even pretend
  that you can get more than actual support for what you want from vendors.
  The standard is not at fault for not giving you the irrationally perfect.

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87pu7xt0mp.fsf@pps.jussieu.fr>
EN>   Juliusz, I find your approach in this thread to be irrational,
EN>   and I suspect that it has some philosophical underpinnings that
EN>   I cannot quite get a grip on.

Okay, I'll try to make my ``philosophical underpinnings'' explicit.

One of the early proponents of rigour in the discipline of programming
was Edsger W. Dijkstra.  Together with Hoare and other people,
Dijkstra developed a number of more or less formal techniques to aid
in the writing of reliable software.  (An important point to
understand is that a formal technique is often useful in an informal
setting: for instance, while Dijkstra introduced the notions of loop
invariant and weakest precondition in a formal framework, most good
programmers use them to reason informally about their programs.)

In one of his notes, Dijkstra mentions that when presenting his ideas,
he sometimes met the objection ``but what if the compiler (resp. the
hardware) is unreliable?''.  Dijkstra argued (and I fully agree with
this point) that this is immaterial to the argument at hand, and that
the person asking this question had completely missed the point.  The
fact that we use compilers with bugs, hardware that breaks at random
points, or power companies that arbitrarily decide that darkness is
the norm does not make the task to write reliable software any less
meritoric.

Similarly, I would argue that the fact that no Common Lisp implemen-
tation fully conforms to the standard, or that my Common Lisp imple-
mentation runs on hardware that may very well not survive the day
(touch wood) does not make the task of writing a rigorous and precise
specification for the language vain.  The existence of the Common Lisp
standard, which I regard as a brilliant example of how it is possible
to be intelectually rigorous without relying on formal tools, seems to
imply that at least part of the Common Lisp community shares this
view.

Regards,

                                        Juliusz
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211628484496411@naggum.net>
* Juliusz Chroboczek <···@pps.jussieu.fr>
| The existence of the Common Lisp standard, which I regard as a brilliant
| example of how it is possible to be intelectually rigorous without
| relying on formal tools, seems to imply that at least part of the Common
| Lisp community shares this view.

  I wonder if anyone does _not_ share it.  Seriously.  The resistance to
  "formal tools" is, however, pretty strong among those who have studied
  Scheme well and are sick and tired of completely fruitless formalisms
  that turn into huge implementation costs or restrictions on the freedom
  of the implementors.  If there is one lesson one learns from having dealt
  with many language specifications, it is that overspecifying is far worse
  than underspecifying.

  It seems to me that you are fighting an enemy that does not exist when
  you argue as if there are people who do not share what you have written
  and it seems pretty arrogant that yours is the only way to express or
  desire the values and results of "rigor".  I actually think _everybody_
  here are sufficiently well educated that you should presume that their
  objections to "formalism" is to their application, not the principles.
  Thus, it does not help to argue for the principles when peple are opposed
  to their application.  Quite the contrary, arguing for the principles
  when the application is challenged only makes people more hostile to the
  application, because it implies that you think the principles are not
  understood unless they are agreed to be applied just your way.

  I also think what Tim Bradshaw said here is very relevant: As long as you
  only ask for something that meet objections, there will be no progress.
  Write up an _exact_ proposal for what you want the community to agree to,
  and you might actually find people able to accomodate you, but if you
  only demand something that people have serious objections to, there is no
  way anyone can figure out what you are _really_ after, i.e., what you
  would be happy if you got.  In general, if people cannot figure out when
  your complaints will end, you lose credibility fast, as it is essentially
  indistinguishable from an attitude problem.

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <871yka5azp.fsf@pps.jussieu.fr>
EN>   I wonder if anyone does _not_ share it.  Seriously.  The resistance to
EN>   "formal tools" is, however...

Dear Erik,

May I most respectfully suggest that you might want to actually read
what people say before following up?

Specifically, I was not arguing in favour of formal tools.  I was
speaking of the need to clearly separate language design from battery
technology.

                                        Juliusz
From: Jochen Schmidt
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9q4rkp$3k9$1@rznews2.rrze.uni-erlangen.de>
Juliusz Chroboczek wrote:

> EN>   I wonder if anyone does _not_ share it.  Seriously.  The resistance
> to
> EN>   "formal tools" is, however...
> 
> Dear Erik,
> 
> May I most respectfully suggest that you might want to actually read
> what people say before following up?
> 
> Specifically, I was not arguing in favour of formal tools.  I was
> speaking of the need to clearly separate language design from battery
> technology.

The point was not about battery technology - It was only to show that there 
is everytime a case with finite resources that can occur and I think we all 
agree how ridiculous it would be to standardise a language to
be used only on computers with powerlines (or better perpetuum mobiles...)
You argue that it is not possible for you to use tail-recursion in CL 
because the ANSI-Standard does not _mandate_ it. But you _have_ the choose 
to either use an implementation that offers this feature (there are even 
free ones like CMUCL offering this!) or switch to another language that 
(may) offer it (Scheme...). I don't know what would change if we would now 
specify it to be a part of the language - the implementations having it 
will still have it and those who did not have it will probably keep not 
having it with the price of being a bit less standard compliant. If someone 
has not the resources, ability or interest to implement it, writing it into 
the standard will not change it! We even _have_ features perfectly well 
defined in the standard that are not available in some implementations!
So you have no guarantees - even if it's in the standard.

ciao,
Jochen

--
http://www.dataheaven.de
From: Erik Naggum
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <3211825004083939@naggum.net>
* Juliusz Chroboczek
| May I most respectfully suggest that you might want to actually read
| what people say before following up?

  The only excuse you have for your incredible arrogance is that you lack
  the English language skills to understand when people who argue against
  you understand more of the topic at hand than you do.  Going back through
  your tremendously snotty articles, you have consistently missed the point
  that people have tried to provide you with ever more detail and examples
  and arguments, but it appears that your inability to grasp arguments that
  run counter to your existing beliefs is not solely caused by an abject
  failure to listen -- it has deeper reasons.  I suggest that you talk to
  someone who can help you understand that people who argue against you may
  have a slightly different set of premises than you do and that even if
  your divine intuition about the eternal truth of these premises seem so
  unquestionable to yourself, they are in fact _bunk_ when seen by others
  in the light of your failure to address criticism and your failure to
  grasp that people in fact _do_ read what arrogant nonsense you spout.

///
-- 
  My hero, George W. Bush, has taught me how to deal with people.  "Make no
  mistake", he has said about 2500 times in the past three weeks, and those
  who make mistakes now feel his infinite wrath, or was that enduring care?
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwr8scvlby.fsf@world.std.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> Similarly, I would argue that the fact that no Common Lisp implemen-
> tation fully conforms to the standard,

The standard does not require that you don't have a resource problem.
Conformance is straightforward.  

 (defun common-lisp ()
   (write-string "Resources exhausted."))

There.  That's a fully conforming implementation that stands as a 
counterexample to your claim. Heh...

You might not like this as a standard of conformance, but at least we
tried to set achievable goals for implementors. ;-)
From: Jochen Schmidt
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9pt9lh$bm4$1@rznews2.rrze.uni-erlangen.de>
Juliusz Chroboczek wrote:

> JS> If you run your (loop) example on a laptop running on batteries
> JS> you will see in not so long time that even (loop) is doomed to
> JS> "terminate" because some resource is exhausted.
> 
> I see.  It's actually useless to try to write reliable programs,
> because my machine might very well run out of batteries.

yes you got it ;-)

ciao,
Jochen

--
http://www.dataheaven.de
From: Jochen Schmidt
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9pufhk$2pv$1@rznews2.rrze.uni-erlangen.de>
Juliusz Chroboczek wrote:

> JS> If you run your (loop) example on a laptop running on batteries
> JS> you will see in not so long time that even (loop) is doomed to
> JS> "terminate" because some resource is exhausted.
> 
> I see.  It's actually useless to try to write reliable programs,
> because my machine might very well run out of batteries.

The interesting thing is what you do to make it useful again: you buy
a laptop with longer runtime and you ensure that you load the batteries
before all goes down. All this things are in no way standard in Scheme
but it seems to work in reality. So why should it be a problem to ensure
using a CL that supports tail-calls?

ciao,
Jochen

--
http://www.dataheaven.de
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <r8swadq7.fsf@itasoftware.com>
Juliusz Chroboczek <···@pps.jussieu.fr> writes:

> KMP> But the issue of (progn (loop) (foo))
> KMP> is not a question of unbounded resources, it's a question of 
> KMP> infinite resources.  (foo) will never be reached.
> 
> Yes, we fully agree about that.  There is no such thing as an actually
> infinite amount of work.  This was emphatically not the issue I was
> trying to get you to comment on.
> 
> >> If you prefer, we were not even considering the notion of actual
> >> infinity.
> 
> KMP> Then in my parlance, you are not addressing the form '(loop)'.
> 
> We agree about that, I am definitely not.
> 
> As you say, there is no ambiguity.  With any reasonable semantics, the
> programs (loop) and (progn (loop) (error "Foo!")) are equivalent.

Normal order semantics are unreasonable?  First I've heard of that.

> On the other hand, consider the following:
> 
>   (defun foo ()
>     (foo))
> 
> and suppose that you compile FOO without tail-call elimination.  Does
> FOO terminate?
> 
> The obvious answer is that my machine has finite memory, hence FOO
> will run out of stack at some point, hence FOO terminates on my
> machine.  This answer invokes a specific maximum stack size, and so is
> not acceptable as part of a language definition.

It also assumes that each call to (foo) consumes some amount of stack.
This is an unnecessary requirement.
From: Kent M Pitman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <sfwsndcu0ss.fsf@world.std.com>
···@itasoftware.com writes:

> > KMP> Then in my parlance, you are not addressing the form '(loop)'.
> > 
> > We agree about that, I am definitely not.
> > 
> > As you say, there is no ambiguity.  With any reasonable semantics, the
> > programs (loop) and (progn (loop) (error "Foo!")) are equivalent.
> 
> Normal order semantics are unreasonable?  First I've heard of that.

I believe the context was "reasonable semantics for CL" -- i.e.,
"reasonable transcription of the semantics which are present and fixed
for CL even if not specified formally".  The absence of a formal
semantics presentation is not license to do new design.

It's been a long time since I had to deal with these.  Is "normal
order" the one where you don't have to do things in order?  If so, you
should remember that "reasonableness" is like "goodness"--it depends
on context and is not a universal property of the universe.  In CL,
evaluation works left to right, so you have to first dispense with one
form to get to the next. If you are unable to dispense with a certain
form, you're done.
 
> > On the other hand, consider the following:
> > 
> >   (defun foo ()
> >     (foo))
> > 
> > and suppose that you compile FOO without tail-call elimination.  Does
> > FOO terminate?
> > 
> > The obvious answer is that my machine has finite memory, hence FOO
> > will run out of stack at some point, hence FOO terminates on my
> > machine.  This answer invokes a specific maximum stack size, and so is
> > not acceptable as part of a language definition.

The language definition does not call "out of astack" a semantic
erorr but a resource limitation.  Read about the difference between
the classes SERIOUS-CONDITION and ERROR.  It is not an error to do this,
it just probably will result in a serious condition (stack resource xhausted);
though it might result also in lots of time passing and then the machine
having to be rebooted; that's less likely to get trapped but is also
a resource limitation.  Neither is a semantic error per se.

> It also assumes that each call to (foo) consumes some amount of stack.
> This is an unnecessary requirement.

It doesn't matter.  CL semantics deal neither with stack nor time, so either
of those commodities can run out and you have no control of it.  This is not
to say those issues are not important, but they are left, as a matter of
language design, to the vendor.  A vendor is not prohibited from doing
tail call elimination, they are simply not required to.

What is not left to the vendor is the right to hop over inconvenient
statements in order to get to one that is more convenient...
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <vgi8tvjh.fsf@itasoftware.com>
Kent M Pitman <······@world.std.com> writes:

> ···@itasoftware.com writes:
> 
> > > KMP> Then in my parlance, you are not addressing the form '(loop)'.
> > > 
> > > We agree about that, I am definitely not.
> > > 
> > > As you say, there is no ambiguity.  With any reasonable semantics, the
> > > programs (loop) and (progn (loop) (error "Foo!")) are equivalent.
> > 
> > Normal order semantics are unreasonable?  First I've heard of that.
> 
> I believe the context was "reasonable semantics for CL" -- i.e.,
> "reasonable transcription of the semantics which are present and fixed
> for CL even if not specified formally".  

Ah, I was thrown by your use of the work `any'.

> It's been a long time since I had to deal with these.  Is "normal
> order" the one where you don't have to do things in order?  

Technically, normal order is when you reduce the leftmost reducable
redex at each step.  You can look upon this as a `lazy evaluation'
model.  When you have no more reducable redexes, you have found the
`normal form'.  Both normal order and applicative order will reduce to
the same normal form (when both reduce), but there are some
expressions for which applicative order reduction won't reduce.
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <dh38zeyhpu1.fsf@glory.dcs.ed.ac.uk>
KMP> This is not to say those issues are not important, but they are
KMP> left, as a matter of language design, to the vendor.  A vendor is
KMP> not prohibited from doing tail call elimination, they are simply
KMP> not required to.

I understand that.  I wasn't complaining about the vendors' behaviour,
but about the guarantees given by the standard being too weak for my
personal programming style.

People like you have taught me to program by the book, not for the
implementation.  The lack of strong guarantees about tail-call
elimination in the book forces me to program for the implementation
(or to give up on a number of techniques that I happen to find useful).

Of course, I am not implying that you (plural) have done anything
wrong when designing ANSI Common Lisp.  I am quite willing to believe
that this was the right compromise at the time (possibly for the very
same reasons as the ones you gave recently for the omission of a MOP).

Regards,

                                        Juliusz
From: Andy Freeman
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <8bbd9ac3.0110091326.503b7d78@posting.google.com>
···@itasoftware.com wrote in message news:<············@itasoftware.com>...
> Normal order semantics are unreasonable?  First I've heard of that.

You hang out in the wrong bars.

Normal order semantics has nice properties for certain kinds of
analysis on certain kinds of programs.  However, it's hard to
keep those properties and write certain kinds of programs in a
"natural" way.  Since more people are interested in those programs
than in those properties....

Normal order semantics also has a distinct lack of locality.
That's "inefficient" for machines (which is not a big issue) and
inefficient for humans (which is a HUGE issue).

I like to play with myself as much as the next guy, but the formal
language community's mathturbating has gotten the rest of us C, C++,
and Java.  The popularity of those languages is not irrational - they,
and their implementations, are better at what most users need than
"good" languages; that superiority is the fault of the designers of
"good" languages.

-andy
From: ···@itasoftware.com
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <adyzp8v1.fsf@itasoftware.com>
······@earthlink.net (Andy Freeman) writes:

> ···@itasoftware.com wrote in message news:<············@itasoftware.com>...
> > Normal order semantics are unreasonable?  First I've heard of that.
> 
> You hang out in the wrong bars.

I suspected as much.  Where do the hot babes who hack lisp hang out?

> Normal order semantics has nice properties for certain kinds of
> analysis on certain kinds of programs.  However, it's hard to
> keep those properties and write certain kinds of programs in a
> "natural" way.  Since more people are interested in those programs
> than in those properties....

Agreed, but that doesn't make the semantics unreasonable, just
impractical.  (And note that every applicative order language has a
few normal-order constructs in it.)

As a followup, people who are interested in the debate over preserving
termination characteristics when compiling might want to read these
papers: 

@inproceedings{ rozas169695taming,
    author = "Guillermo Juan Rozas",
    title = "Taming the {Y} operator",
    pages = "226--234",
    url = "citeseer.nj.nec.com/169695.html" }

@inproceedings{ weise91automatic,
    author = "D. Weise and R. Conybeare and E. Ruf and S. Seligman",
    title = "Automatic Online Partial Evaluation",
    booktitle = "Functional Programming Languages and Computer Architecture, Cambridge, Massachusetts, August 1991 (Lecture Notes in Computer Science, vol. 523)",
    publisher = "Berlin:\ Springer-Verlag",
    editor = "J. Hughes",
    pages = "165--191",
    year = "1991"
}

@misc{ mulmuley87full,
  author = "K. Mulmuley",
  title = "Full Abstraction and Semantic Equivalence",
  text = "K. Mulmuley. Full Abstraction and Semantic Equivalence. MIT Press, 1987.",
  year = "1987" }
From: Valeriy E. Ushakov
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <9pv27f$2h1u$1@news.spbu.ru>
Juliusz Chroboczek <···@pps.jussieu.fr> wrote:

> On the other hand, consider the following:
> 
>   (defun foo ()
>     (foo))
> 
> and suppose that you compile FOO without tail-call elimination.  Does
> FOO terminate?
[...]
> The point I'm trying to make is that FOO terminates when compiled
> without tail call elimination, and that FOO does not terminate if
> compiled with tail call elimination.  Thus, tail call elimination is
> not a mere optimisation, but a semantic feature.

Have you considered the possibility of call frames allocated in the
heap and being garbage-collectable?  In that case the tail-call
merging of the tail self-calls is *only* an optimization that saves
you a trouble of allocating new call frame and gc'ing the previous one
sometime later when gc is ran.

I think this was actually done for some functional programming
language (istr it was TIM (three instruction machine), but I just
don't remember...).

SY, Uwe
-- 
···@ptc.spbu.ru                         |       Zu Grunde kommen
http://www.ptc.spbu.ru/~uwe/            |       Ist zu Grunde gehen
From: Juliusz Chroboczek
Subject: Re: Tail recursion & CL
Date: 
Message-ID: <87eloa5e4g.fsf@pps.jussieu.fr>
VU> Have you considered the possibility of call frames allocated in the
VU> heap and being garbage-collectable?  In that case the tail-call
VU> merging of the tail self-calls is *only* an optimization that saves
VU> you a trouble of allocating new call frame and gc'ing the previous one
VU> sometime later when gc is ran.

No.  It's easier to do in that case, but you still need the compiler
to explicitly recognise tail calls.

With heap-allocated activation frames, there is a variable, known
e.g. as the ``current activation frame pointer'' (CFP), which points
at the currently executing function's activation frame.  Every frame
contains a ``previous activation frame'' field (sometimes known as the
``continuation''), which points at the caller's activation frame.
Thus, all the currently active activation frames are structured in a
linked list, known as the ``dynamic chain,'' and being the equivalent
of the ``call stack'' of stack-based systems.

Because the CFP is treated as a root by the GC, and because all the
active activation frames are linked by their previous frame pointers,
none of the active activation frames can be GCd.

When a call is made, first the IP is stored in the current activation
frame.  Then a new activation frame is allocated, its ``previous''
field is set to the value of the CFP, and the CFP is set to the new
activation frame.  (This is isomorphic to pushing a new frame on a
stack.)

Now consider what happens on a (non-merged) tail call.  The previous
field of the new frame is set to the current frame.  Yet, the current
frame does not serve any important purpose, as it will immediately be
disactivated *after* the callee has terminated.  However, the caller's
frame will remain on the dynamic chain until the callee returns, thus
preventing it from being GCd.

The trick, in this case, is simply to remove the caller's activation
frame from the chain *before* the callee is invoked.  This is as
simple as setting the previous field of the callee's frame to the
*previous* activation frame, rather than to the current one.

Normal call sequence:              Merged tail call:

  callee's --+                         callee's --+
             |                                    |
  caller's <-+                         caller's   |
                                                  |
  previous                             previous <-+

This enables the caller's frame to potentially be GCd before the
callee returns.

In summary: in systems with heap-based allocation of the dynamic chain
tail call merging consists in simply changing the value of a single
pointer.  However, this is still something that needs to be explicitly
taken care of by the compiler.

                                        Juliusz

P.S. ``Call-tail merging'' adopted.  Thanks.