Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
Tail recursion & CL
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  Messages 51 - 75 of 231 - Collapse all  -  Translate all to Translated (View all originals) < Older  Newer >
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Rahul Jain  
View profile  
 More options Oct 1 2001, 2:30 am
Newsgroups: comp.lang.lisp
From: Rahul Jain <rj...@rice.edu>
Date: 01 Oct 2001 01:25:30 -0500
Local: Mon, Oct 1 2001 2:25 am
Subject: Re: Tail recursion & CL
t...@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 -=- mailto:rahul-j...@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.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Kent M Pitman  
View profile  
 More options Oct 1 2001, 3:47 am
Newsgroups: comp.lang.lisp
From: Kent M Pitman <pit...@world.std.com>
Date: Mon, 1 Oct 2001 07:46:03 GMT
Local: Mon, Oct 1 2001 3:46 am
Subject: Re: Tail recursion & CL

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.  


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Bruce Hoult  
View profile  
 More options Oct 1 2001, 4:51 am
Newsgroups: comp.lang.lisp
From: Bruce Hoult <br...@hoult.org>
Date: Mon, 01 Oct 2001 20:51:08 +1200
Local: Mon, Oct 1 2001 4:51 am
Subject: Re: Tail recursion & CL
In article <sfwk7yfsszo....@world.std.com>, Kent M Pitman

<pit...@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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Tim Bradshaw  
View profile  
 More options Oct 1 2001, 6:04 am
Newsgroups: comp.lang.lisp
From: Tim Bradshaw <t...@cley.com>
Date: 01 Oct 2001 11:03:56 +0100
Local: Mon, Oct 1 2001 6:03 am
Subject: Re: Tail recursion & CL

* 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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Bruce Hoult  
View profile  
 More options Oct 1 2001, 6:56 am
Newsgroups: comp.lang.lisp
From: Bruce Hoult <br...@hoult.org>
Date: Mon, 01 Oct 2001 22:56:46 +1200
Local: Mon, Oct 1 2001 6:56 am
Subject: Re: Tail recursion & CL
In article <ey3snd3r81f....@cley.com>, Tim Bradshaw <t...@cley.com>
wrote:

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Kent M Pitman  
View profile  
 More options Oct 1 2001, 11:01 am
Newsgroups: comp.lang.lisp
From: Kent M Pitman <pit...@world.std.com>
Date: Mon, 1 Oct 2001 14:58:35 GMT
Local: Mon, Oct 1 2001 10:58 am
Subject: Re: Tail recursion & CL

Bruce Hoult <br...@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.

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.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Thomas F. Burdick  
View profile  
 More options Oct 1 2001, 4:45 pm
Newsgroups: comp.lang.lisp
From: t...@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick)
Date: 01 Oct 2001 13:45:17 -0700
Local: Mon, Oct 1 2001 4:45 pm
Subject: Re: Tail recursion & CL
Kent M Pitman <pit...@world.std.com> writes:

Say what?  I was responding to the following:

Rahul Jain <rj...@rice.edu> writes:
> Francois-Rene Rideau <fare+NOS...@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.

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Bruce Hoult  
View profile  
 More options Oct 1 2001, 5:44 pm
Newsgroups: comp.lang.lisp
From: Bruce Hoult <br...@hoult.org>
Date: Tue, 02 Oct 2001 09:44:24 +1200
Local: Mon, Oct 1 2001 5:44 pm
Subject: Re: Tail recursion & CL
In article <sfwk7yfo19g....@world.std.com>, Kent M Pitman

<pit...@world.std.com> wrote:
> Bruce Hoult <br...@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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Erik Naggum  
View profile  
 More options Oct 1 2001, 7:40 pm
Newsgroups: comp.lang.lisp
From: Erik Naggum <e...@naggum.net>
Date: Mon, 01 Oct 2001 23:40:39 GMT
Local: Mon, Oct 1 2001 7:40 pm
Subject: Re: Tail recursion & CL
* 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.

///


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Francois-Rene Rideau  
View profile  
 More options Oct 1 2001, 7:44 pm
Newsgroups: comp.lang.lisp
From: Francois-Rene Rideau <fare+NOS...@tunes.org>
Date: 02 Oct 2001 01:44:36 +0200
Local: Mon, Oct 1 2001 7:44 pm
Subject: Re: Tail recursion & CL
t...@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é


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Bruce Hoult  
View profile  
 More options Oct 2 2001, 12:11 am
Newsgroups: comp.lang.lisp
From: Bruce Hoult <br...@hoult.org>
Date: Tue, 02 Oct 2001 16:11:46 +1200
Local: Tues, Oct 2 2001 12:11 am
Subject: Re: Tail recursion & CL
In article <3210968437468...@naggum.net>, Erik Naggum <e...@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:

  <bruce-76CFD2.22564601102...@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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Erik Naggum  
View profile  
 More options Oct 2 2001, 4:38 am
Newsgroups: comp.lang.lisp
From: Erik Naggum <e...@naggum.net>
Date: Tue, 02 Oct 2001 08:38:30 GMT
Local: Tues, Oct 2 2001 4:38 am
Subject: Re: Tail recursion & CL
* 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.

