Re: The origins of CL conditions system
- From: Maciej Katafiasz <mathrick@xxxxxxxxx>
- Date: Fri, 30 Nov 2007 12:42:16 +0000 (UTC)
Den Fri, 30 Nov 2007 02:48:56 -0500 skrev Kent M Pitman:
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))))
(defun try-and-continue ()
(handler-bind ((error #'(lambda (c)
(invoke-restart 'ignore))))
(can-error)))
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]:
(defun error (datum)
(signal datum)
(invoke-debugger datum))
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.
Cheers,
Maciej
[1] All the hair of what "datum" can be aside.
.
- References:
- The origins of CL conditions system
- From: Maciej Katafiasz
- Re: The origins of CL conditions system
- From: Kent M Pitman
- Re: The origins of CL conditions system
- From: Rainer Joswig
- Re: The origins of CL conditions system
- From: bob_bane
- Re: The origins of CL conditions system
- From: Damien Kick
- Re: The origins of CL conditions system
- From: Kent M Pitman
- The origins of CL conditions system
- Prev by Date: OT: "frgo" is not frog! WAS: Re: A simple debugging macro
- Next by Date: Compilation order when compiling and loading a file
- Previous by thread: Re: The origins of CL conditions system
- Next by thread: Re: The origins of CL conditions system
- Index(es):
Relevant Pages
|