This does not include multilevel or lambda-list destructuring (which I think might be too complex functionality to add to let), but satisfies my desire for binding multiple results from a function when the results are returned either as multiple values or in a list.
* Mark Stickel wrote: > http://www.ai.sri.com/~stickel/mvlet.lisp > contains macros for MVLET and MVLET* that extend the LET and LET* > forms to include bindings of the form > ((:values var1 var2 var*) [init-form]) > ((:list var1 var2 var*) [init-form]) > ((:list* var1 var2 var*) [init-form])
One thing that occurred to me about these complex multipurpose LET-like syntaxes being proposed is that, if they are intended eventually to replace LET, then one needs to think about the status of LET itself. If they are *not* then the remainder of this article is not particularly interesting, but I think that they are intended so to do at least be some proposers.
LET is currently a special operator in CL. But these extensions would make it much more complex than any other special operator in the language. Further, some of these extensions are naturally defined in terms of other operators (MULTIPLE-VALUE-BIND for instance), which are themselves in fact macros.
So really, a LET extended like this should not be a special operator. If there are problems with its being written as a macro (which I think must only be declaration handling), then they should be fixed so it can be. So, there's then a choice of making LET not be special at all -- it can be defined as a macro easily enough (I think!), or of providing some kind of primitive SUBLET which just did what LET does now, and was special.
I presume the reasons that LET is defined as special now would still hold, so there should still be some special operator provided to do the basic variable-binding thing.
this syntax is very similar to what I suggested recently. I think it can be done better, and even w/o keywords, but this is nice, for now. People can experiment with it, and see if they like it.
My own opinion, is that LET should remain a special operator with backwards-compatibility, but extend the syntax to handle both MVs and destructuring. It is great the the CAR of each binding is an atom, because now the language designers will be able to provide new meaning to lists in the CAR position, i.e.:
or something along these lines. Personally, I'd like LET handle both MVs and destructuring. If they decided to pick only one of those two, I'd imagine they'd pick destructuring, though everyone seems to want MVs. You can get the list back with MULTIPLE-VALUE-LIST anyway. I'm guessing that b/c that's what happened with LOOP (AFAIK, LOOP doesn't easily handle MV bindings in the prologue).
The reason for making the new LET backwards compatible is to prevent Lisp from adding another operator; plus, if they did, and it worked out nicely, then it would render so several current operators/macros obsolete, like LET, LET*, and MULTIPLE-VALUE-BIND. Better to just make LET more general and do away with MULTIPLE-VALUE-BIND. They've already de-necessitated MULTIPLE-VALUE-SETQ with (SETF VALUES).
although I can see the value of these binding forms, we should not forget that DESTRUCTURING-BIND and MULTIPLE-VALUE-BIND go through a lot more work than they appear to be and that it's a lot harder to specify exactly how these things are supposed to interact with missing values than one might believe from watching the trivial cases.
I note in passing that the generated machine code for the following two forms are identical in Allegro CL 5.0.1 for all the machines I have tested it on. (Duane?)
(let (a b) (setf (values a b) (truncate pi 3)) (list a b)) (multiple-value-bind (a b) (truncate pi 3) (list a b))
| Personally, I'd like LET handle both MVs and destructuring.
I think MULTIPLE-VALUE-BIND and DESTRUCTURING-BIND abuse indentation and clutter up the binding/setting distinction, and I see no reason why we should now fix that non-problem by cluttering up the meaning of binding so much we don't know what we're getting, anymore.
when I use MULTIPLE-VALUE-BIND and DESTRUCTURING-BIND, it is around short pieces of code because I frequently need to rearrange the returned values before using them as arguments for another function call, or even to avoid the overhead of MULTIPLE-VALUE-CALL, which I would much prefer to be implemented at least as efficiently as APPLY (and perhaps with the same underlying machinery, even possibly exposed as an argument sequence).
* Dave Bakhash wrote: > My own opinion, is that LET should remain a special operator with > backwards-compatibility, but extend the syntax to handle both MVs and > destructuring. It is great the the CAR of each binding is an atom, > because now the language designers will be able to provide new meaning > to lists in the CAR position, i.e.:
That's exactly what I was trying to argue against. Special operators should *not* be vast complex things partly defined in terms of other macros like this proposed LET, they should be primitive, simple things like QUOTE.
If LET is going to get big & hairy then it should lose its status as a special operator. Assuming the reasons for LET being special in the first place are still valid, then a new special operator, (`SUBLET') needs to be added, which does what LET does now.
(Or the new hairy operator could be called something else, like BIND, which leaves the base language undisturbed, but some people seem allergic to that.)
[...] > If LET is going to get big & hairy then it should lose its status as a > special operator. Assuming the reasons for LET being special in the > first place are still valid, then a new special operator, (`SUBLET') > needs to be added, which does what LET does now. > > (Or the new hairy operator could be called something else, like BIND, > which leaves the base language undisturbed, but some people seem > allergic to that.)
I'm not; I agree that LET should be left as it is. If we are to have a new and improved Swiss army knife of a binding construct (I don't know if it is really such a good idea), the proper approach I think is the one taken by LOOP. None of the other iteration constructs in Lisp were disturbed; for some time people used it as an extension to the language, and after experience with it was accumulated, it was adopted as part of the standard. So let it be with binding. After all, an omnipotent binding construct is a matter of macrology; then, if it is useful, people will start using it and after some time, perhaps, there will be enough voices saying `we want BIND into the language, it is indispensable.'
Erik Naggum <e...@naggum.no> writes: > I note in passing that the generated machine code for the following two > forms are identical in Allegro CL 5.0.1 for all the machines I have > tested it on. (Duane?)
> (let (a b) (setf (values a b) (truncate pi 3)) (list a b)) > (multiple-value-bind (a b) (truncate pi 3) (list a b))
Yes. One internal tool I use to figure out what the compiler is doing is excl::compiler-walk. It doesn't always tell the truth, (thus its obscurity) because it can't always emulate the precise environment that the compiler will actually see. And in this case, it is not quite right, but with a little insider help I can get it close enough:
USER(3): (let ((comp::.for-value. 'comp::multiple)) (excl::compiler-walk '(let (a b) (setf (values a b) (truncate pi 3)) (list a b)))) (LET (A B) (LET* () (MULTIPLE-VALUE-BIND (#:G43 #:G44) (TRUNCATE 3.141592653589793d0 3) (LET () (SETQ A #:G43) (SETQ B #:G44) (VALUES #:G43 #:G44)))) (EXCL::.PRIMCALL 'SYSTEM::QLIST2 A B)) USER(4): (let ((comp::.for-value. 'comp::multiple)) (excl::compiler-walk '(multiple-value-bind (a b) (truncate pi 3) (list a b)))) (MULTIPLE-VALUE-BIND (A B) (TRUNCATE 3.141592653589793d0 3) (EXCL::.PRIMCALL 'SYSTEM::QLIST2 A B)) USER(5):
If you try this without the magic in front the walker gets the wrong idea about where the results are going and so tries to use the one-return version of truncate. The awkwardness of these tools is one reason why I am working on compiler-environments to provide access to the compilation and macroexpasion process.
-- Duane Rettig Franz Inc. http://www.franz.com/ (www) 1995 University Ave Suite 275 Berkeley, CA 94704 Phone: (510) 548-3600; FAX: (510) 548-8253 du...@Franz.COM (internet)
* Erik Naggum wrote: > (let ((x 3) (y 4) q r) > (setf (values q r) (truncate x y)))
The problem with this is that you can't put reasonable declarations in. You'd like to be able to say:
(let (a b) (declare (type good-type a b)) (setf (values a b) ...))
But you can't because A and B aren't always of GOOD-TYPE. You could wrap a LOCALLY around the inner bit, but that's kind of obviously a hack. At least one compiler (CMUCL) cares about these things quite a lot. Of course for your case it's easy because you can just invent cheap initial values, but if the values are large objects there may be no easy way to get useful initial values.
At least in the case of multidimensional arrays, type declarations for large objects really can help, because a suitable declaration with information about bounds can help compilers generate much better indexing code.
(The SERIES package has exactly this problem, which is why I know about it...)
* Tim Bradshaw <t...@tfeb.org> | The problem with this is that you can't put reasonable declarations in. | You'd like to be able to say: | | (let (a b) | (declare (type good-type a b)) | (setf (values a b) ...)) | | But you can't because A and B aren't always of GOOD-TYPE.
I would insist that they are, and that a compiler that barfs on this is missing the point and is just being too anal about its type checking. if there are no references to a local binding prior to its first being set, who cares that it has to be NIL if ever there WERE a reference to it? the only way that could happen is if you change the code and recompile, and then it should barf. note that the language specification on this point may be interpreted to death to support both your point and mine.
* Erik Naggum wrote: > I would insist that they are, and that a compiler that barfs on this is > missing the point and is just being too anal about its type > checking.
I think that's kind of reasonable, but ...
> note that the language specification on this > point may be interpreted to death to support both your point and mine.
it seems to me that the standard is reasonably clear, in particular the `Declaration TYPE' entry in 3.8 says:
At the moment the scope of the declaration is entered, the consequences are undefined if the value of the declared variable is not of the declared type.
And I think that's reasonably clear. But there may be other things which lead to other interpretations (my experience of this is just based on talking to CMUCL people when I discovered that CMUCL took this interpretation).
* Tim Bradshaw <t...@tfeb.org> | At the moment the scope of the declaration is entered, the | consequences are undefined if the value of the declared variable is | not of the declared type. | | And I think that's reasonably clear.
well, it sort of depends on how you interpret "the value of a variable". I would argue that if a variable isn't actually accessed, neither does it have a value. I'm aware that the specification isn't fully with me on this one, but in my view, there's a distinction between A and B in this respect in the form (LET ((A NIL) B) ...), if for no other reason than that it communicates a different intent.
in any case, when the behavior is undefined for value references, and there aren't any references, no undefined behavior will occur, right?
About (let (a b) (declare (something-non-nil a b)) (setf (values a b) ...
Erik Naggum <e...@naggum.no> writes: > * Tim Bradshaw <t...@tfeb.org> > | At the moment the scope of the declaration is entered, the > | consequences are undefined if the value of the declared variable is > | not of the declared type. > | > | And I think that's reasonably clear.
[ ... ]
> in any case, when the behavior is undefined for value references, and > there aren't any references, no undefined behavior will occur, right?
I may want to talk later about some of the specific compiler issues brought up in this thread about scope, liveness, and trees falling in the desert ... but for now I'd like to address an issue that this conversation triggers in me that we don't talk about very much. Perhaps that is because we don't have a name for it, and so I took the liberty of coining a phrase "implementationally portable", to which I modified the subject line. If anyone has a better name for it, I'm open to suggestions. Hopefully its name defines itself relatively obviously, at least after my discussion below.
Tim is entirely correct that this is nonconformant code, and thus should not be done in conforming, or portable, code. However, Erik has a point of practical significance, and that is that practice does not always include strict conformance. Specifically, the "is undefined" is not the same as "is an error", and means that the vendor/implementor of the lisp is free to assign defined semantics, either by default or in a public interface, as to what might happen. Thus, as long as the user stays within the vendor's prescribed interface, the code is portable, at least within the particular lisp or lisps for which the interface is prescribed.
Obviously, implementational portability is not covered in the spec; it is never the duty of any spec to specify the unspecified :-) But I believe that it is a useful concept, and may deserve some attention.
I won't have time to expand on this much, but here are some thoughts:
1. If every vendor prescribed the same semantics to otherwise nonconforming code, then it becomes a de-facto standard. Code written to this standard can't be considered conformant, but is nonetheless usable. The downside is that if a new implementation enters the fray, it has a hard time living up to the de-facto standards set by the other implementations. A good example of this is the PCL implementation, which was the basis for many (but not all) CLOS implementations. If a user started counting on functionality provided by PCL extensions, then the code might be portable to all PCL-based CLOS implementations, but not to non-PCL based versions.
2. A widely used technique for establishing implementational portability is the use of #+/#- to conditionalize. The plus side is that since the number of implementations is finite, any source can be made implementationally portable by porting to all implementations, but the down side is that new implementations will almost certainly not fit into the #+/#- portability, without changing the source to add the new version.
3. Within any one lisp implementation, but not necessarily on the same architecture, implementational portability is somewhat practical, to the extent that the vendor/implementor implements the same extensions to all of their lisps. To my knowledge, most CL vendors do a fairly good job of providing implementational portability within their rang of architectures.
The major danger of relying on implementational portability is that since it is not part of the CL spec, and in fact it may not even be documented or spec'd explicitly by the implementor, it thus may change later, breaking your program (and angering you). Protection from this requires a good relationship with the supplier of your lisp (or, if you have source code, a willingness to get in a change it).
-- Duane Rettig Franz Inc. http://www.franz.com/ (www) 1995 University Ave Suite 275 Berkeley, CA 94704 Phone: (510) 548-3600; FAX: (510) 548-8253 du...@Franz.COM (internet)
Duane Rettig <du...@franz.com> writes: > include strict conformance. Specifically, the "is undefined" is not the > same as "is an error", and means that the vendor/implementor of the lisp > is free to assign defined semantics, either by default or in a public > interface, as to what might happen. Thus, as long as the user stays within > the vendor's prescribed interface, the code is portable, at least within > the particular lisp or lisps for which the interface is prescribed.
In my haste, I forgot to add a thought to this paragraph:
In other words, "is an error" means "don't do it", whereas "is undefined" implies "ask your vendor or don't do it", and asking your vendor may either yield extra functionality, or it may also yield "don't do it".
-- Duane Rettig Franz Inc. http://www.franz.com/ (www) 1995 University Ave Suite 275 Berkeley, CA 94704 Phone: (510) 548-3600; FAX: (510) 548-8253 du...@Franz.COM (internet)
Erik Naggum <e...@naggum.no> writes: > I'm aware that the specification isn't fully with me on > this one, but in my view, there's a distinction between A and B in this > respect in the form (LET ((A NIL) B) ...), if for no other reason than > that it communicates a different intent.
Well, fwiw, the spec says (as you admit) they're the same but *stylistically* I personally make the same distinction you do.
On a related point, I also try hard always to do &optional (foo nil) or &optional (foo '()) when I mean to have one of these get used for value before some check that the value was supplied. I mostly do not like &optional foo
* Duane Rettig wrote: > In my haste, I forgot to add a thought to this paragraph: > In other words, "is an error" means "don't do it", whereas "is undefined" > implies "ask your vendor or don't do it", and asking your vendor may either > yield extra functionality, or it may also yield "don't do it".
I agree, and the problem with the particular case of LET & initial values is that of the three compilers I use regularly, one (CMUCL) complains always, one (Allegro) allows it (always, I think?) and one (Genera) complains in a particular funny case (arrays). Unfortunately there's a large package for Lisp (series) which generates exactly this kind of code and which is sufficiently obscure in it's implementation that I couldn't work out how to fix it and eventually gave up using it as a result (because CMUCL is an important implementation for me).
So I wasn't really trying to make the gratuitous language-lawyer point that it might have seemed I was (if it did!), I've actually been bitten by exactly this problem in various nasty ways, which perhaps causes me to overreact.
Tim Bradshaw <t...@tfeb.org> writes: > * Duane Rettig wrote: > > In my haste, I forgot to add a thought to this paragraph:
> > In other words, "is an error" means "don't do it", whereas "is undefined" > > implies "ask your vendor or don't do it", and asking your vendor may either > > yield extra functionality, or it may also yield "don't do it".
I think "is unspecified" means ask the vendor what the funny value is that comes back here or what mostly benign thing happens or whether there is an extension hiding.
I think "is undefined" means ask the vendor but expect them to tell you that you will fry your disk or crash your machine if you try.
I think "is an error" means we didn't think hard enough about this at language design time to have a considered opinion on which of the above two categories something falls into.
None of these opinions should be interpreted as me interpreting the spec. These are just my private, personal beliefs as a user.
As a rule, I did not feel that as the editor I had the ability to casually change things from one category to another. I pretty much tried to preserve what CLTL said except in specific places where I was directed to do otherwise by X3J13, usually through an approved vote on an issue writeup. So don't assume that because I held the "pen", my interpretations above are what is reflected in the actual text.
In article <sfwg11ftl8w....@world.std.com>, Kent M Pitman <pit...@world.std.com> wrote:
>I think "is an error" means we didn't think hard enough about this at >language design time to have a considered opinion on which of the above two >categories something falls into.
I thought we got rid of the phrase "is an error". That was the language that CLTL used, but in the ANS it became "consequences are undefined".
-- Barry Margolin, bar...@bbnplanet.com GTE Internetworking, Powered by BBN, Burlington, MA *** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups. Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
Barry Margolin <bar...@bbnplanet.com> writes: > In article <sfwg11ftl8w....@world.std.com>, > Kent M Pitman <pit...@world.std.com> wrote: > >I think "is an error" means we didn't think hard enough about this at > >language design time to have a considered opinion on which of the above two > >categories something falls into.
> I thought we got rid of the phrase "is an error". That was the language > that CLTL used, but in the ANS it became "consequences are undefined".
As a matter of policy, I'm sure you're right. At the detail level, I can't remember if the policy was carried through or how the undefined/unspecified split was handled. It might be that I just defaulted everything to the scarier "undefined" or it might be I left some as were originally. It's been a while and my memory is pretty uncertain. Maybe sometime I'll look it up to be sure. But it won't be today.
Discussion subject changed to "OT: error terms in an evryday situation (Ex: Re: Implementational Portability (was: multiple-value binding let and let*))" by Vassil Nikolov
Duane Rettig wrote: > ...I took the liberty of coining a phrase "implementationally portable"...if > anyone has a better name for it, I'm open to suggestions...
What about "implementationally potable?" as in easily consumed :-)
> Tim is entirely correct that this is nonconformant code, and thus should not be > done in conforming, or portable, code. However, Erik has a point of practical > significance, and that is that practice does not always include strict > conformance. Specifically, the "is undefined" is not the same as "is an > error", and means that the vendor/implementor of the lisp is free to assign > defined semantics, either by default or in a public interface, as to what might > happen.
From tim's posting it seems that implementations deal with undefined in different ways. I'm am interested by all of this. And I will now make what I'm sure is a blindingly obvious point: If a compiler finds something that is undefined, that is something that is not an error or not valid, would it not be better to carp rather that barf (like you do with an error) or purr silently (like when everything is right with the world). Whether this is something you would want to do at compile time or read time I don't know ;-)
Although I accept the point about 'contact your vendor', these seem to me to be subtle errors, (as highlighted by this thread), and who would you know to do this? Only those who are sufficiently experienced or with access to the ANSI standard (or is this to be a requirement of all cl programmers?) I could see somebody as cack handed as myself getting seriously burnt some day and spending alot of time recovering. So here is a heart felt request for more carping.
Please flame away, as I realise that I have probably missed the point (again).
In article <sfwlnb7qlkn....@world.std.com>, Kent M Pitman <pit...@world.std.com> wrote:
>Barry Margolin <bar...@bbnplanet.com> writes: >> I thought we got rid of the phrase "is an error". That was the language >> that CLTL used, but in the ANS it became "consequences are undefined".
>As a matter of policy, I'm sure you're right. At the detail level, I can't >remember if the policy was carried through or how the undefined/unspecified >split was handled. It might be that I just defaulted everything to the >scarier "undefined" or it might be I left some as were originally. It's been >a while and my memory is pretty uncertain. Maybe sometime I'll look it up >to be sure. But it won't be today.
Actually, I think this was something that Kathy did before you took over, which may be why you don't remember making the changes.
-- Barry Margolin, bar...@bbnplanet.com GTE Internetworking, Powered by BBN, Burlington, MA *** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups. Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
In article <37BD6A2B.3FCE9...@pindar.com>, William Deakin <w.dea...@pindar.com> wrote:
>From tim's posting it seems that implementations deal with undefined in different >ways. I'm am interested by all of this. And I will now make what I'm sure is a >blindingly obvious point: If a compiler finds something that is undefined, that >is something that is not an error or not valid, would it not be better to carp >rather that barf (like you do with an error) or purr silently (like when >everything is right with the world). Whether this is something you would want to >do at compile time or read time I don't know ;-)
There's no single answer to this, because there are a number of reasons why some things are left undefined. Sometimes it's because they're difficult to detect, particularly at compile time; if they require run-time checks, the programmer may want to disable them for optimization purposes. Other times it's because we didn't have agreement that a particular behavior was necessary, and wishes to allow implementations leeway (undefined situations allow for implementation extensions).
-- Barry Margolin, bar...@bbnplanet.com GTE Internetworking, Powered by BBN, Burlington, MA *** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups. Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
Barry Margolin <bar...@bbnplanet.com> writes: > In article <sfwlnb7qlkn....@world.std.com>, > Kent M Pitman <pit...@world.std.com> wrote: > >Barry Margolin <bar...@bbnplanet.com> writes: > >> I thought we got rid of the phrase "is an error". That was the language > >> that CLTL used, but in the ANS it became "consequences are undefined".
> >As a matter of policy, I'm sure you're right. At the detail level, > >I can't remember if the policy was carried through or how the > >undefined/unspecified split was handled. It might be that I just > >defaulted everything to the scarier "undefined" or it might be I > >left some as were originally. It's been a while and my memory is > >pretty uncertain. Maybe sometime I'll look it up to be sure. But > >it won't be today.
> Actually, I think this was something that Kathy did before you took over, > which may be why you don't remember making the changes.
That could well be. She did a lot of the rearranging of the structure of the document in that time, and might have done some microrearranging, too. I know it was under her tenure that we took some votes on various policies of overall look and feel. I still have copies of all the states of the document as it evolved through that time. Maybe sometime I'll put that up on an ftp site just for grins in case anyone wants to research it. And I have the hardcopy of each of the drafts, which I either need to get a bigger house for (since I don't have room for a display area at my current house) or I need to offer up on ebay... They're kinda cool, really. Like the symbolics doc set, which is also in storage. Had to make some sacrifices to the fun of having it all out in order to find semi-affordable housing in Boston area. Real estate is too high even around here. And I understand it's dirt cheap compared to the Bay Area. Dunno how they can stand it. Pay must be awesome out there... though I imagein it must all go straight into real estate interest...
* William Deakin | Although I accept the point about 'contact your vendor', these seem to me | to be subtle errors, (as highlighted by this thread), and who would you | know to do this? Only those who are sufficiently experienced or with | access to the ANSI standard (or is this to be a requirement of all cl | programmers?) I could see somebody as cack handed as myself getting | seriously burnt some day and spending alot of time recovering.
a serious programmer who does not know what to expect from his compiler is a contradiction in terms. I don't think _any_ programmer who has not actually read and actually understood the specification of his language, or at least the parts thereof that he uses, should be allowed to write code for other people than himself unless it occurs under very close supervision by someone who does know the specification.
of course, this is predicated on the desire to see programming as a professional discipline on par with law, medicine, auditing, etc, not the (continuation of) largely unskilled labor that happens to get paid to do any kind of non-quality work because the market is in desperate need of just about _anything_ to solve their mostly perceived problems or to keep going after making a seriously misguided decision about which software to invest in in the first place.