///


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Bruce Hoult  
View profile  
 More options Oct 2 2001, 7:11 am
Newsgroups: comp.lang.lisp
From: Bruce Hoult <br...@hoult.org>
Date: Tue, 02 Oct 2001 23:11:18 +1200
Local: Tues, Oct 2 2001 7:11 am
Subject: Re: Tail recursion & CL
In article <3211000707208...@naggum.net>, Erik Naggum <e...@naggum.net>
wrote:

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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Erik Naggum  
View profile  
 More options Oct 2 2001, 9:34 am
Newsgroups: comp.lang.lisp
From: Erik Naggum <e...@naggum.net>
Date: Tue, 02 Oct 2001 13:34:18 GMT
Local: Tues, Oct 2 2001 9:34 am
Subject: Re: Tail recursion & CL
* 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.

///


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Kent M Pitman  
View profile  
 More options Oct 2 2001, 10:42 am
Newsgroups: comp.lang.lisp
From: Kent M Pitman <pit...@world.std.com>
Date: Tue, 2 Oct 2001 14:39:27 GMT
Local: Tues, Oct 2 2001 10:39 am
Subject: Re: Tail recursion & CL

Bruce Hoult <br...@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
...

read more »


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Tim Bradshaw  
View profile  
 More options Oct 2 2001, 11:04 am
Newsgroups: comp.lang.lisp
From: Tim Bradshaw <t...@tfeb.org>
Date: 02 Oct 2001 16:03:21 +0100
Local: Tues, Oct 2 2001 11:03 am
Subject: Re: Tail recursion & CL

Erik Naggum <e...@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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Juliusz Chroboczek  
View profile  
 More options Oct 8 2001, 10:30 am
Newsgroups: comp.lang.lisp
From: Juliusz Chroboczek <j...@pps.jussieu.fr>
Date: 07 Oct 2001 16:07:43 +0200
Local: Sun, Oct 7 2001 10:07 am
Subject: Re: Tail recursion & CL

>> 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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Juliusz Chroboczek  
View profile  
 More options Oct 8 2001, 10:30 am
Newsgroups: comp.lang.lisp
From: Juliusz Chroboczek <j...@pps.jussieu.fr>
Date: 07 Oct 2001 16:24:01 +0200
Local: Sun, Oct 7 2001 10:24 am
Subject: Re: Tail recursion & CL
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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Juliusz Chroboczek  
View profile  
 More options Oct 8 2001, 10:30 am
Newsgroups: comp.lang.lisp
From: Juliusz Chroboczek <j...@pps.jussieu.fr>
Date: 07 Oct 2001 16:45:34 +0200
Local: Sun, Oct 7 2001 10:45 am
Subject: Re: Tail recursion & CL
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?


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Juliusz Chroboczek  
View profile  
 More options Oct 8 2001, 10:30 am
Newsgroups: comp.lang.lisp
From: Juliusz Chroboczek <j...@pps.jussieu.fr>
Date: 07 Oct 2001 16:59:13 +0200
Local: Sun, Oct 7 2001 10:59 am
Subject: Re: Tail recursion & CL
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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Juliusz Chroboczek  
View profile  
 More options Oct 8 2001, 10:30 am
Newsgroups: comp.lang.lisp
From: Juliusz Chroboczek <j...@pps.jussieu.fr>
Date: 07 Oct 2001 17:02:50 +0200
Local: Sun, Oct 7 2001 11:02 am
Subject: Re: Tail recursion & CL
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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Erik Naggum  
View profile  
 More options Oct 8 2001, 11:12 am
Newsgroups: comp.lang.lisp
From: Erik Naggum <e...@naggum.net>
Date: Mon, 08 Oct 2001 15:12:34 GMT
Local: Mon, Oct 8 2001 11:12 am
Subject: Re: Tail recursion & CL
* 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?


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Kent M Pitman  
View profile  
 More options Oct 8 2001, 11:37 am
Newsgroups: comp.lang.lisp
From: Kent M Pitman <pit...@world.std.com>
Date: Mon, 8 Oct 2001 15:36:10 GMT
Local: Mon, Oct 8 2001 11:36 am
Subject: Re: Tail recursion & CL

Juliusz Chroboczek <j...@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.)


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Coby Beck  
View profile  
 More options Oct 8 2001, 1:39 pm
Newsgroups: comp.lang.lisp
From: "Coby Beck" <cb...@mercury.bc.ca>
Date: Mon, 08 Oct 2001 17:39:32 GMT
Local: Mon, Oct 8 2001 1:39 pm
Subject: Re: Tail recursion & CL

"Erik Naggum" <e...@naggum.net> wrote in message

news:3211542753013083@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")


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Thomas F. Burdick  
View profile  
 More options Oct 8 2001, 5:38 pm
Newsgroups: comp.lang.lisp
From: t...@famine.OCF.Berkeley.EDU (Thomas F. Burdick)
Date: 08 Oct 2001 14:38:56 -0700
Local: Mon, Oct 8 2001 5:38 pm
Subject: Re: Tail recursion & CL

Erik Naggum <e...@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.

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Messages 51 - 75 of 231 < Older  Newer >
« Back to Discussions « Newer topic     Older topic »