there's something that's had me curious for some time now. Given who wrote the relevant CLtL2 chapter, it seems like a question directed particularly at Kent Pitman, but of course any feedback is welcome.
So, I'm interested in knowing the history and background behind the CL conditions and restarts system. I know it drew directly from the dialects it was standardising, in particular, I seem to rememeber reading another post that mentioned Symbolics had a particularly comprehensive collection of standard conditions. However, I'd like to know where it came from to Symbolics. Was Lisp the first to have conditions? Was it influenced by something else? Was it very much ahead of other languages by having a conditions system?
Even more interesting are restarts. Whereas most serious languages today offer exceptions, ones to include a built-in support for restarting computation afterwards are exceptionally rare; in fact, I haven't encountered any that wouldn't be Lisps (and Dylan is a Lisp for purpose of this classification. From what I understand, Smalltalk also has restarts, but it's arguably in the Lisp family as well). Casual googling around doesn't suggest I missed any very prominent languages, either. There's a considerable mental gap between having a system that supports non-local transfer control in the form of exceptions, and a system that includes a formalised protocol for recovering and restarting computation, as evidenced by the lack of languages to offer such a facility.
I'm still amazed by how woefully incomplete an exception system without structured means of recovery is, yet how I never felt that prior to learning CL. Therefore I'm really interested in learning the origins of the concept. I suspect the strong tradition of interactive development and built-in debugger naturally leads to the development of a complete error handling system, whereas in environments where successful and failed execution alike leads to process termination, one might never consciously realise such a need. But that's just my guess, which might, or might not have historical evidence to support it. I hope the village elders will enlighten me in this regard :)
> there's something that's had me curious for some time now. Given who > wrote the relevant CLtL2 chapter, it seems like a question directed > particularly at Kent Pitman, but of course any feedback is welcome.
> So, I'm interested in knowing the history and background behind the CL > conditions and restarts system. I know it drew directly from the dialects > it was standardising, in particular, I seem to rememeber reading another > post that mentioned Symbolics had a particularly comprehensive collection > of standard conditions. However, I'd like to know where it came from to > Symbolics. Was Lisp the first to have conditions? Was it influenced by > something else? Was it very much ahead of other languages by having a > conditions system?
> Even more interesting are restarts. Whereas most serious languages today > offer exceptions, ones to include a built-in support for restarting > computation afterwards are exceptionally rare; in fact, I haven't > encountered any that wouldn't be Lisps (and Dylan is a Lisp for purpose > of this classification. From what I understand, Smalltalk also has > restarts, but it's arguably in the Lisp family as well). Casual googling > around doesn't suggest I missed any very prominent languages, either. > There's a considerable mental gap between having a system that supports > non-local transfer control in the form of exceptions, and a system that > includes a formalised protocol for recovering and restarting computation, > as evidenced by the lack of languages to offer such a facility.
> I'm still amazed by how woefully incomplete an exception system without > structured means of recovery is, yet how I never felt that prior to > learning CL. Therefore I'm really interested in learning the origins of > the concept. I suspect the strong tradition of interactive development > and built-in debugger naturally leads to the development of a complete > error handling system, whereas in environments where successful and > failed execution alike leads to process termination, one might never > consciously realise such a need. But that's just my guess, which might, > or might not have historical evidence to support it. I hope the village > elders will enlighten me in this regard :)
Interesting artice. Maybe I'm a bit dense, but what exactly is the difference between handler-bind and handler-case and where would you use one or the other?
On Mon, 26 Nov 2007 23:21:24 +0100, "John Thingstad" <jpth...@online.no> wrote: > Maybe I'm a bit dense, but what exactly is the difference between > handler-bind and handler-case and where would you use one or the > other?
Den Mon, 26 Nov 2007 23:21:24 +0100 skrev John Thingstad:
> Interesting artice. > Maybe I'm a bit dense, but what exactly is the difference between > handler-bind and handler-case and where would you use one or the other?
No, it's actually confusing and not obvious at all if you don't already know. The crux of the issue is that spec for HANDLER-CASE says "In this case, the dynamic state is unwound appropriately". Which, in particular, means no going back to the signalling site. Thus HANDLER-CASE is pretty much like a try..catch block.
HANDLER-BIND, on the other hand, does not unwind the stack before invoking your handlers. So if the signalling site has established any restarts, you can invoke them.
John Thingstad writes: > Maybe I'm a bit dense, but what exactly is the difference between > handler-bind and handler-case and where would you use one or the > other?
Funny thing you should ask that today. I was just messing with these two for the first time two nights ago, and completely missed the critical difference when reading about them in the Hyperspec. I then proceeded to waste nearly two hours puzzling over why I couldn't get something simple working, and came this close to posting a question here before I figured it out. Maciej explained the critical difference exactly. I'd just add that handler-case is is analogous to a simple case form.
Maciej Katafiasz <mathr...@gmail.com> writes: > So, I'm interested in knowing the history and background behind the CL > conditions and restarts system. I know it drew directly from the dialects > it was standardising, in particular, I seem to rememeber reading another > post that mentioned Symbolics had a particularly comprehensive collection > of standard conditions. However, I'd like to know where it came from to > Symbolics. Was Lisp the first to have conditions? Was it influenced by > something else? Was it very much ahead of other languages by having a > conditions system?
> Even more interesting are restarts. Whereas most serious languages today > offer exceptions, ones to include a built-in support for restarting > computation afterwards are exceptionally rare; in fact, I haven't > encountered any that wouldn't be Lisps (and Dylan is a Lisp for purpose > of this classification. From what I understand, Smalltalk also has > restarts, but it's arguably in the Lisp family as well).
It's been difficult getting these ideas out into the mainstream. I had a terrible time getting the CL community into them. I didn't invent them, but I was worried they would not ever leave the Lisp Machine, and had numerous discussions with vendors EVEN AFTER producing the sample implementation above and handing it to them and telling them they could use it where they told me the ridiculous claim that a fancy condition system like the Lisp Machine had required special hardware.
Months or even years later, some of them called me back and said kind of sheepishly, "uh, we tried that thing and gee, it seems to work. can we use it?" And then gradually one started to see marketing literature at conferences saying they implemented Kent Pitman's condition system, which was kind of flattering, but pointed to the fact that it didn't have a catchy name.
(But at least my case was better than what happened to David Gray at TI, where his streams proposal came to be called Gray Streams and then people on the other side of the pond started re-spelling it Grey because they thought that it was a color ... or at least a colour.)
Anyway, the pollination of an idea from one language to another is a tricky business that doesn't happen easily.
Just look at the similar difficulty we've had getting pretty printing into the world in general. Dick Waters wrote papers back in the early 80's showing that the techniques we use in CL are perfectly usable for pretty printing code in block structured languages like Ada and PL/1. Yet neither those languages nor their successors picked up on the option to add such a facility to their programmatic repertoire, perhaps because they were already too busy not picking up on the idea of having a homoiconic language...
> Casual googling > around doesn't suggest I missed any very prominent languages, either.
You're looking for PL/1 for Honeywell Multics, and particularly the MIT variant, which may not have been precisely what Honeywell had, I'm not sure. See http://www.multicians.org/multics.html which may say.
Several of the guys who designed the Lisp Machine had previously been using that system, which amounted to a "PL/1 machine" (not in quite the sense that a LispM was a "Lisp Machine", but certainly in the sense that the operating system was written in a high level language, PL/1, and there was a unifying execution model on the stack which was best described by PL/1).
The restart facility was not so linguistic as practical. I think it used some sort of 'on signal' construct (my PL/1 was never advanced, though I used it in school, and is quite rusty now, so at this point accept that this is all blurry and was second-hand to start with).
And remember, too, that the world was in a kind of proto-state where the ideas were coming fast and furious ahead of platforms to implement them on. So they had the abstractions, but they needed a place to deploy those ideas in a new context. So they lept to the Lisp Machine, and started to use that platform as a forum to evolve new ideas.
Dave Moon (David A. Moon in the literature) had done Maclisp for PL/1 and was later a chief architect of the LispM system.
Bernie Greenberg was one of the most prominent Multicians, and implemented Emacs for Multics in Lisp (no, not GNU Lisp, nor GNU Emacs. This was long before those days. I think he used Multics Maclisp.) Bernie later did the LMFS file system, among other things, for Genera.
Others are enumerated in the Revision-18 paper mentioned above.
Anyway, if you follow the people, not the languages, you get a better sense of the flow of things. These people hopped from one implementation and system to another, carrying ideas with them.
Likewise you can see that UNWIND-PROTECT is present in things like ITS TECO [as the ..N q-register, which was executed upon unwind], but again the unifying characteristic is the community at MIT in which this arose. I'm not sure who created ..N, maybe Steele, but certainly Steele was present, and was present for UNWIND-PROTECT in CL, and later for "finally" in Java.
Actually, what was strange and frustrating was that Steele was so involved in Java and didn't put restarts into it, when he surely must have known of them... and I think they mesh so nicely with continuations, etc. that it's a shame.
For myself, I used Lisp Machines, and liked them, but there were not many of them and many people couldn't use them. I was amused and saddened by how many of their ideas were regarded as "needing special hardware", and I made it a personal mission to show that many LispM ideas could be rehosted on stock hardware (i.e., off-the-shelf, or non-special-purpose hardware) just fine. That was how my ZBABYL mail reader came about for TECO-based Emacs, and it's why I took an interest in getting the Zetalisp condition system into CL. (Early CL didn't even have ERRSET or IGNORE-ERRORS, if memory serves me, so any program that ever signaled ERROR under CLTL was dead in the water as far as guarantees of portability. But the idea of adding just that one operator as a way of "fixing" the problem was horrifying to me, so I wanted as much of the LispM New Error System as I could get.)
> There's a considerable mental gap between having a system that supports > non-local transfer control in the form of exceptions, and a system that > includes a formalised protocol for recovering and restarting computation, > as evidenced by the lack of languages to offer such a facility.
Yeah, weird, huh? But I think the thing to understand is that people judge the goodness of what they have in contrast to what they had before. So most users of languages don't see themselves as impoverished because they have previously used languages without try/catch and now they have try/catch, so they've moved up. We've had our eyes opened, so it's hard to go back. But convincing someone they are missing out is hard.
> I'm still amazed by how woefully incomplete an exception system without > structured means of recovery is, yet how I never felt that prior to > learning CL.
"A mind once stretched by a new idea never regains its original dimension." -- Oliver Wendell Holmes
And, incidentally, without a practice of defining the recovery points. While we got restarts into CL, we were unable to get many specific restarts in. And that's an aggravation. But again it was lack of experience with the system, not to mention considerable political disagreement over the nature of the inheritance model (I've spoken on this in other forums, but ask me if the relevance of this in this context is not clear and I'll expand), that interfered with getting some specific conditions and many specific restarts added.
But my theory was that if we could get at least a few in, people would come to understand the impoverished nature and would naturally want more. I have a meta-theory of design that says that if you don't offer a feature, no one will send bug reports, but if you offer a stub of a feature, everyone will complain wildly for extensions to it. So the real hurdle, IMO, is to get the proverbial "foot in the door". Having ABORT, CONTINUE, USE-VALUE and STORE-VALUE and having them defined in only a few places is very weak... but it illustrates an idea that now people know they need fleshed out. So it was enough.
> Therefore I'm really interested in learning the origins of > the concept. I suspect the strong tradition of interactive development > and built-in debugger naturally leads to the development of a complete > error handling system,
This is certainly relevant, though it's a feedback loop since what leads to liking the debugger is the good facility.
I think the homoiconicity reference above is also relevant, since the usefulness of the debugger is also helped by the ability to use a familiar langauge to operate in it. Visual Studio finally has ways of doing this in clumsy ways with Watch and Immediate panes, but for years this was really hard. And even now it's marginal and unpleasant by comparison to Lisp in my opinion--though some might prefer it (at least it's finally to the point where there can be a debate on the matter).
> whereas in environments where successful and > failed execution alike leads to process termination, one might never > consciously realise such a need.
Indeed, the mere step up from debugging binary dumps was important. The Macintosh "bomb" icon used to pass for error handling.
> > So, I'm interested in knowing the history and background behind the CL > > conditions and restarts system. I know it drew directly from the dialects > > it was standardising, in particular, I seem to rememeber reading another > > post that mentioned Symbolics had a particularly comprehensive collection > > of standard conditions. However, I'd like to know where it came from to > > Symbolics. Was Lisp the first to have conditions? Was it influenced by > > something else? Was it very much ahead of other languages by having a > > conditions system?
> > Even more interesting are restarts. Whereas most serious languages today > > offer exceptions, ones to include a built-in support for restarting > > computation afterwards are exceptionally rare; in fact, I haven't > > encountered any that wouldn't be Lisps (and Dylan is a Lisp for purpose > > of this classification. From what I understand, Smalltalk also has > > restarts, but it's arguably in the Lisp family as well).
> It's been difficult getting these ideas out into the mainstream. I > had a terrible time getting the CL community into them. I didn't > invent them, but I was worried they would not ever leave the Lisp > Machine, and had numerous discussions with vendors EVEN AFTER > producing the sample implementation above and handing it to them and > telling them they could use it where they told me the ridiculous claim > that a fancy condition system like the Lisp Machine had required > special hardware.
One of the best examples was that the feature that you can continue from an error (and the handler is called without unwinding the stack) didn't make it into the C++ standard. Though Stroustrup (Design and Evolution of C++) reports that they had people from Texas Instruments with Lisp Machine experience explaining the Lisp Machine error handling. According to the TI guys (can't remember the exact wording, don't have the book handy), this feature was not used much. So it did not make it into C++.
I think the CL error handling facility is extremely useful and I always see it as a good sign, if a Common Lisp implementation not just provides the functionality, but actually uses it. It helps me. Especially if the development environment uses it (system facility, REPL, ...) and there are many useful restarts provided.
So, Kent, I thank you for your work on helping it to be in ANSI CL and the implementors who chose not to ignore it. ;-)
On Nov 27, 9:06 am, Rainer Joswig <jos...@lisp.de> wrote:
> One of the best examples was that the feature that you can > continue from an error (and the handler is called > without unwinding the stack) didn't make it into the C++ standard. > Though Stroustrup (Design and Evolution of C++) reports > that they had people from Texas Instruments with Lisp > Machine experience explaining the Lisp Machine error handling. > According to the TI guys (can't remember the exact wording, don't have > the book handy), this feature was not used much. So it did > not make it into C++.
I refer to that book as Stroustrup's "Design Rationalization and Mutation of C++". That's section 16.6, "Resumption vs. Termination". At a meeting in 1991, several people who had had long experience with languages with continuable exceptions (including Mary Fontana from TI and Jim Mitchell from Sun/PARC) basically said "continuable exceptions are not particularly useful except for debugging, and they allowed us to write bad code."
Personally, I *like* having the debugger available all the time, and have never been persuaded by "this feature is dangerous when used by amateurs" arguments (which were routinely used by the C++ designers to limit C++).
bob_bane <bob.b...@gst.com> writes: > On Nov 27, 9:06 am, Rainer Joswig <jos...@lisp.de> wrote: >> One of the best examples was that the feature that you can >> continue from an error (and the handler is called >> without unwinding the stack) didn't make it into the C++ standard. >> Though Stroustrup (Design and Evolution of C++) reports >> that they had people from Texas Instruments with Lisp >> Machine experience explaining the Lisp Machine error handling. >> According to the TI guys (can't remember the exact wording, don't have >> the book handy), this feature was not used much. So it did >> not make it into C++.
> I refer to that book as Stroustrup's "Design Rationalization and > Mutation of C++". That's section 16.6, "Resumption vs. Termination". > At a meeting in 1991, several people who had had long experience with > languages with continuable exceptions (including Mary Fontana from TI > and Jim Mitchell from Sun/PARC) basically said "continuable exceptions > are not particularly useful except for debugging, and they allowed us > to write bad code."
> Personally, I *like* having the debugger available all the time, and > have never been persuaded by "this feature is dangerous when used by > amateurs" arguments (which were routinely used by the C++ designers to > limit C++).
That was the most stupid thing I've read this year! They actually said things like that?
If you should ban features that can be dangerous when used by amateurs, then they should ban C all together.
/andreas
-- A: Because it fouls the order in which people normally read text. Q: Why is top-posting such a bad thing? A: Top-posting. Q: What is the most annoying thing on usenet and in e-mail?
Den Tue, 27 Nov 2007 08:34:01 -0500 skrev Kent M Pitman:
> Just look at the similar difficulty we've had getting pretty printing > into the world in general. Dick Waters wrote papers back in the early > 80's showing that the techniques we use in CL are perfectly usable for > pretty printing code in block structured languages like Ada and PL/1. > Yet neither those languages nor their successors picked up on the option > to add such a facility to their programmatic repertoire, perhaps because > they were already too busy not picking up on the idea of having a > homoiconic language...
Pretty printing is still on my list of things I feel I should read about, but haven't had the time to yet.
>> Casual googling >> around doesn't suggest I missed any very prominent languages, either.
> You're looking for PL/1 for Honeywell Multics, and particularly the MIT > variant, which may not have been precisely what Honeywell had, I'm not > sure. See http://www.multicians.org/multics.html which may say.
> Several of the guys who designed the Lisp Machine had previously been > using that system, which amounted to a "PL/1 machine" (not in quite the > sense that a LispM was a "Lisp Machine", but certainly in the sense that > the operating system was written in a high level language, PL/1, and > there was a unifying execution model on the stack which was best > described by PL/1).
> The restart facility was not so linguistic as practical. I think it used > some sort of 'on signal' construct (my PL/1 was never advanced, though I > used it in school, and is quite rusty now, so at this point accept that > this is all blurry and was second-hand to start with).
Interesting. My idea about about PL/1 is vague at best, but from reading a little bit of multicians.org, it seems that it had considerable flexibility of expression for things that weren't accommodated for in the base language. Implementing support for restarts, which requires the ability to traverse the stack without unwinding, _in_ the language doesn't sound trivial. Just look at all the doomed attempts at adding exception handling to plain C.
> And remember, too, that the world was in a kind of proto-state where the > ideas were coming fast and furious ahead of platforms to implement them > on. So they had the abstractions, but they needed a place to deploy > those ideas in a new context. So they lept to the Lisp Machine, and > started to use that platform as a forum to evolve new ideas. [...] > Anyway, if you follow the people, not the languages, you get a better > sense of the flow of things. These people hopped from one > implementation and system to another, carrying ideas with them.
Right.
> Likewise you can see that UNWIND-PROTECT is present in things like ITS > TECO [as the ..N q-register, which was executed upon unwind], but again > the unifying characteristic is the community at MIT in which this arose. > I'm not sure who created ..N, maybe Steele, but certainly Steele was > present, and was present for UNWIND-PROTECT in CL, and later for > "finally" in Java.
TECO scares me senseless.
> Actually, what was strange and frustrating was that Steele was so > involved in Java and didn't put restarts into it, when he surely must > have known of them... and I think they mesh so nicely with > continuations, etc. that it's a shame.
Indeed. In general, I find the claim that Java was dragging people halfway to Lisp a stretch at the very least. It's true that it finally got the idea that GC and JIT can be fast into the mainstream, but otherwise it's been an exercise in how verbose, restricted, confused and obfuscated[1] you can make a language before you kill all benefits of having a GC.
> For myself, I used Lisp Machines, and liked them, but there were not > many of them and many people couldn't use them. I was amused and > saddened by how many of their ideas were regarded as "needing special > hardware", and I made it a personal mission to show that many LispM > ideas could be rehosted on stock hardware (i.e., off-the-shelf, or > non-special-purpose hardware) just fine. That was how my ZBABYL mail > reader came about for TECO-based Emacs, and it's why I took an interest > in getting the Zetalisp condition system into CL. (Early CL didn't even > have ERRSET or IGNORE-ERRORS, if memory serves me, so any program that > ever signaled ERROR under CLTL was dead in the water as far as > guarantees of portability. But the idea of adding just that one > operator as a way of "fixing" the problem was horrifying to me, so I > wanted as much of the LispM New Error System as I could get.)
I guess we should be thankful for your efforts then, the thought of having ERRSET for your error handling is indeed terrifying.
>> There's a considerable mental gap between having a system that supports >> non-local transfer control in the form of exceptions, and a system that >> includes a formalised protocol for recovering and restarting >> computation, as evidenced by the lack of languages to offer such a >> facility.
> Yeah, weird, huh? But I think the thing to understand is that people > judge the goodness of what they have in contrast to what they had > before. So most users of languages don't see themselves as impoverished > because they have previously used languages without try/catch and now > they have try/catch, so they've moved up. We've had our eyes opened, so > it's hard to go back. But convincing someone they are missing out is > hard.
That is true. Even sadder is the never ending tale of people who have literally no idea about Lisp, yet somehow managed to acquire the idea that the defining property is that it's interpreted and slow.
>> I'm still amazed by how woefully incomplete an exception system without >> structured means of recovery is, yet how I never felt that prior to >> learning CL.
> "A mind once stretched by a new idea never regains its original > dimension." -- Oliver Wendell Holmes
> And, incidentally, without a practice of defining the recovery points. > While we got restarts into CL, we were unable to get many specific > restarts in. And that's an aggravation. But again it was lack of > experience with the system, not to mention considerable political > disagreement over the nature of the inheritance model (I've spoken on > this in other forums, but ask me if the relevance of this in this > context is not clear and I'll expand), that interfered with getting some > specific conditions and many specific restarts added.
This is the post I was referring to above, though I have no idea where exactly I read it. But I remember you mentioned that multiple inheritance in particular was a big contention point, with vendors such as Gold Hill fighting vehemently not to have it in the standard, which prevented a large class of standard Zetalisp conditions from making it into the spec, such as FILE-ERRORs that are also NETWORK-ERRORs.
>> Therefore I'm really interested in learning the origins of the concept. >> I suspect the strong tradition of interactive development and built-in >> debugger naturally leads to the development of a complete error >> handling system,
> This is certainly relevant, though it's a feedback loop since what leads > to liking the debugger is the good facility.
> I think the homoiconicity reference above is also relevant, since the > usefulness of the debugger is also helped by the ability to use a > familiar langauge to operate in it. Visual Studio finally has ways of > doing this in clumsy ways with Watch and Immediate panes, but for years > this was really hard. And even now it's marginal and unpleasant by > comparison to Lisp in my opinion--though some might prefer it (at least > it's finally to the point where there can be a debate on the matter).
MSVS debugger was so laughably bad for years that I really wondered if they were making it deliberately unusable.
> I hope something in here is useful to your search.
Definitely. Thanks a lot for the most interesting material.
Cheers, Maciej
[1] I'm in large part referring to the years-long strategy of Sun to do _everything_ to convince everyone that Java-the-platform, Java-the- language and Java-the-implementation are one and the same thing, which earned it my eternal hate and disgust. In this regard, MSFT stepping in with .NET was a blessing for competition and progress.
Den Tue, 27 Nov 2007 08:26:34 -0800 skrev bob_bane:
> Personally, I *like* having the debugger available all the time, and > have never been persuaded by "this feature is dangerous when used by > amateurs" arguments (which were routinely used by the C++ designers to > limit C++).
Right, instead C++ chose to have features that are dangerous when used by anyone, not just amateurs.
Rainer Joswig <jos...@lisp.de> writes: > According to the TI guys (can't remember the exact wording, don't have > the book handy), this feature was not used much. So it did > not make it into C++.
I claim that you can benefit from features you don't use. I've put up a web page detailling my argument
The idea that a feature can be valuable beyond the number of times it is used is of interest beyond the confines of c.l.l. I wonder what redditors will make of it?
Do features you do not use confer benefits? =========================================== A baroque language ------------------ Common Lisp has some abstruse features that are rarely used. DEFINE-METHOD-COMBINATION allows the programmer to create his own method combinations. The condition system has RESTART-BIND in addition to RESTART-CASE.
This leads to the criticism that Common Lisp is a baroque language with too many features. Part of the logic of the critique is that programmers gain no benefit from the features that they do not use. This is too glib. Large programs need planning, which introduces subtle complications.
Why plan? --------- Why not plunge right in and get on with coding? At the heart of planning computer programs lies the idea of a contrast between some bits of code that are routine and other bits that are tricky. Coding the routine bits always goes smoothly. Coding the tricky bits sometimes ends in tears; the code cannot be written as originally designed and the work done on the tricky bit and on many routine parts of the program feeding in to it must be scrapped.
When we plan, we lightly sketch in the routine parts of the program, concentrating our efforts on anticipating the problems in the tricky bits. The benefit we obtain from planning is a reduction in the costs imposed upon us by the the discovery of show-stopping problems in the tricky bits. We lose the work we did implementing the tricky bit. That is unavoidable. We lose the work we did on the routine parts of the program feeding into the tricky bit, but we only sketched those parts, so the loss is greatly reduced compared to plunging into coding without looking ahead.
The effectiveness of planning ----------------------------- The benefit of planning flows from the routine parts of the code being mere sketches. The more that we can classify as routine, the more effective the the planning process will be at eliminating wasted effort.
When we plan we make two kinds of mistake.
1. Sometimes we think that a part of the program is tricky when it is actually routine. This causes us to spend effort filling in the details earlier than we should. If these details survive into the final program, the mistake costs us nothing. If this detailed worked is discarded later in the planning process we pay a price for the mistake. Had we seen that the work was routine we would only have sketched and only discarded a sketch.
2. Sometimes we think that a part of the program is routine when it is actually tricky. When we come to code it we may succeed, in which case our mistake has cost us nothing, or we may encounter a show-stopping problem and fail. Now we must discard much detailed work before trying again. This kind of mistake can be very expensive, emerging late in the project and impacting much detailed work
Calling a tricky part routine is potentially very expensive. This creates a painful tension. When we are conservative and call a routine part tricky, just to be on the safe side, we are reducing the effectiveness of the planning process. On the other hand, when we strain to say that a part of the program is routine, hoping to maximize the benefit from planning, we had better be right.
Language features and planning ------------------------------ Now that we are clear about how planning delivers its benefits we can look at how this interacts with the features present in or absent from our programming language.
For example, we may be called upon to decide whether a mildly ambitious plan, based around an unusual method combination is routine or tricky. We need to think ahead. A bug may show us that the method combination we had planned on is not in fact quite suitable. If the project is coded in a language with custom method combinations we may be able to anticipate that the code is routine. If problems arise, we can code our way out of trouble. If the project is coded in a language with a fixed set of method combinations we may call the code tricky and feel obliged to nail down the details early on.
The interest arises from the fact that planning needs to be fairly conservative. As we explained earlier the costs of the two kinds of error are different. We only call code routine if we have some notion of Plan B, which we have in reserve if things don't go smoothly. We are only justified in calling it Plan B if Plan A usually works. Sometimes Plan B is to use the advanced version of a language feature. In this case we gain an advantage, in the planning stage, from a feature that we don't actually get round to using.
That seems paradoxical. What are the ingredients that make this happen? There seem to be two.
1. The feature has to be never used in the ordinary sense that actually Fred used it a couple of months ago, but that doesn't count because it was a special case. If it is genuinely never used it can hardly be counted on as Plan B and doesn't help at the planning stage.
2. It seems most likely to happen with features that offer a more general version of a commonly used built-in feature or some way of brute forcing past rare problems.
For example, Common Lisp has macros PUSH, POP, INCF for modifying places. You can define your own with DEFINE-MODIFY-MACRO. The interest is that you can plan with confidence on the basis of using DEFINE-MODIFY-MACRO.
DEFINE-MODIFY-MACRO has limitations. As Graham explains on page 169 of ANSI Common Lisp neither PUSH nor POP can be defined as modify-macros. The reason that you can plan with confidence is that you have DEFINE-SETF-EXPANDER available as Plan B. You might never use DEFINE-SETF-EXPANDER, but you benefit from it anyway because you can plan on the basis that code that is going to use DEFINE-MODIFY-MACRO is routine and leave the details until other parts of your plan are firmed up.
Ghost benefits are real! ------------------------ Software projects require planning. Planning gains in effectiveness the more often you can declare "This bit of code is routine, we can fill in the details later." The right kind of advanced programming language feature helps with this, allowing you to plan with confidence. In this way programmers benefit from features that they don't use.
On 2007-11-27 16:35:44 -0500, Alan Crowe <a...@cawtech.freeserve.co.uk> said:
> Do features you do not use confer benefits?
Graham reached the same conclusion from a slightly different perspective:
"Why do we plan before implementing? The big danger in plunging right into a project is the possibility that we will paint ourselves into a corner. If we had a more flexible language, could this worry be lessened? We do, and it is. The flexibility of Lisp has spawned a whole new style of programming. In Lisp, you can do much of your planning as you write the program.
Why wait for hindsight? As Montaigne found, nothing clarifies your ideas like trying to write them down. Once you're freed from the worry that you'll paint yourself into a corner, you can take full advantage of this possibility."
> > According to the TI guys (can't remember the exact wording, don't have > > the book handy), this feature was not used much. So it did > > not make it into C++.
> I claim that you can benefit from features you don't > use. I've put up a web page detailling my argument
> The idea that a feature can be valuable beyond the number of > times it is used is of interest beyond the confines of > c.l.l. I wonder what redditors will make of it?
> Do features you do not use confer benefits? > =========================================== > A baroque language > ------------------ > Common Lisp has some abstruse features that are rarely > used. DEFINE-METHOD-COMBINATION allows the programmer to > create his own method combinations. The condition system has > RESTART-BIND in addition to RESTART-CASE.
Actually many of the features came from practical use. The condition system was in use already. The Lisp Machine OS was one application were much was used: conditions, objects, pathnames, streams as objects, ... you name it. Common Lisp was taking a lot from earlier experience. For much it was more a battle what to incorporate in the new language and what to leave out (from existing dialects and implementations). These companies had commercial interests, because a feature not in the new language would mean more porting effort for existing code to the new language. On the other hand a new feature in the language would mean work for the implementors. Users wanted some compatibility with a migration path. Implementors wanted to avoid the cost of new constructs. Especially those on 'stock' hardware/software tried to avoid the cost of 'performance hungry' new features or added 'bloat'. If the product was only running on a single type of platform, why have a pathname system that has classes for different pathname types (vms, ufs, hfs, FAT, lmfs, ...)? Why add more dynamic features (like conditions and streams as classes/objects/methods) when runtime dispatch was slow?
Maciej Katafiasz <mathr...@gmail.com> writes: > Den Tue, 27 Nov 2007 08:26:34 -0800 skrev bob_bane:
>> Personally, I *like* having the debugger available all the time, and >> have never been persuaded by "this feature is dangerous when used by >> amateurs" arguments (which were routinely used by the C++ designers to >> limit C++).
> Right, instead C++ chose to have features that are dangerous when used by > anyone, not just amateurs.
:-D
Take a laugh point, if you collect those.
/Andreas
-- A: Because it fouls the order in which people normally read text. Q: Why is top-posting such a bad thing? A: Top-posting. Q: What is the most annoying thing on usenet and in e-mail?
On Nov 27, 5:34 am, Kent M Pitman <pit...@nhplace.com> wrote:
> Bernie Greenberg was one of the most prominent Multicians, and > implemented Emacs for Multics in Lisp (no, not GNU Lisp, nor GNU > Emacs. This was long before those days. I think he used Multics > Maclisp.)
Yes, Multics Emacs was written in Multics Maclisp.
bob_bane wrote: > On Nov 27, 9:06 am, Rainer Joswig <jos...@lisp.de> wrote: >> One of the best examples was that the feature that you can >> continue from an error (and the handler is called >> without unwinding the stack) didn't make it into the C++ standard. >> Though Stroustrup (Design and Evolution of C++) reports >> that they had people from Texas Instruments with Lisp >> Machine experience explaining the Lisp Machine error handling. >> According to the TI guys (can't remember the exact wording, don't have >> the book handy), this feature was not used much. So it did >> not make it into C++.
> I refer to that book as Stroustrup's "Design Rationalization and > Mutation of C++". That's section 16.6, "Resumption vs. Termination". > At a meeting in 1991, several people who had had long experience with > languages with continuable exceptions (including Mary Fontana from TI > and Jim Mitchell from Sun/PARC) basically said "continuable exceptions > are not particularly useful except for debugging, and they allowed us > to write bad code."
In other words, why doesn't C++ provide a primitive for returning to the point from which an exception was thrown and continuing execution from there?
Basically, someone resuming from an exception handler can never be sure that the code after the point of throw was written to deal with the excecution just continuing as if nothing had happened. An exception handler cannot know how much context to "get right" before resuming. To get such code right, the writer of the throw and the writer of the catch need intimate knowledge of each others code and context. This creates a complicated mutual dependency that wherever it has been allowed has led to serious maintenance problems.
I seriously considered the possibility of allowing resumption when I designed the C++ exception handling mechanism and this issue was discussed in quite some detail during standardization. See the exception handling chapter of The Design and Evolution of C++.
If you want to check to see if you can fix a problem before throwing an exception, call a function that checks and then throws only if the problem cannot be dealt with locally. A new_handler is an example of this. </blockquote>
Agree with this point of view or not, I do respect the approach of having actually asked people with experience using languages which supported "resumption" to try and help inform the decision. Kent, if you have the time and/or energy, I would be interested in reading a response to some of the views expressed by Stroustrup in this quote. Of course, I should probably go back and read your paper(s) on the subject, at which I had only glanced in the past, before asking you to possibly repeat yourself. I have Peter Seibel to thank for introducing them to me in _Practical Common Lisp_ in such a way that I started to understand how they are different from C++/Java style exceptions. I do not really have any practical experience with Lisp conditions.
Damien Kick <dk...@earthlink.net> writes: > bob_bane wrote: > > On Nov 27, 9:06 am, Rainer Joswig <jos...@lisp.de> wrote: > >> One of the best examples was that the feature that you can > >> continue from an error (and the handler is called > >> without unwinding the stack) didn't make it into the C++ standard. > >> Though Stroustrup (Design and Evolution of C++) reports > >> that they had people from Texas Instruments with Lisp > >> Machine experience explaining the Lisp Machine error handling. > >> According to the TI guys (can't remember the exact wording, don't have > >> the book handy), this feature was not used much. So it did > >> not make it into C++. > > I refer to that book as Stroustrup's "Design Rationalization and > > Mutation of C++". That's section 16.6, "Resumption vs. Termination". > > At a meeting in 1991, several people who had had long experience with > > languages with continuable exceptions (including Mary Fontana from TI > > and Jim Mitchell from Sun/PARC) basically said "continuable exceptions > > are not particularly useful except for debugging, and they allowed us > > to write bad code."
> In other words, why doesn't C++ provide a primitive for returning to > the point from which an exception was thrown and continuing execution > from there?
> Basically, someone resuming from an exception handler can never be > sure that the code after the point of throw was written to deal with > the excecution just continuing as if nothing had happened. An > exception handler cannot know how much context to "get right" before > resuming. To get such code right, the writer of the throw and the > writer of the catch need intimate knowledge of each others code and > context. This creates a complicated mutual dependency that wherever it > has been allowed has led to serious maintenance problems.
> I seriously considered the possibility of allowing resumption when I > designed the C++ exception handling mechanism and this issue was > discussed in quite some detail during standardization. See the > exception handling chapter of The Design and Evolution of C++.
> If you want to check to see if you can fix a problem before throwing > an exception, call a function that checks and then throws only if the > problem cannot be dealt with locally. A new_handler is an example of > this. > </blockquote>
> Agree with this point of view or not, I do respect the approach of > having actually asked people with experience using languages which > supported "resumption" to try and help inform the decision. Kent, if > you have the time and/or energy, I would be interested in reading a > response to some of the views expressed by Stroustrup in this quote. > Of course, I should probably go back and read your paper(s) on the > subject, at which I had only glanced in the past, before asking you to > possibly repeat yourself. I have Peter Seibel to thank for > introducing them to me in _Practical Common Lisp_ in such a way that I > started to understand how they are different from C++/Java style > exceptions. I do not really have any practical experience with Lisp > conditions.
Well, I think he got bad advice or made some bad decisions, but anyway, for whatever reason, I just disagree with the choice he made. (Though I applaud his willingness to document his reasons so we can all discuss them. One reason I have a lot of thoughts on things is the number of decisions of my own and others that I've seen gone awry, so don't take anything I say to be a criticism of him. I just don't like the choice in this case.) But here are some somewhat hastily tapped out remarks, probably not as well thought through as what he wrote even. As usual, sorry for typos, etc. Ask me if I left something unclear.
If I write
{ f(x); g(x); }
in a traditional language, you could get all alarmist and insist that f must never return because there's no guarantee that g will do the right thing .. perhaps it doesn't expect f to return.
I think the great insight of the New Error System (on the Lisp Machine) was that there was nothing special about returning from an error that distinguishes it from any other kind of return.
Part of the documentation of a function is that it either is or is not expected to return. So error does not return and signal does, and people choose which they want to call based on their willingness to fall through. But certainly the creation of a restart point is a proof that there's a willingness to return, so I just don't see why that's an issue at all.
Another possible way of restating what Stroustrup is saying, and I don't mean to put words in his mouth--I'm just speculating trying to figure out what he meant--is that "people shouldn't program dynamically twisty combinations of function calls and closures". That would again be ridiculous, so I hope he's not saying that. If you look at the sample code for the condition system you'll be struck by how it's nothing more than a bunch of very straightforward function calls and macros. So the question is how that could be dangerous without, basically, all function calls and/or macros being dangerous. I just don't get it.
I have for a long time described restarts as being just continuations+reflection. They happen at a point in a program when the choice point is found but you don't know which continuation to invoke. But surely there's nothing wrong with invoking a continuation any more than returning or any more than invoking a delegate.
And it would work fine with strong typing, too, since under each protocol there is a specific set of arguments that things take.
What he may be referring to is that C++ made a mess of the unwind-protect operation, which you have to kludge up (as boost does) through destructors on stack-allocated objects. That may mean implicitly that people who were not maintaining decent stack discipline were rudely exposed by injecting restarts too easily into code that was not prepared for that. But I don't think it's fair to lay the blame on restarts for that, since the real blame if that's the case goes to the language for having created a non-modular case.
I'd liken it to the practice I've seen at some point in the past of vendors saying "we poll for interrupts, and only in certain specific and predictable places, so it's ok to not write without-interrupts if you're in what wants to be a critical section because you're implicitly in one just because the implementation promises". That's just broken, since the implementation should make you write the without-interrupts (or without-preemption or whatever) and then it should optimize cases where it knows that the rule allows it to be removed. Doing anything else means that if someone goes to change the compiler, all code will break. And yet, it's not fair to say "that means it's dangerous to poll in other places than were originally thought of". What's dangerous is, instead, to tell people they can rely on contingent truths [as per Kripke].
It is completely ridiculous to claim that any materially useful protocol can be built on his workaround suggestion: | If you want to check to see if you can fix a problem before | throwing an exception, call a function that checks and then | throws only if the problem cannot be dealt with locally. since the WHOLE POINT of the error system is not to add functionality but to add protocol. And protocol is not about calling your own functions, it's about calling those supplied by someone else, someone who didn't conspire with you in the design, but rather plugged into a framework on the promise there would be someone at the "other end". By not providing a way to register something that could do this check, and by not providing a way to search the registered checks, he has forced each user into doing precisely what each user is not empowered to do: to write their own condition system.
When I wrote the sample implementation [1] of Revision 18 of the CL condition system ages ago there was no part I couldn't write in terms of the actions I wanted to happen.. the only part I couldn't write was "making everyone call my functions". That part has to be given by the system. And if it is not, that's the end of the discussion.
There may also have been an issue for C++ that passing arguments over the control return was hard. That was before delegates, and it is such a pain in the neck to use parameterized functions that I can easily imagine someone not wanting to bother (though I assume Stroustrup could have managed it). And macro abstraction in C++ is a pain, which could have contributed, too. So maybe again he was really complaining about how darned cumbersome and inflexible C++ is syntactically. I'd agree with that if he wanted to say that.
C# could have, and should have, done better. (I'm not a big fan of static languages, but I do use them, and among that space of languages, I really like the design of C# as a local optimum in a design space I wish I didn't have to spend much time in). It's a bit disappointing that C# didn't get this right because it did make some good decisions and really thoughtful decisions on a number of other things. Maybe it's because the CLR has no support for it. Ditto with the JVM and Java. Alas.
But nothing that any of these languages couldn't just suddenly fix if they get a mind to.
Meanwhile it underscores my point about why I stubbornly stick to Lisp. It's not that I wouldn't use something that came along if it was just as good. It's that people seem so far intent on not making something that's just as good. I still find many useful things in Lisp, and particularly Common Lisp, that are not picked up elsewhere. When there's a legitimately better Lisp dialect, maybe I'll use that. When there's a better language,
Damien Kick wrote: > bob_bane wrote: >> On Nov 27, 9:06 am, Rainer Joswig <jos...@lisp.de> wrote: >>> One of the best examples was that the feature that you can >>> continue from an error (and the handler is called >>> without unwinding the stack) didn't make it into the C++ standard. >>> Though Stroustrup (Design and Evolution of C++) reports >>> that they had people from Texas Instruments with Lisp >>> Machine experience explaining the Lisp Machine error handling. >>> According to the TI guys (can't remember the exact wording, don't have >>> the book handy), this feature was not used much. So it did >>> not make it into C++.
>> I refer to that book as Stroustrup's "Design Rationalization and >> Mutation of C++". That's section 16.6, "Resumption vs. Termination". >> At a meeting in 1991, several people who had had long experience with >> languages with continuable exceptions (including Mary Fontana from TI >> and Jim Mitchell from Sun/PARC) basically said "continuable exceptions >> are not particularly useful except for debugging, and they allowed us >> to write bad code."
> In other words, why doesn't C++ provide a primitive for returning to the > point from which an exception was thrown and continuing execution from > there?
> Basically, someone resuming from an exception handler can never be sure > that the code after the point of throw was written to deal with the > excecution just continuing as if nothing had happened. An exception > handler cannot know how much context to "get right" before resuming. To > get such code right, the writer of the throw and the writer of the catch > need intimate knowledge of each others code and context. This creates a > complicated mutual dependency that wherever it has been allowed has led > to serious maintenance problems.
> I seriously considered the possibility of allowing resumption when I > designed the C++ exception handling mechanism and this issue was > discussed in quite some detail during standardization. See the exception > handling chapter of The Design and Evolution of C++.
> If you want to check to see if you can fix a problem before throwing an > exception, call a function that checks and then throws only if the > problem cannot be dealt with locally. A new_handler is an example of this. > </blockquote>
I think resumable exception make a lot less sense in a static language (where the goal is that the program is 'correct' and shouldn't require interactive 'fixing' at any point in time).
The problem is that whenever there is an exception, there are generally multiple ways to get out of the exceptional situation and resume. In a dynamic language, this is pretty straightforward: You present the different possibilities to the programmer / user and let them decide what to do. (This could, for example, be presented in user-friendly dialogs.)
In a static language, you would have to rely on automagically picking the 'correct' restart - and I guess that's where the mental model of the average static thinker doesn't cope anymore and they just bail out.
In article <5r9uetF130lm...@mid.individual.net>, Pascal Costanza <p...@p-cos.net> wrote:
> I think resumable exception make a lot less sense in a static language > (where the goal is that the program is 'correct' and shouldn't require > interactive 'fixing' at any point in time).
Many (most?) exceptions have little to do with program "correctness", they're generally related to dynamic input data. Is "file not found" a bug in the program?
The ones that are due to program bugs are hardware exceptions like bus error or segmentation violation, which are caused by improper use of pointers.
I suppose dynamically-typed langages do allow for some exceptions -- TYPE-ERROR, NO-APPLICABLE-METHOD, WRONG-NUMBER-OF-ARGUMENTS, etc. -- that would be prevented by compile-time type checking. But these aren't the most interesting uses of the condition system. -- Barry Margolin Arlington, MA
Barry Margolin wrote: > In article <5r9uetF130lm...@mid.individual.net>, > Pascal Costanza <p...@p-cos.net> wrote:
>> I think resumable exception make a lot less sense in a static language >> (where the goal is that the program is 'correct' and shouldn't require >> interactive 'fixing' at any point in time).
> Many (most?) exceptions have little to do with program "correctness", > they're generally related to dynamic input data. Is "file not found" a > bug in the program?
> The ones that are due to program bugs are hardware exceptions like bus > error or segmentation violation, which are caused by improper use of > pointers.
> I suppose dynamically-typed langages do allow for some exceptions -- > TYPE-ERROR, NO-APPLICABLE-METHOD, WRONG-NUMBER-OF-ARGUMENTS, etc. -- > that would be prevented by compile-time type checking. But these aren't > the most interesting uses of the condition system.
I don't know in general, but for example many of the Java exceptions don't sound as if they were related to dynamic input data.
Pascal Costanza wrote: > Barry Margolin wrote: >> In article <5r9uetF130lm...@mid.individual.net>, >> Pascal Costanza <p...@p-cos.net> wrote:
>>> I think resumable exception make a lot less sense in a static >>> language (where the goal is that the program is 'correct' and >>> shouldn't require interactive 'fixing' at any point in time).
>> Many (most?) exceptions have little to do with program "correctness", >> they're generally related to dynamic input data. Is "file not found" >> a bug in the program?
>> The ones that are due to program bugs are hardware exceptions like bus >> error or segmentation violation, which are caused by improper use of >> pointers.
>> I suppose dynamically-typed langages do allow for some exceptions -- >> TYPE-ERROR, NO-APPLICABLE-METHOD, WRONG-NUMBER-OF-ARGUMENTS, etc. -- >> that would be prevented by compile-time type checking. But these >> aren't the most interesting uses of the condition system.
> I don't know in general, but for example many of the Java exceptions > don't sound as if they were related to dynamic input data.
...which could of course be because of premature overengineering of the Java exception hierarchy, which is not one of the best ones anyway...
> Well, I think he got bad advice or made some bad decisions, but anyway, > for whatever reason, I just disagree with the choice he made. (Though I > applaud his willingness to document his reasons so we can all discuss > them. One reason I have a lot of thoughts on things is the number of > decisions of my own and others that I've seen gone awry, so don't take > anything I say to be a criticism of him. I just don't like the choice > in this case.) But here are some somewhat hastily tapped out remarks, > probably not as well thought through as what he wrote even. As usual, > sorry for typos, etc. Ask me if I left something unclear.
> If I write
> { f(x); g(x); }
> in a traditional language, you could get all alarmist and insist that f > must never return because there's no guarantee that g will do the right > thing .. perhaps it doesn't expect f to return.
> I think the great insight of the New Error System (on the Lisp Machine) > was that there was nothing special about returning from an error that > distinguishes it from any other kind of return.
> Part of the documentation of a function is that it either is or is not > expected to return. So error does not return and signal does, and > people choose which they want to call based on their willingness to fall > through. But certainly the creation of a restart point is a proof that > there's a willingness to return, so I just don't see why that's an issue > at all.
I think there is some fundamental confusion about what restarts are and aren't, and it's too easy to fall into the trap of thinking about them as some kind of magic, *without regard for the bundled protocol*. In fact, I did that very thing when I was going through the condition system interface and wrapping my head around all the implications. There are two things to understanding restarts properly:
1. Conditions and restarts are just functions + some syntactic sugar around them to aid with common cases.
2. There is an associated *protocol*. The language "support" for restarts is actually a tiny addition that allows expressing the protocol in a way that would be impossible to add in user code. Everything else is just a small matter of programming. In particular, the only "magic" primitive is SIGNAL; ERROR and friends are _not_.
The problems arise when you take restarts for more than they are and associate some magical properties with them (as I did). Consider this: the standard says a restart, as established by RESTART-BIND, can either transfer control non-locally, or return. Therefore, you could try to write the following:
(defun save-data () nil)
(defun can-error () (restart-bind ((ignore (lambda () nil) ; This should continue after ERROR and DO-MORE-STUFF :report-function (lambda (s) (write "Ignore error and continue anyway" :stream s)))) (unless (save-data) (error "Could not save data") (do-more-stuff))))
I was initially very confused when it invoked the debugger, and choosing IGNORE there only produced the message "Restart returned NIL". Doing away with that confusion takes two steps:
1. Understanding that signalling functions are not magic. The fact that (error 'foo) breaks into the debugger does not come from the condition, but from the fact that ERROR is essentially defined as[1]:
2. Because the debugger break is just a function call, and invoking a restart is also a function call, and the restart itself is no magic, but a function, all it can do is whatever a function can do. The only odd thing about restarts is that their dynamic environment is adjusted slightly at the time they are run. Therefore, if a restarts returns a value, what will happen is what always happens, ie. its caller will receive that value. And indeed, the debugger receives it. What *won't* happen is a magic return to the point where ERROR was called, because that's not a part of protocol specified by ERROR.
I believe Stroustrup's confusion stems from thinking along the above code's lines, which gives rise to the fear that once you introduce restarts, the callers will somehow be able to override each and every exception and force continuing anyway.
> C# could have, and should have, done better. (I'm not a big fan of > static languages, but I do use them, and among that space of languages, > I really like the design of C# as a local optimum in a design space I > wish I didn't have to spend much time in).
It's a very apt observation, and very much aligned with my own view on C#. It's "just a better Java", but as far as Javas go, it's a good one. Perhaps going beyond a certain point of improving upon Java was just too much unlike it, so it met with natural resistance of the language being designed to *be* Java.