Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)

H. S. Teoh hsteoh at quickfur.ath.cx
Tue Feb 21 08:56:25 PST 2012


On Tue, Feb 21, 2012 at 03:54:30PM +0100, Artur Skawina wrote:
> On 02/21/12 09:15, H. S. Teoh wrote:
> > 
> > Sorry for this super-long post, but I wanted to lay my ideas out in a
> > coherent fashion so that we can discuss its conceptual aspects without
> > getting lost with arguing about the details. I hope this is a step in
> > the right direction toward a better model of exception handling.
> 
> I haven't read all of that huge trolli^Hbrainstorming thread, as most
> of it was just bikeshedding - the exception hierarchy needs to just be
> done; design by committee never works.

It would have helped if you actually read all of it. :) I wasn't talking
about the exception hierarchy at all. I was talking about how to recover
from a given problem P without knowing where in the hierarchy P is, or
even whether there is a hierarchy. Specifically, how high-level code 20
levels up the call stack can do something meaningful with a low-level
error 20 levels down, without having to know anything about error codes,
the context of the error, the state of variables, that sort of thing.
And yet *still* have the low-level details sorted out once a decision is
made.


> You made some good points there, and had an interesting idea, so I'll
> just comment on that. 

Credit where credit is due. This idea is not mine, it's the model they
use in Common Lisp. I don't program in Lisp. I just read the article and
found the idea interesting and possibly very useful in the context of D.

The only thing I added, perhaps, is that instead of problem-specific
conditions, as they appear to have in Lisp, I'm looking at generic
categories of conditions, that you can handle from high-level code
without ever needing to know the specifics of exactly what the condition
is, and yet have the low-level code work it all out once you've made a
decision.


> > The try-catch mechanism is not adequate to implement all the
> > recovery actions described above. As I've said before when
> > discussing what I
> 
> Imagine that catch blocks are delegates. Now, when something throws an
> exception, the catch handler is looked up, just like right now, except
> the stack isn't unwound, the matching catch-delegate is called. If it
> returns 'X' control flow is returned to the 'throw' scope, so that the
> failed operation can be retried.  If it returns 'Y' then  the search
> for another matching catch block continues, that one is called and so
> on. If a delegate returns 'Z' the stack is unwound and control is
> passed to the code following this catch statement.

It's not always as simple as "return control to throw scope", the point
of not rolling back the stack is so that you can present a list of
resolution alternatives to the catch block, and have it select one, then
you proceed with that method of recovery.

These resolution alternatives need to be standardized, since otherwise
you pollute high-level code with low-level knowledge (suboperation Y023
20 levels down the call stack has failure mode F04 which has recovery
options R078, R099, R132, so I can catch F04, then choose between R089,
R099, R132), making it only able to deal with a single problem decided
beforehand by the coder. What you want is to be able to say things like,
given any transient problem, I don't care what exactly, retry 5 times
then give up. Or, given any component failure, I don't know nor care
which component, try an alternative component if one exists, otherwise
give up.

This way, if a new component is added to the low-level code, with brand
new ways of failure, the high-level code can still handle the new
failures, even though when it was written such problems didn't even
exist yet.


> Add a bit syntactic sugar, and the result could be something like
> this:
> 
> void f1(C c) {
>    retry:
>    if (!c.whatever())
>       throw(retry) new WhateverEx("Help! XYZ failed", moreDetails, errorCodesEtc);
> }
> 
> void f2(C c) {
>    try {
>       f1(c);
>    } catch (WhateverEx e) {
>       if (isTransient(e)) {
>          doSomethingAndPray();
>          continue;
>       }
>       if (nothingICanDo(e))
>          throw;
>       if (iCanDealWithItHere(e))
>          break;
>    }
>    /* ... */
> }
> 
> Wouldn't this be enough to handle all of your cases?

Not quite, but close. :) And this is a very nice syntax.


> If exiting the scope maps to 'break' ie means
> unwind-and-continue-from-here, it's even somewhat compatible with the
> current scheme (you cannot jump with goto to inside the catch blocks,
> but that's not a good idea anyway).
> 
> Implementation question: could this be done w/o causing heap
> allocation in most functions containing catch statements?
[...]

I've thought about using mixin templates to do insert labels to retry
blocks and goto's in catch blocks, so no cost is incurred unless an
error actually happens. Not sure if this is a good implementation
method, though.


T

-- 
BREAKFAST.COM halted...Cereal Port Not Responding. -- YHL


More information about the Digitalmars-d mailing list