Anthony Carrico wrote: > From Clinger: > (eq? '[[[[foo]]]] 'foo) => #t
SML/NJ says: ((((4)))) = 4 => true : bool
> I'm currently in the camp that thinks R5RS multiple values already > make perfect sense. They seem to jive 100% with my intuition that > Scheme is really just a convenient direct style syntax for CPS. From > that point of view continuations can have multiple results because > continuations are just lambdas and lambdas can have multiple > parameters. How could this be controversial?
The first controversial issue there is "lambdas can have multiple parameters". You suggest (later in your post) that an error might have been made extending compose to multiple values - but perhaps the error is made in the way multiple values are thought about and handled in the first place.
Anyway, by the same token, how could it be controversial to treat a sequence of multiple values as a first-class entity? The R5RS formal semantics does it internally:
If you look at the definition for 'cwv' in the R5RS formal semantics (end of sec 7.2.4), translated into something more Scheme-like, and factoring out some of the CPS and helper procedures like twoarg (which checks arg count) and applicate (essentially 'apply'), it looks like the code below (note if you compare to R5RS, you might notice an extra 'k' in my version, because R5RS has an omission in the definition of cwv - there should be a kappa after the last occurrence of e*, otherwise call-with-values would terminate a program):
Now, e* is a "sequence" in the formal semantics. All that's being done here is that the sequence of multiple values produced by the producer function, is passed to an anonymous procedure which passes them to the consumer function. In fact, if you factor out all the CPS except for the provided continuation k, it looks like this:
So one question is, if this is what's happening in the formal semantics, mightn't it be a good idea to be able to do something similar in Scheme? Why are we forced to do something so simple using a very special procedure, call-with-values, and if we write code like the above, it is an error?
From my own perspective, I've always found multiple values not to fit intuitively with the rest of Scheme. This has nothing to do with any of the political history or the fact that they didn't exist in earlier versions of Scheme: I learned Scheme post-R5RS, so to me, multiple values have always been there. But they never really seemed to fit.
It was about a year ago that I first read the 1995 discussion about this first-class sequence proposal. I found Matthias B's arguments convincing, and I had enough familiarity with ML at that point that it was instantly attractive to me.
The first-class sequence proposal seems to provide at least three benefits which I consider worthwhile:
1. Allow a multiple-value return result to be treated as a first-class value. While I understand that Scheme is not necessarily about making everything first class, I find the non-first-classness of multiple-values unpleasant. It limits how functions which return multiple values can be invoked. I find that to be against the spirit of Scheme. I could expand on that, but in brief, the inability to reliably use an expression of the form (let ((x (f ...))... for any function f is hard for me to understand, although I do understand the continuation justification. The fact that the primary motivating factor seems to be efficiency/performance for multiple-value return only makes things worse, for me - surely there are ways to achieve performance without requiring special and somewhat unwieldy non-first-class constructs.
2. Expose a more efficient structure for argument-passing than lists. Smart compilers already do this under the hood. The formal semantics already treats arguments as "sequences", for some of the same reasons as compilers do, afaict: because lists are an unnecessarily complex & inefficient structure for argument passing. But at the Scheme level, if you don't bind all a function's arguments to individual variables, you get a list, which isn't always what's wanted. The same argument that argues for multiple value return for performance/efficiency reasons, argues for something other than lists as a way to package arguments.
3. Create consistency between argument passing and multiple value return. Repeating part of point 2, right now we have what looks like real lists for arguments at the Scheme level, which can be too heavy; and we have values which can't be dealt with directly and have to be funnelled through a procedure's argument list via call-with-values (talking about features defined by R5RS). The first-class sequence proposal seems to provide a middle ground which cleans things up and improves flexibility.
> The only value I see in the "whacked-tuples" model is it makes things > nice for people who happen to want to write a procedure whose mode of > operation is to pass along an arbitrary number of results from one > procedure to the corresponding parameters of another. This particular > control flow isn't even possible in the functional world, so why is > anyone interested in calling it "compose"?
The end result of the first-class sequence proposal would be something very similar to ML, so it's hardly at variance with the functional world, depending on what you mean by "functional world". One definition of the functional world has lambdas only taking one parameter...
> It seems to me that people are trying to generalize something > simple (compose in the world of functions with one parameter > and one result) into a context where it > doesn't make any sense. So let's call that compose-with-a-twist. Now, > What makes compose-with-a-twist so important that we should worry > about it?
> The logic: > 1. Scheme has multiple values. > 2. Let's extend compose to multiple values. > 3. Hmm... this seems a little strange. > 4. Let's redesign Scheme's call mechanism.
> I submit that the error may have been made in step 2!
Or step 1! My logic doesn't start with step 1. It starts with wanting a consistent and usable solution to the issue of passing multiple values around: whether to functions, or from functions.
> If people still want to continue with step 4, at least consider the > following: Clinger showed that there is no price to be payed for > multiple values (when you aren't using them).
But some people find them to be a less-than-useful or attractive mechanism, whereas the problem they are supposed to solve does exist. The sequence proposal (arguably) provides a more complete & elegant solution.
> To my knowledge he didn't show that there is no price > to be payed for sequences (aka tuples).
True. That would need to be investigated, as would the usability within Scheme of the proposal (IMO).
> In particular, who's job is it to unwind all those tuples (aka > sequences) of one? The tuple constructors? The accessors? During the > normal course of program flow, who's job is it to make sure they don't > build up in the first place? Are they a space leak in certain > unfortunate code?
Maybe Matthias B. could describe that. Appel's book says (ch 5.3), talking about primops: "Each [primop] category is converted into [CPS] in one of two ways, depending on whether the operator takes one argument or more than one." It makes sense that it would happen at function call sites and other tuple construction locations.
> The other thing I find strange about tuples is that tuple constructors > must be "special" procedures since they take more than one argument in > a one argument only world.
Every procedure call would implicitly construct a tuple, wouldn't it? An explicit tuple constructor would only be needed in certain situations. In many respects, it simply seems like exposing something that already happens internally. Multiple values are already very "special". One way to look at this proposal is it makes them less special.
Note that I'm not claiming I'm "right" about all this. I don't think my understanding is in-depth enough to make that judgement. But the proposal sounds worthwhile to me, and I haven't yet seen any reasons that, to me, argue for rejecting it.
"Anton van Straaten" <an...@appsolutions.com> writes:
> Note that I'm not claiming I'm "right" about all this. I don't think my > understanding is in-depth enough to make that judgement. But the proposal > sounds worthwhile to me, and I haven't yet seen any reasons that, to me, > argue for rejecting it.
Actually, I think you've argued the point very well. Indeed, it cuts to the heart of my difficulties with call/values (aside from R4RS incompatibility). I certainly could live with Matthias Blume's proposal. Will Clinger's proposal seems to be very concerned with the eq?-ness of sequence/tuple objects which Matthias Blume seems to have engineered out of the systemm by making tuples immutable.
So what are we mere users missing here?
david rush -- He who would make his own liberty secure, must guard even his enemy from repression -- Thomas Paine
>>>>> "bear" == Ray Dillinger <b...@sonic.net> writes:
bear> But the ability to pass arbitrary multiple results directly in bear> as parameters to another function would make scheme into a better bear> DD language than any now extant.
Huh? That's *exacty* what CALL-WITH-VALUES does. Could you clarify what you mean?
-- Cheers =8-} Mike Friede, Völkerverständigung und überhaupt blabla
Michael Sperber <sper...@informatik.uni-tuebingen.de> virkkoi:
> >>>>> "bear" == Ray Dillinger <b...@sonic.net> writes:
> bear> But the ability to pass arbitrary multiple results directly in > bear> as parameters to another function would make scheme into a better > bear> DD language than any now extant.
> Huh? That's *exacty* what CALL-WITH-VALUES does. Could you clarify > what you mean?
I'd hazard a guess that the essential difference is in the convenience between:
Michael Sperber <sper...@informatik.uni-tuebingen.de> writes: > >>>>> "bear" == Ray Dillinger <b...@sonic.net> writes:
> bear> But the ability to pass arbitrary multiple results directly in > bear> as parameters to another function would make scheme into a better > bear> DD language than any now extant.
> Huh? That's *exacty* what CALL-WITH-VALUES does. Could you clarify > what you mean?
"Anton van Straaten" <an...@appsolutions.com> writes:
> Anthony Carrico wrote: > > From Clinger: > > (eq? '[[[[foo]]]] 'foo) => #t
> SML/NJ says: ((((4)))) = 4 => true : bool
Yes. And any mathematicias will agree that
(x) = x
and therefore, by extension,
((((x)))) = x
I really don't see why people would get bent out of shape by something like that. Also, notice that (as I mentioned before several times) you can already define such a bracketing construct (call it TUPLE) with these properties it RnRS Scheme, so it can't be all that "strange":
> > In particular, who's job is it to unwind all those tuples (aka > > sequences) of one? The tuple constructors? The accessors? During the > > normal course of program flow, who's job is it to make sure they don't > > build up in the first place? Are they a space leak in certain > > unfortunate code?
> Maybe Matthias B. could describe that.
I already did.
> > The other thing I find strange about tuples is that tuple constructors > > must be "special" procedures since they take more than one argument in > > a one argument only world.
> Every procedure call would implicitly construct a tuple, wouldn't it? An > explicit tuple constructor would only be needed in certain situations.
Exactly. In fact, you can define your explicit tuple constructor (assuming my proposal) as, the rest of the machinery is in how LAMBDA and function application works:
Matthias Blume <matth...@shimizu-blume.com> writes: > Exactly. In fact, you can define your explicit tuple constructor > (assuming my proposal) as, the rest of the machinery is in how LAMBDA > and function application works:
Oops, this sentence got garbled. Read as:
"In fact, you can define your explicit tuple constructor (assuming my proposal) as:
(define (tuple l) l)
(The rest of the machinery is in how LAMBDA and function application works.)"
Matthias> Michael Sperber <sper...@informatik.uni-tuebingen.de> writes:
>> >>>>> "bear" == Ray Dillinger <b...@sonic.net> writes:
bear> But the ability to pass arbitrary multiple results directly in bear> as parameters to another function would make scheme into a better bear> DD language than any now extant.
>> Huh? That's *exacty* what CALL-WITH-VALUES does. Could you clarify >> what you mean?
Matthias> "directly" as opposed to "via CALL-WITH-VALUES"
Matthias> (Just a guess.)
Probably right, though.
I don't see a totally baby-bottom-smooth way to paper over the distinction between procedures and continuations, I guess. Scheme uses CALL-WITH-VALUES to paper over the gap, ML uses tuples. Both are cool in some contexts, awkward in others.
-- Cheers =8-} Mike Friede, Völkerverständigung und überhaupt blabla
> Michael Sperber <sper...@informatik.uni-tuebingen.de> virkkoi: > > >>>>> "bear" == Ray Dillinger <b...@sonic.net> writes:
> > bear> But the ability to pass arbitrary multiple results directly in > > bear> as parameters to another function would make scheme into a better > > bear> DD language than any now extant.
> > Huh? That's *exacty* what CALL-WITH-VALUES does. Could you clarify > > what you mean?
> I'd hazard a guess that the essential difference is in the convenience > between:
> (call-with-values (lambda () e) f)
> and
> (f e)
Yeah. Or more to the point being able to say
(f (e (j (i param1 param2 param3 .... param20))))
to compose a bunch of procedures that each take 20 arguments and return 20 results.
Michael Sperber <sper...@informatik.uni-tuebingen.de> wrote: > I don't see a totally baby-bottom-smooth way to paper over the > distinction between procedures and continuations, I guess. Scheme > uses CALL-WITH-VALUES to paper over the gap, ML uses tuples. Both are > cool in some contexts, awkward in others.
Amen brother! The only baby-bottom-smooth resolution is to prevent functions from returning at all. Too bad it's such a pain in the baby's bottom.
Second class parameter/values are a control construct; that's Scheme. Ignoring parameters/values and just passing/returning a single first class vectors/list/sequence/tuple and calling its elements your "parameters/results" is also an option inside of Scheme.
If you ditch the second class parameters/values, you no longer have Scheme. In that case, the lambda-the-ultimate goto-with-parameters mental model is gone. Let's just call it "MLisp" and see if it can prove itself on its own merits (rather than just downplaying the merits of the Scheme model).
I'll certainly admit that the Scheme rest parameter is an artifact of some by-gone interpreter's implementation, so allow the Scheme rest parameter to be bound to something opaque which can be implemented as a list of arguments, set of registers, a tuple, etc. Now, with some functions and macros, it ought to be possible define a Scheme in native MLisp, and a Mlisp in native Scheme. Then we get some answers.
I'd welcome the chance to learn about ML concepts with a lisp syntax and syntax-case macros, I just wouldn't call it a logical extension of what I understand Scheme to be.
Anthony Carrico <acarr...@memebeam.org> writes: > If you ditch the second class parameters/values, you no longer have > Scheme.
I don't see why that would be so. In fact, most (all?) Scheme programs would still run, so what's the problem?
> In that case, the lambda-the-ultimate goto-with-parameters > mental model is gone.
Again, I don't understand this statement at all. What does one have to do with the other?
> Let's just call it "MLisp" and see if it can > prove itself on its own merits (rather than just downplaying the > merits of the Scheme model).
Remind me: what are those merits of the "Scheme model" (whatever *that* is), and how do they rely on the second-class-ness of parameters and values?
I always thought that if there is a "Scheme model", then it is all about making as many things as possible first-class. (Not that that's necessarily a good thing. In many cases I'd argue that it is not. Parameters and values, though, aren't one of these cases.)
Second class parameter/values are a control construct; that's Scheme. [....]
If you ditch the second class parameters/values, you no longer have Scheme. In that case, the lambda-the-ultimate goto-with-parameters mental model is gone. Let's just call it "MLisp" and see if it can prove itself on its own merits (rather than just downplaying the merits of the Scheme model).
I'm not so sure about this tuple stuff fitting in with "the spirit of Scheme" -- or even making much sense.
I've divided this possibly too long post into three parts:
* questions unanswered in the proposal * some surprising behaviors implied by the proposal * the spirit of scheme and its implications here
* questions unanswered in the proposal
Matthias wrote the proposal in a very informal style which makes it hard to tell precisely what it says. Informally, I get the gist, but when I try to imagine how the details should be filled in, I don't quite see it.
and so I don't see how Matthias' definition isn't circular. Is part of the proposal here that `let' is no longer what R5RS calles a "Derived expresion type"? Does this proposal entail an extensive rewrite of the definition? Can we see that report?
People in this thread have been asking questions which I'll paraphrase as "Why not do this? What's the impact?" I think that to really answer those questions, minimally, one ought to produce the proposed changes for R6RS in detail; ideally, work out its implications for various compilation and interpretation strategies.
* some surprising behaviors implied by the proposal
The proposal tells us that:
(f 1 2 3)
is just syntax for:
(f (tuple 1 2 3))
and also that tuples are to be "good old-fashioned first-class values like everything else in Scheme".
Taking these proposals at their word, and putting them together, we get some suprises, like:
(length (list (tuple 1 2 3))) => 3
and so, for example, unlike R5RS, it is no longer certain that (absent errors):
I suppose that one could _partly_ "rescue" the tuple propsal by asserting that:
(let ((x (tuple 1 2 3))) <body>)
is an error and that only a multi-binding construct such as:
(let ((x y z (tuple 1 2 3))) <body>)
is permissable, but then in what sense is it that tuples are "first class values" again?
* the spirit of scheme and its implications here
I don't think there's any real definition of the "spirit of scheme" but I do think it's a topic that can be discussed meaningfully.
I look for evidence of "the spirit of Scheme" in a few places: the rabbit thesis, the "Lambda: The Ultimate..." papers, the RnRS series, my own experience as implementor and user, the experience of other implementors and users. I also like to look at some of the influences floating around in the early days of Scheme, such as work of Christopher Strachey, et al.
I seem to recall seeing many spirit-oriented discussions on this list focus intensely on just one sentence which can be found in R5RS, the first sentence of the introduction:
Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.
That's an important sentence, but I like the next one too (emphasis added):
Scheme demonstrates that a very small number of rules for forming expressions, with no restrictions on how they are composed, suffice to form a _practical_ and efficient programming language that is flexible enough to support most of the major programming paradigms in use today.
And these two from the summary:
It was designed to have an exceptionally _clear_and_simple_ semantics and few different ways to form expressions. A _wide_ variety_of_programming_paradigms_, including _imperative_, functional, and _message_passing_styles_, _find_convenient_ expression_ in Scheme.
So the first observation I want to make is that the tuples proposal, at least arguably, removes a clear and simple semantics for lambda and procedure application and replaces them with a somewhat opaque and complicated semantics. It's not at all obvious to me that tuples really make the math of the semantics any easier -- it is clear to me that tuples and single-argument-lambda introduce a serious indirection between procedures and procedure application, and that other important view: that procedure application corresponds to a machine-language "goto while passing parameters".
And the second observation I want to make is that it is not obviously in keeping with the spirit of scheme to try to turn it into a particular lambda calculus of interest to ML fans. You'd want to at least demonstrate, say, a really simple yet kick-ass compiler and interpreter grounded in that approach first. (I'm using "not obviously" advisedly -- I'd also say "not obviously not". I'm just trying to say that saying "call-with-values doesn't _feel_ right to me; tuples feel better" isn't enough. It was the suprising harmonization of theory with practice that caused Scheme to resonate so loudly when it appeared.)
I'll dig a little deeper into two points in particular. First, the "goto with parameters" view; second, the question "what is the scheme-spirit take on functional languages".
** goto with parameters
I thought that part of the "spirit of scheme" is that one interpretation of function application is that is a "goto with parameters".
Another part of the "spirit of scheme" is that "returning a value" is an example of function application.
A third part of the "spririt of scheme" is to avoid unecessary restrictions on how expressions may be composed.
Putting those three parts of the "spirit of Scheme" together, it is clear that R4RS was missing some generality. It contained the arbitrary restriction that one could pass multiple parameters in most function applications, but not in the function applications implied by "returning". All continuations took precisely one parameter.
The "goto with parameters" idea makes sense at several levels. Operationally, it's a vague but useful way to characterize some important properties the kinds of computers we know how to build best -- so when we're programming in those terms we are, in some sense, programming "close to the hardware". Pragmatically, the goto idea leads to very simple compilation and interpretation strategies -- strategies very clearly related to the surface language. Mathematically, the goto idea is expressible in a minimally augmented example of an applicative-order lambda calculus. The goto idea, along with first-class continuations, very concisely provides, explains, and generalizes all important control flow constructs found in similar languages. Those four levels can all be grasped comparatively easily by intuition. Physics, the practice of programming, math, and explicative power all converging and harmonizing over a very intuitive abstract structure --- with five-part harmonies like that, do you really wonder why I sometimes say "space aliens program in scheme"?
The tuples idea doesn't exactly abandon the idea of the "goto with parameters" idea -- but it does throw in some new layers of indirection there. Intuition is told, "don't worry, this all gets put back together in a reasonable way by the compiler". I think something is lost in this proposal.
One way to view the idea of "first class tuples" is that it is the idea of reifying the parameter passing conventions as values which represent the intention to pass a particular set of parameters.
One way to view the idea of "call-with-values" and "values" is that it is based on exactly the opposite idea: of avoiding creating values which represent the intention to pass a particular set of parameters.
Paradoxes like the `length' examples shown above illustrate that there is a distinction to be made between applying a procedure to a single first-class value which can be interpreted as a reified "intention to pass a particular list of parameters" and a procedure application to that list of parameters. Scheme already has a mechanism for making that distinction: `apply'.
** the scheme-spirit take on functinoal languages
The interesting, foundational work on implementing functional languages bears a lot of resemblence to the rabbit thesis. A compiler for a tiny core language is exhibited, and then many high-level concepts are expressed as transformations into that language.
The static-typing work done by functional language researchers clearly does not apply to Scheme as a whole. Certainly it could be reinterpreted as a compilation/linting strategy for a subset of Scheme programs. (R5RS 1.1: "Scheme has latent as opposed to manifest types." and R5RS Summary: "Scheme is [...] a dialect of the Lisp program language [....]")
Where I think Scheme research _might_ pay off with language changes/extensions is from consideration of implementations of the normal-order lambda calculus. Does graph
Anthony Carrico wrote: > Second class parameter/values are a control construct; that's > Scheme.
Although, until R5RS, there was no issue about multiple values because they didn't exist. Then multiple values were added, and as a result, that part of Scheme became something that's not considered completely Scheme by some people - a view I have some sympathy with.
> If you ditch the second class parameters/values, you no longer have > Scheme.
I'm sure we all have different ideas about what makes Scheme Scheme. That one hasn't ever occurred to me, except as a sort of restriction - minor in the case of parameters, but rearing its head more with multiple value return. "Ditching" second class parameters/values makes it sound as though something is lost - but what? I listed a number of things I saw as lost with multiple values, including the very Scheme-like ability to reliably store the results of any function in a variable.
> In that case, the lambda-the-ultimate goto-with-parameters > mental model is gone.
How so? Can you give an example of a description of some operation, from the "lambda-the-ultimate goto-with-parameters" model perspective, which is somehow unreasonable or broken with first-class sequences?
If we want to relate this discussion to lambda-the-ultimate-ness, one problem is that real lambdas only take one parameter (that should be read in the same way as "real men don't eat quiche".) So where do we look for models of how to handle lambdas with multiple parameters? I don't know, and would appreciate any pointers. Scheme provides one model, but as I've argued, to me its multiple values handling seems out of place. ML provides another model, which *in ML* I certainly like, and don't have that out of place feeling about. I'm less familiar with Haskell, but its model seems quite similar to ML's - in particular, (4) == 4, and f (x,y) passes a single tuple (x,y) to the function f.
Given this, the idea that we're talking about the ML'izing of Scheme here seems invalid. We may be talking about bringing Scheme more in line with other lambda-based functional languages with pattern-matched argument binding, though.
> Let's just call it "MLisp" and see if it can > prove itself on its own merits (rather than just downplaying the > merits of the Scheme model).
I see the proposal as essentially a kind of extension, although one that removes the need for some existing core constructs. In a Scheme with sequences, you'll be able to use (f (g)) where g returns multiple values, instead of (call-with-values g f). The former seems much more like "the Scheme model" to me. call-with-values can be implemented in terms of that, but not vice versa.
So, I'm not understanding the big problem here, and I don't see which merits of the Scheme model are being downplayed. I'm willing to try to understand, given or pointed to an explanation.
> I'll certainly admit that the Scheme rest parameter is an artifact of > some by-gone interpreter's implementation, so allow the Scheme rest > parameter to be bound to something opaque which can be implemented as > a list of arguments, set of registers, a tuple, etc. Now, with some > functions and macros, it ought to be possible define a Scheme in > native MLisp, and a Mlisp in native Scheme. Then we get some > answers.
Something that addressed the concerns of both sides would obviously be ideal. RnRS seems capable of straddling almost any line, what's one more? ;)
> I'd welcome the chance to learn about ML concepts with a lisp syntax > and syntax-case macros, I just wouldn't call it a logical extension of > what I understand Scheme to be.
I certainly don't see the proposal as a way to migrate Scheme towards ML. I wouldn't be in favor of that. I like ML for some kinds of applications, but I value the flexibility that Scheme provides. I'm just not seeing how this proposal takes anything away from that flexibility.
Instead of "MLisp", may I suggest "2Scheme", as a contraction of TupleScheme. This also has parallels with Lisp1 & Lisp2, which may be the kind of split we're beginning to create here... :^}
Wow! All I can say is thank you Tom Lord for doing such a great job of expressing my concern. Most of the questions Matthias Blume and Anton van Straaten asked back to me are best answered in your post.
I think Tom did a great job of expressing how (people like me) understand the Scheme model in terms of goto with parameters, and how (for people like me) the "tuple" model seems one step removed from the machine. If I may argue-by-irony, in "Compiling with Continuations", Andrew Appel takes the output from ML front end and maps it to Scheme's goto with parameters model. A lot of the optimizations he describes involve cleaning up all those tuple accesses!
Anton van Straaten <an...@appsolutions.com> wrote:
> Although, until R5RS, there was no issue about multiple values because they > didn't exist. Then multiple values were added, and as a result, that part > of Scheme became something that's not considered completely Scheme by some > people - a view I have some sympathy with.
I feel just opposite way. Until R5RS, it felt like there was a hole in Scheme because you you couldn't inject a continuation with an arbitrary signature. R5RS didn't add something new to the picture, it filled that curious hole in the existing mechanics. In fact, it made the kind of function composition that you desire possible.
> How so? Can you give an example of a description of some operation, from > the "lambda-the-ultimate goto-with-parameters" model perspective, which is > somehow unreasonable or broken with first-class sequences?
> If we want to relate this discussion to lambda-the-ultimate-ness, one > problem is that real lambdas only take one parameter (that should be read in > the same way as "real men don't eat quiche".) So where do we look for > models of how to handle lambdas with multiple parameters? I don't know, and > would appreciate any pointers.
Ah. I apologize, as you say we are coming from very different points of view. I am not talking about those "real lambdas". Not at all. Those lambdas have little to do with what I'm talking about, it's a different mechanism, they just happen to have the same alias by some accident of history. I'm talking about the Steele/Sussman ultimate-lambdas. See "The Original 'Lambda Papers' by Guy Steele and Gerald Sussman" at http://library.readscheme.org/page1.html. Sorry for the confusion.
> In a Scheme with sequences, you'll be able to use (f (g)) where g > returns multiple values, instead of (call-with-values g f). The > former seems much more like "the Scheme model" to me. > call-with-values can be implemented in terms of that, but not vice > versa.
> So, I'm not understanding the big problem here, and I don't see which merits > of the Scheme model are being downplayed. I'm willing to try to understand, > given or pointed to an explanation.
Ok, the big problem is that, in my world, "(f (g)) where g returns multiple values" is an error, not a virtue! This is what I was trying to point out when I said that maybe this whole discussion fell off the Scheme wagon back at "compose". On the other hand, if in some circumstance I do want f to take all of g's values as parameters, I have an easy way to do it: make f into the continuation of g. How do I do that? The call-with-values procedure.
There is a big huge difference between "(g) initializing the first argument of f" and "f being the continuation of (g)". The proponents of this idea conflate the two situations, "what's the big deal?, its the same thing!". In my world, it's not the same thing, it's very different!
In your world, the operator is indeed the continuation of the first and only argument. It sort of makes every call into a call-with-values.
I understand that you all aren't in my world, and there is probably some value in your world, but I hope you can understand that it sometimes seems like you guys are trying to sweep some very fundamental issues under the rug. Again, read Tom's post (well, except the final paragraph :). I'm sure it is more coherent than mine.
The Glasgow Haskell Compiler GHC has something _very_ like Scheme multiple values: unboxed tuples. Unsurprisingly, they are second class. Here's an excerpt from GHC documentation:
<blockquote> 7.2.2. Unboxed Tuples
Unboxed tuples aren't really exported by GHC.Exts, they're available by default with -fglasgow-exts. An unboxed tuple looks like this:
(# e_1, ..., e_n #)
Unboxed tuples are used for functions that need to return multiple values, but they avoid the heap allocation normally associated with using fully-fledged tuples. When an unboxed tuple is returned, the components are put directly into registers or on the stack; the unboxed tuple itself does not have a composite representation. Many of the primitive operations listed in this section return unboxed tuples.
- Unboxed tuples may only be constructed as the direct result of a function, and may only be deconstructed with a case expression. eg. the following are valid:
f x y = (# x+1, y-1 #) g x = case f x x of { (# a, b #) -> a + b }
but the following are invalid:
f x y = g (# x, y #) g (# x, y #) = x + y
The IO and ST monads use unboxed tuples to avoid unnecessary allocation during sequences of operations.
</blockquote>
I would not be surprised if Simon Peyton-Jones got that idea from Scheme.
I must say that I use multiple values and multiple-valued functions a great deal. OTH, I hardly ever mention call-with-values, directly. Multiple-valued functions _greatly_ help in writing purely functional code that involves the threading of a state:
As you can see, 'properties' is the list of properties that gets threaded through. Functions like find-prop-in-open-properties search the list for some keys, return the found values (or the default values), and the remainder of the list. At the end, the code checks that all properties given by the user have been taken care of. The code is purely functional and referentially transparent. This code should be familiar to some people on this newsgroup. And yes, it can run on Gambit.
For one thing, the code looks like pattern-matching on the list. For another, the above expression frees me from explicitly checking that the list lst indeed has exactly three elements. I get deconstruction, binding, and error checking in one line. Here's a similar example from the real code:
Matthias> Remind me: what are those merits of the "Scheme model" (whatever Matthias> *that* is), and how do they rely on the second-class-ness of Matthias> parameters and values?
The merits are that there is a symmetry between parameters and return values.
If you're asking for "merits over the ML model"---here are some of my gripes with it:
- There are two ways of doing multi-parameter functions---via currying or tuples, with no clear indication of which one is superior. (I haven't seen a clear discipline written up when to use one or the other.) With currying, "parameter lists" aren't first-class, either.
- Zero-parameter functions are not really a problem in practice, but they break my notion of baby-bottom-smoothness.
- I'd say implementing tuples without excessive allocation through boxing is more difficult than implementing the Scheme model. (In fact, I remember ML implementations that didn't bother in the first place. The result was worse than Emacs.)
-- Cheers =8-} Mike Friede, Völkerverständigung und überhaupt blabla
I am no longer able (nor eager) to invest too much of my time and my emotional energy in arguing these things, so I will (try to) limit myself to this one last article on the topic.
At the time it was written, my proposal was not meant as a serious suggestion for changing the definition of Scheme. It merely served to show that there are ways of getting the benefits of multiple return values without the ballast of having to deal with contortions such as call-with-values.
Having said this, I will answer your three areas of concern in order. I'll keep it as brief as I can.
1. It is, obviously, true that one cannot explain the semantics of LAMBDA with those of LET if at the same time one is to explain LET in terms of LAMBDA.
My attempt at explaining my new LAMBDA semantics using LET were not meant to be formal in any sense. (Why do people always take usenet articles and treat them as if they were POPL submissions? They are not.)
Anyway, I also explained the semantics of my new LAMBDA quite clearly without using LET, I think. Here is a brief summary:
- all functions (including continuations) take precisely one argument - there is a distiguished value, call it "[]", which should be though of as an "empty tuple" - given two or more values v_1,...,v_k (k >=2), there is a "k-tuple value" containing these values (call it "[v_1 ... v_k]") - LAMBDA forms: * (LAMBDA () ...) evaluates to a procedure whose argument must be the empty tuple []. Passing any other argument is considered a runtime error. * (LAMBDA (x) ...) evaluates to a procedure whose single argument will be bound to the formal parameter x for the purpose of evaluating the body. * (LAMBDA (x_1 ... x_k) ...) evaluates to a procedure whose single argument must be a k-tuple [v_1 ... v_k]. For the purpose of evaluating the body, each v_i will be bound to the corresponding x_i. * (LAMBDA x ...) evaluates to a procedure which inspects its single argument. If the argument is the empty tuple [], then x will be bound to the empty list; if the argument v is not a tuple, then x will be bound to the freshly allocated singleton list (v); if the argument is a k-tuple [v_1 ... v_k], then x will be bound to a freshly allocated list (v_1 ... v_k). - Application forms: * (f) calls the procedure that f evaluates to and passes [] is the single argument. * (f arg) calls the procedure that f evaluates to and passes the result of evaluating arg as its single argument. * (f arg_1 ... arg_k) with k >= 2 calls the procedure that f evaluates to and passes the tuple [v_1 ... v_k] as its single argument where each v_i is obtained by evaluating the corresponding arg_i.
A suitable modification of the denotational semantics given in RnRS is easy (or so I believe). I leave it as an exercise to the interested reader. :-)
Remarks:
* I claim that under the above rules, all Scheme programs that currently do not exhibit runtime errors will continue to run and behave as they did before. This should be fairly straightforward to prove by a simple case analysis, matching applications with corresponding lambdas.
* One can quibble about the equality properties of tuples. I leave this open here. If we allow EQ? on tuples, then one obvious axiom should be:
If t is a tuple [v_1 ... v_k] and (EQ? t u) ==> #t, then u is a tuple [w_1 ... w_k] and \forall i . (EQ? v_i w_i) ==> #t.
I would not want to place many (if any) more restrictions than that on implementations (see next paragraph).
* In my original article I also sketched out an implementation strategy which could succinctly be described as "lazy tupling". In all Scheme programs that are currently legal there would never be any tuple that gets physically constructed, so there is no inherent cost in using first-class tuples. (Current implementations already have to check for arity mismatches, and tupling can quite easily be piggy-backed on these arity tests. One can optimize common cases by having multiple entry points for each function: one for the 1-argument case, one for the 0-or-k-argument case.)
* The R5RS procedure VALUES can be defined as the identity function. (This is the same as an explicit tuple constructor.)
* The R5RS procedure CALL-WITH-VALUES can be defined as
(define call-with-values (LAMBDA (p c) (c (p ()))))
Tom has a point here: It may come as a surprise to some that it is no longer true that regardless of what the value of x we have
(length (list x)) ==> 1
In fact, if LIST is defined as
(define LIST (lambda l l))
then it acts as a converter from tuples to lists. This might even be more useful than trying to preserve the "expected" behavior above.
In any case, I personally do not see variable-arity functions as more than gratuituous cuteness. They are never really necessary. In my code, when I use LIST I really want to use records (preferably with named fields). Modern dialects of Scheme offer those. When lists are used they way they were meant to be used (i.e., as an inductive data structure), LIST is seldom useful as a function. Most of the other built-in variable argument functions (+, *, ...) are even less useful, in my view. In my code I rarely use them with anything else than exactly two arguments. Having the obvious fold built into the operator is what I consider "cute" but not terribly helpful.
The bottom line is that I can envision a Scheme which does not rely so heavily on variable argument lists, so if there is any problem with those in relation to tuples, it might be a non-issue.
(The syntactic convenience provided by variable-arity functions could easily be recovered by macros. Procedure values, however, do not need to be able to accept a variable number of arguments. In cases where one really wants to pass a statically unknown number of values to a function, one can do that already by explicitly passing a list of these values.)
3. The "spirit of Scheme". I will keep this very short since it is mostly mumbo-jumbo anyway.
I have no idea what this lambda-as-goto has to do with tuples. The two things are completely separate issues. The existence of variable-argument lists in RnRS is proof enough that if there is any such problem at all, it already exists in present Scheme.
In any case, this thread is testimony to the fact that different people have quite different ideas of what the "sprit of Scheme" is. Moreover, they seem to be able to arrive at opposite ends of the design spectrum by following their intuition about this "spirit". This, if nothing else, shows that "spirit of Scheme" is a wishy-washy concept at best.
> Matthias> Remind me: what are those merits of the "Scheme model" (whatever > Matthias> *that* is), and how do they rely on the second-class-ness of > Matthias> parameters and values?
> The merits are that there is a symmetry between parameters and return > values.
But there isn't, and that's the exact problem.
> If you're asking for "merits over the ML model"---here are some of my > gripes with it:
> - There are two ways of doing multi-parameter functions---via currying > or tuples, with no clear indication of which one is superior. (I > haven't seen a clear discipline written up when to use one or the > other.) With currying, "parameter lists" aren't first-class, > either.
The same is true for Scheme (although its awkward syntax will most likely discourage most programmers from using curried functions).
> - Zero-parameter functions are not really a problem in practice, but > they break my notion of baby-bottom-smoothness.
Sorry, but I have no idea of what your "notion of baby-bottom-smoothness" is. (Nor do I want to find out. :-)
> - I'd say implementing tuples without excessive allocation through > boxing is more difficult than implementing the Scheme model.
No, it is not. See my other articles on this topic. In fact, it is fairly easy to implement the tuple model in such a way that *no* tuples get constructed for currently legal Scheme programs. (In other words, tuples would only get constructed in programs that take advantage of the new semantics.)
> (In fact, I remember ML implementations that didn't bother in the first > place. The result was worse than Emacs.)
That may very well be so. But what does this have to do with the current debate? Just because someone somewhere sometime in some completely different context did a bad job at implementing a feature does not meant that the feature is bad.
>>>>> "Matthias" == Matthias Blume <matth...@shimizu-blume.com> writes: >> If you're asking for "merits over the ML model"---here are some of my >> gripes with it:
>> - There are two ways of doing multi-parameter functions---via currying >> or tuples, with no clear indication of which one is superior. (I >> haven't seen a clear discipline written up when to use one or the >> other.) With currying, "parameter lists" aren't first-class, >> either.
Matthias> The same is true for Scheme (although its awkward syntax will most Matthias> likely discourage most programmers from using curried functions).
You're not getting my point: the syntactic overhead in Scheme of currying over multiple parameters implies a clear discipline. In ML, the notational costs are very similar (slightly lower for currying), which muddies the water and forces people to put protocol conversions in their program where the essence of what they're doing doesn't imply them.
>> - I'd say implementing tuples without excessive allocation through >> boxing is more difficult than implementing the Scheme model.
Matthias> No, it is not. See my other articles on this topic. In fact, it is Matthias> fairly easy to implement the tuple model in such a way that *no* Matthias> tuples get constructed for currently legal Scheme programs.
I guess we differ on assessing the relative costs of implementing these things.
>> (In fact, I remember ML implementations that didn't bother in the first >> place. The result was worse than Emacs.)
Matthias> That may very well be so. But what does this have to do with the Matthias> current debate?
Not much, but at least that somebody else didn't find it as quite as easy as you do.
Matthias> Just because someone somewhere sometime in some completely Matthias> different context did a bad job at implementing a feature Matthias> does not meant that the feature is bad.
I never attributed "badness" to any feature that's part of the discussion---neither in words nor in intentions.
Note that I also never said I like the "Scheme model" significantly better than the "ML model." I like both about the same. (I *do* dislike the R4RS model.) I sure don't see a clear advantage in the ML model, though.
-- Cheers =8-} Mike Friede, Völkerverständigung und überhaupt blabla
Michael Sperber <sper...@informatik.uni-tuebingen.de> writes: > Note that I also never said I like the "Scheme model" significantly > better than the "ML model." I like both about the same. (I *do* > dislike the R4RS model.) I sure don't see a clear advantage in the ML > model, though.
Well, IMNSHO, R5RS is the worst of the solutions. I think I like Blume/Clinger better than R4RS though.
david rush -- He who would make his own liberty secure, must guard even his enemy from repression -- Thomas Paine
Anthony Carrico <acarr...@memebeam.org> writes: > Ok, the big problem is that, in my world, "(f (g)) where g returns > multiple values" is an error, not a virtue! This is what I was trying > to point out when I said that maybe this whole discussion fell off the > Scheme wagon back at "compose". On the other hand, if in some > circumstance I do want f to take all of g's values as parameters, I > have an easy way to do it: make f into the continuation of g.
I'm sorry but you have just restated the definition of composition. In (f (g)), f *is* the continnuation of (g). Just what is it that you consider an error?
> How do I do that? The call-with-values procedure.
Which is arguably a much clunkier syntax than the natural expression of composition.
david rush -- He who would make his own liberty secure, must guard even his enemy from repression -- Thomas Paine
Anthony Carrico <acarr...@memebeam.org> writes: > Ok, the big problem is that, in my world, "(f (g)) where g returns > multiple values" is an error, not a virtue! This is what I was trying > to point out when I said that maybe this whole discussion fell off the > Scheme wagon back at "compose". On the other hand, if in some > circumstance I do want f to take all of g's values as parameters, I > have an easy way to do it: make f into the continuation of g.
I'm sorry but you have just restated the definition of composition. In (f (g)), f *is* the continnuation of (g). Just what is it that you consider an error?
> How do I do that? The call-with-values procedure.
Which is arguably a much clunkier syntax than the natural expression of composition.
> There is a big huge difference between "(g) initializing the first > argument of f" and "f being the continuation of (g)".
There isn't once you apply the CPS-transform.
> The proponents > of this idea conflate the two situations, "what's the big deal?, its > the same thing!". In my world, it's not the same thing, it's very > different!
I don't understand your world then. In a fully CPS-converted program, all tail calls are continuations. Passing a parameter to a continuation is no different from passing a parameter to a `regular' function. FWIW, this actually causes some difficulties in CPS-oriented compilers and has given rise to the A-Normal transform, but even there the main (handwave) difference is in the number of continuation functions introduced. All functions still use the same argument application mechanism.
The point is that whatever you think about your world, they really *are* the same, mathematically. Practically, compiler use various implementation techniques to optimize function calls depending on the context. The beauty of Blume's proposal is that it has a minimal (he asserts zero) run-time cost for existing RnRS programs.
> In your world, the operator is indeed the continuation of the first > and only argument. It sort of makes every call into a > call-with-values.
Precisely. This is the basic theory of the _Lambda, the Ultimate_ papers, actually.
david rush -- He who would make his own liberty secure, must guard even his enemy from repression -- Thomas Paine
Matthias Blume <matth...@shimizu-blume.com> wrote: > I am no longer able (nor eager) to invest too much of my time and my > emotional energy in arguing these things, so I will (try to) limit > myself to this one last article on the topic.
I think that you are correct. We have fully explored both sides of the debate. If some language designers can find users willing to swallow an application rule for for k=0, 2, 3, 4... with an exception for k=1, and a paradoxical tuple definition to cover the exception up, then you have a winning proposal. It's fun to explore crazy ideas, otherwise we'd make no progress. A successful crazy idea has a big payoff, I just haven't seen it yet for this one. Turning k=1 into a "free" call-with-values doesn't seem like any kind of payoff to me, certainly not something to trade a uniform application rule for. Maybe it will make sense to me in another context someplace down the road. Thank you for the time invested, I always learn a lot from your work.
David Rush <dr...@aol.net> wrote: > Anthony Carrico <acarr...@memebeam.org> writes: >> Ok, the big problem is that, in my world, "(f (g)) where g returns >> multiple values" is an error, not a virtue! This is what I was trying >> to point out when I said that maybe this whole discussion fell off the >> Scheme wagon back at "compose". On the other hand, if in some >> circumstance I do want f to take all of g's values as parameters, I >> have an easy way to do it: make f into the continuation of g.
> I'm sorry but you have just restated the definition of > composition. In (f (g)), f *is* the continnuation of (g). Just what is > it that you consider an error?
Assume f has two parameters and g has two results.
Consider a loose interpretation of the "what happens to extra values" question. The error is that the application fails to initialize f's second argument.
Consider a strict resolution of the "what happens to extra values" question. The error is that g passes two values to the continuation that intiailizes f's first argument.
See the cps below for precise details.
>> How do I do that? The call-with-values procedure.
> Which is arguably a much clunkier syntax than the natural expression > of composition.
The syntax you call the "natural expression of composition" isn't an expression of composition. G's continuation is the initializer of f's first argument, not f itself. It is only equivalent if (a) f has only one parameter, and (b) g has only one result.
>> There is a big huge difference between "(g) initializing the first >> argument of f" and "f being the continuation of (g)".
***MISSING** our first error. To see the other error, just substitute g-cps into that expression.
((lambda (K) (K a b)) (lambda (x-tmp) ((lambda (y-tmp) (f-cps K x-tmp y-tmp)) ***MISSING***)))
and reduce:
((lambda (x-tmp) ((lambda (y-tmp) (f-cps K x-tmp y-tmp)) ***MISSING***)) a b***EXTRA***)
and so b***EXTRA*** is our second error. Obviously b***EXTRA was supposed to end up where ***MISSING*** is, so the CPS transform very clearly shows both how problems that result from the "compose" confusion.
>> In your world, the operator is indeed the continuation of the first >> and only argument. It sort of makes every call into a >> call-with-values.
> Precisely. This is the basic theory of the _Lambda, the Ultimate_ > papers, actually.
No, the lambda described in the ultimate papers doesn't work that way, and it doesn't require magic tuples to fix the mistakes introduced by the feature. Call-with-values introduces the same feature without any mistakes, so I can't understand why a few people seem to hate it.