gyro@cymbal.reasoning.COM (Scott Layson Burson) (02/12/91)
This is a follow-up to my previous message about a beneficial property of tail-recursion elimination vis-a-vis garbage collection. Jim McDonald of Lucid has pointed out to me that my message might be taken to imply that the benefit I was describing is somehow unique to Scheme, and couldn't be had in an implementation of Common Lisp even if that implementation supported full tail-recursion elimination, as do, for instance, Lucid CL and Coral CL. I didn't mean to say that; I meant to be claiming this benefit for tail-recursion elimination independent of language. It's just that the CL I use the most is a more traditional implementation, and so I sloppily thought of that as a property of the language, which it isn't. -- Scott
harlan@copper.ucs.indiana.edu (Pete Harlan) (02/14/91)
gyro@cymbal.reasoning.COM (Scott Layson Burson) writes:
[tail-recursion isn't a property of a language, but an implementation concern]
Certainly a compiler for any language may perform tail-call
optimization, and in this respect it is a language-independent issue.
However, when a *language*, rather than a compiler, guarantees an
optimization, it opens the door for a programming style that might not
be a portable program in the language if the optimization were not
guaranteed.
W.r.t. tail-call optimization, I can write a continuation-passing
style program in Scheme and it is correct; if I transliterate it to
Common Lisp, the correctness of the program depends on whether the CL
compiler performs tail-call optimization. Thus the program can't be
properly called a CL program.
In other words, the Scheme language includes CPS programs, while
Common Lisp does not. The difference is an 'optimization', but this
only proves that when you guarantee an optimization for a language,
rather than a compiler, you (might) expand the set of valid programs
for that language.
Peter "I Love Passing Continuations Around" Harlan
Indiana University harlan@copper.ucs.indiana.edu
[Perhaps the problem lies in language specifications ignoring the
concept of finite computer memory? This makes it difficult to even
define what tail-call optimization might mean. In any case, this is
a bug, not a feature, in language specifications, since memory is
indeed limited.]
carlton@husc10.harvard.edu (david carlton) (02/16/91)
In article <harlan.666514120@copper> harlan@copper.ucs.indiana.edu (Pete Harlan) writes:
Certainly a compiler for any language may perform tail-call
optimization, and in this respect it is a language-independent issue.
However, when a *language*, rather than a compiler, guarantees an
optimization, it opens the door for a programming style that might not
be a portable program in the language if the optimization were not
guaranteed.
really? the scheme semantics doesn't mention tail recursion anywhere
that i can tell, which would seem to imply that optimizing it out
doesn't change the actual meaning of a program. indeed, i have a hard
time seeing how it could, since converting a tail-recursive function
call to a jump merely involves throwing out information that is
useless, so it is hard for me to see how it could possibly change the
meaning of anything. could you please send a me program (written in
scheme) that works in scheme but wouldn't if scheme didn't handle tail
recursion properly, so i can see what you are talking about?
W.r.t. tail-call optimization, I can write a continuation-passing
style program in Scheme and it is correct; if I transliterate it to
Common Lisp, the correctness of the program depends on whether the CL
compiler performs tail-call optimization. Thus the program can't be
properly called a CL program.
In other words, the Scheme language includes CPS programs, while
Common Lisp does not. The difference is an 'optimization', but this
only proves that when you guarantee an optimization for a language,
rather than a compiler, you (might) expand the set of valid programs
for that language.
again, i am confused. writing programs in a CPS style would be
horribly wasteful of memory if a program didn't optimize
tail-recursive function calls into jumps, but it should still give the
same result. unless, of course, the implementation runs out of
memory. so i suppose that there are programs that are valid scheme
but are invalid without this optimization, but the only such programs
are ones that would run forever. are there any others?
david carlton
carlton@husc9.harvard.edu
hanche@imf.unit.no (Harald Hanche-Olsen) (02/17/91)
In article <CARLTON.91Feb15122042@husc10.harvard.edu> carlton@husc10.harvard.edu (david carlton) writes: In article <harlan.666514120@copper> harlan@copper.ucs.indiana.edu (Pete Harlan) writes: Certainly a compiler for any language may perform tail-call optimization, and in this respect it is a language-independent issue. However, when a *language*, rather than a compiler, guarantees an optimization, it opens the door for a programming style that might not be a portable program in the language if the optimization were not guaranteed. really? the scheme semantics doesn't mention tail recursion anywhere that i can tell, which would seem to imply that optimizing it out doesn't change the actual meaning of a program. indeed, i have a hard time seeing how it could, since converting a tail-recursive function call to a jump merely involves throwing out information that is useless, so it is hard for me to see how it could possibly change the meaning of anything. On a machine with infinite memory, optimizing tail recursion does not change the meaning of programs. However, in Scheme you can write a loop using a tail call and the loop can execute millions of times without running out of memory if it can do it once. I'd say that whether or not the program runs out of memory changes its meaning, whether the semantics say so or not ... - Harald Hanche-Olsen <hanche@imf.unit.no> Division of Mathematical Sciences The Norwegian Institute of Technology N-7034 Trondheim, NORWAY
pcg@cs.aber.ac.uk (Piercarlo Grandi) (02/19/91)
On 16 Feb 91 23:24:34 GMT, hanche@imf.unit.no (Harald Hanche-Olsen) said:
hanche> In article <CARLTON.91Feb15122042@husc10.harvard.edu> carlton@husc10.harvard.edu (david carlton) writes:
carlton> really? the scheme semantics doesn't mention tail recursion
carlton> anywhere that i can tell, which would seem to imply that
carlton> optimizing it out doesn't change the actual meaning of a
carlton> program.
Actually the english text (as opposed to the denotational semantics) of
the 3.99 scheme report mandates tail recursion in the implementation
wherever possible. The denotational semantics are only *part* of the
scheme report. Clearly tail recursion does not make a difference in the
world of mathematics, but scheme is not lambda calculus; the report
defines the behaviour of conforming scheme *language implementations*,
to the point of prescribing specific syntax and giving various levels of
conformance.
The scheme report is essentially the specification of scheme's 'eval'
function (which is never explicitly mentioned :->), and some "quality of
implementation" requirements *are* indeed explicitly made.
hanche> I'd say that whether or not the program runs out of memory
hanche> changes its meaning, whether the semantics say so or not ...
Well said! However, more than its meaning, it changes its behaviour, and
here we are skating on very thin ice. The semantics of "bottom" and of
partial functions are not that easy to agree about :-).
--
Piercarlo Grandi | ARPA: pcg%uk.ac.aber.cs@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth | UUCP: ...!mcsun!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk
gyro@cymbal.reasoning.COM (Scott Layson Burson) (02/19/91)
Date: 15 Feb 91 17:20:42 GMT
From: david carlton <carlton@husc10.harvard.edu>
In article <harlan.666514120@copper> harlan@copper.ucs.indiana.edu (Pete Harlan) writes:
Certainly a compiler for any language may perform tail-call
optimization, and in this respect it is a language-independent issue.
However, when a *language*, rather than a compiler, guarantees an
optimization, it opens the door for a programming style that might not
be a portable program in the language if the optimization were not
guaranteed.
really? the scheme semantics doesn't mention tail recursion anywhere
that i can tell, which would seem to imply that optimizing it out
doesn't change the actual meaning of a program. indeed, i have a hard
time seeing how it could, since converting a tail-recursive function
call to a jump merely involves throwing out information that is
useless, so it is hard for me to see how it could possibly change the
meaning of anything. could you please send a me program (written in
scheme) that works in scheme but wouldn't if scheme didn't handle tail
recursion properly, so i can see what you are talking about?
Garbage collection and tail-call optimization occupy an interesting
position as language design properties go. As you quite correctly point
out, they do not change the meaning of any program, and thus they don't
show up in an abstract semantics. Nevertheless they are very
important... [cont'd]
W.r.t. tail-call optimization, I can write a continuation-passing
style program in Scheme and it is correct; if I transliterate it to
Common Lisp, the correctness of the program depends on whether the CL
compiler performs tail-call optimization. Thus the program can't be
properly called a CL program.
In other words, the Scheme language includes CPS programs, while
Common Lisp does not. The difference is an 'optimization', but this
only proves that when you guarantee an optimization for a language,
rather than a compiler, you (might) expand the set of valid programs
for that language.
again, i am confused. writing programs in a CPS style would be
horribly wasteful of memory if a program didn't optimize
tail-recursive function calls into jumps, but it should still give the
same result. unless, of course, the implementation runs out of
memory.
I would disagree with Pete Harlan's use of the words "correct" and
"valid" in this context, supplying instead the word "practical", which
is what garbage collection and tail-call optimization are really about.
so i suppose that there are programs that are valid scheme
but are invalid without this optimization, but the only such programs
are ones that would run forever. are there any others?
Yes! On any given finite computer there are a lot of programs that can
be written that will run out of memory in an implementation without
garbage collection and tail-call optimization, but will execute to
completion in an implementation with these properties (and, in fact,
will be perfectly reasonable programs that you might very well actually
want to write). It is definitely not necessary that a program be
infinite in order for the difference to show up (since we have no
infinite computers). As a practical matter, it turns out that a
guarantee that all implementations will have these properties makes for
a very considerable expansion and generalization in the ways that
programs can be written.
I am repeatedly mentioning garbage collection and tail-call optimization
in the same breath, by the way, not to suggest that they must always
appear together -- clearly they needn't -- but because they are like
each other in this way. Tail-call optimization can be seen as garbage-
collecting the stack; in fact, one can even imagine an implementation
that worked exactly that way: it would wait until all available stack
space was exhausted, then delete all useless frames from the stack.
Useless frames are easy to detect, on a typical machine, because the
return address of the frame points directly to the pop-frame-and-return
instruction sequence.
-- Scott
hieb@heston.cs.indiana.edu (Robert Hieb) (02/20/91)
Two points: 1) The "official" Scheme semantics is an approximation. There are various aspects of Scheme that are under-specified, over-specified, or wrongly-specified by the denotational specification. 2) There is more than one way to model a language. It is not clear that a denotational model is the best way to model a language like Scheme. An operational model might be better in some respects. To expand on the second point, consider the lambda calculus. Although there are denotational models, the primary model must be beta reduction. It is worth noting that beta reduction is clean with respect to both tail recursion and garbage. In fact, beta reduction is even clean with respect to environments, a claim which few Scheme systems can make. That is, the environment of a closure or continuation in most Scheme implementations hangs onto variable bindings that are not free in the text of the closure or continuation (or in the transitive closure thereof). Garbage collection and tail recursion are too fundamental to Scheme to ignore based on a notion of semantics that is not broad enough to include them. Without guarantees of both Scheme would be useless as a programming language. One would be forced to add specific looping and allocation/deallocation constructs. C with first-class closures and continuations would be an interesting language, but not Scheme. Bob Hieb Indiana University