View mode: basic / threaded / horizontal-split · Log in · Help
February 21, 2012
Re: The Right Approach to Exceptions
Never mind modifying fields of the exception at some intermediate catch place.
Someone could even catch the exception and not rethrow it.
So: do some trusting. Life gets easier :-)

--jm


On 02/21/2012 12:46 PM, Juan Manuel Cabo wrote:
>> I think he meant to say things have been like that for a while and there's no blood in the streets.
> 
> That's exactly what I meant.
>
February 21, 2012
Re: The Right Approach to Exceptions
On Tuesday, 21 February 2012 at 15:38:15 UTC, Juan Manuel Cabo 
wrote:
>> This works:
>> // note: the int parameter above isn't static
>> dbConn.query("select age from people where id='foobar'");
>> throw new WithErrorCode!FileNotFoundException(
>>           db.rs.getValue(1), "file not found");
> ...
>> Can you offer a real world use-case where the above isn't 
>> sufficient?
>
>
> What happened is that a file wasn't found. What one wants to 
> catch is
> a FileNotFoundException.
>
> Do you suggest that I have to:
>
>    try {
>        ...
>    } catch (FileNotFoundException ex) {
>        ...
>    } catch (WithErrorCode!FileNotFoundException ex) {
>        ...
>    } catch (WithRainbows!FileNotFoundException ex) {
>        ...
>    }
> and so on?
>
> --jm

FileNotFoundException is the super class of the others so the 
first catch clause is enough. in fact, the others will never be 
called if listed in the above order.
February 21, 2012
Re: The Right Approach to Exceptions
> FileNotFoundException is the super class of the others so the first catch clause is enough. in fact, the others will
> never be called if listed in the above order.

Nice! I missed that. But what if you want to add ErrorCode and Rainbows?
And with your approach, one has to test for type and downcast, or
otherwise have multiple catch blocks (I don't want to miss plain
FileNotFoundExceptions). So it's square one.

With Variant[string] (or something equivalent, nothing better comes to mind)
one does:


   try {
       ...
   } catch (FileNotFoundException ex) {
        if (ex.hasInfo(MyNameConstant)) {
            ... use that ...
        }
        ... common handling ...
   }


--jm
February 21, 2012
Re: The Right Approach to Exceptions
Also, you would lose the stacktrace by rethrowing with a different exception object.
(Currently, the stacktrace is lost by rethrowing the same object, but the Exception.file
and Exception.line are not lost, and it seems that it is very easy to not lose the
stacktrace when rethrowing, and it is the correct thing (for instance, java doesn't
lose the stacktrace when rethrowing, and C++ with its throw; statement for rethrowing
doesn't either).

--jm

On 02/21/2012 01:15 PM, Juan Manuel Cabo wrote:
>> FileNotFoundException is the super class of the others so the first catch clause is enough. in fact, the others will
>> never be called if listed in the above order.
> 
> Nice! I missed that. But what if you want to add ErrorCode and Rainbows?
> And with your approach, one has to test for type and downcast, or
> otherwise have multiple catch blocks (I don't want to miss plain
> FileNotFoundExceptions). So it's square one.
> 
> With Variant[string] (or something equivalent, nothing better comes to mind)
> one does:
> 
> 
>     try {
>         ...
>     } catch (FileNotFoundException ex) {
>          if (ex.hasInfo(MyNameConstant)) {
>              ... use that ...
>          }
>          ... common handling ...
>     }
> 
> 
> --jm
> 
>
February 21, 2012
Re: The Right Approach to Exceptions
On Tuesday, 21 February 2012 at 14:56:52 UTC, Andrei Alexandrescu 
wrote:
>>
>> Can you offer a real world use-case where the above isn't 
>> sufficient?
>
> This has been discussed. A function would want to add 
> contextual information to an exception and rethrow it. 
> Requiring a new type for each such flow does not scale.
>
>
> Andrei

This solution works given the fact that I know what type of data 
I want to add and I can't see a situation where that isn't the 
case. In Juan's case, he wanted to add an error code so he _knew_ 
already what type is required (int).
It seems we did a full circle. I'll ask again, what are you 
trying to optimize here? Number of instantiations?
February 21, 2012
Re: The Right Approach to Exceptions
On Tuesday, 21 February 2012 at 16:15:17 UTC, Juan Manuel Cabo 
wrote:
>> FileNotFoundException is the super class of the others so the 
>> first catch clause is enough. in fact, the others will
>> never be called if listed in the above order.
>
> Nice! I missed that. But what if you want to add ErrorCode and 
> Rainbows?
> And with your approach, one has to test for type and downcast, 
> or
> otherwise have multiple catch blocks (I don't want to miss plain
> FileNotFoundExceptions). So it's square one.
>
> With Variant[string] (or something equivalent, nothing better 
> comes to mind)
> one does:
>
>
>     try {
>         ...
>     } catch (FileNotFoundException ex) {
>          if (ex.hasInfo(MyNameConstant)) {
>              ... use that ...
>          }
>          ... common handling ...
>     }
>
>
> --jm

Regarding the downcast - you still perform a check in the code 
above! You gained nothing by replacing a type check with a check 
on a hash.

Regarding composition of several traits - even that simple 
snippet is enough:
throw new 
WithRainbows!withErrorCode!withFoobar!FileNotFoundException(...);

That's without further design which could probably improve this 
further.
February 21, 2012
Re: The Right Approach to Exceptions
On 2/21/12 10:39 AM, foobar wrote:
> Regarding the downcast - you still perform a check in the code above!
>  You gained nothing by replacing a type check with a check on a
> hash.

You do gain because capability checks don't force a tree structure, 
whereas downcasting does.

> Regarding composition of several traits - even that simple snippet is
>  enough: throw new
> WithRainbows!withErrorCode!withFoobar!FileNotFoundException(...);
>
> That's without further design which could probably improve this
> further.

To quote a classic:

> It's clear that you are trying to generify exceptions. This
> contradicts the very notion of what exceptions are. You also seem to
> try to optimize the amount of exception classes. Making user code
> convoluted for the sake of some premature optimization which most
> likely has negligible affect is completely unacceptable. I get that
> you are a templates master, that does *NOT* mean everything must be
> made generic. You seem to prove the old saying that when all you have
> is a hammer everything looks like a nail.

It's gotta be one or the other.


Andrei
February 21, 2012
Re: The Right Approach to Exceptions
> throw new WithRainbows!withErrorCode!withFoobar!FileNotFoundException(...);

So:

   catch (WithRainbows!withErrorCode!withFoobar!FileNotFoundException ex) {
        ....
   } catch (WithRainbows!withErrorCode!withFoobar!FileNotFoundException ex) {
        ....
   } catch (WithErrorCode!withRainbows!withFoobar!FileNotFoundException ex) {
        ....
   } catch (WithRainbows!withFoobar!withErrorCode!FileNotFoundException ex) {

and so on (in this case will be, its 3! == 6).

and you would have to write them all. You cannot catch only WithRainbows!* because
you miss the FileNotFoundException at the end.


Please, refer to my previous posts.
I don't want to start to repaste my posts.
In one of them, I said that what you care about for the catch selection
is the *what* of the error.   Not the *cause* of the error, not the *where*
of the error (no one catches by *where*). And that it seems wrong to encode
anything other than the *what* of the error in the type name. Other things
such as the cause or the date should be encoded inside the exception object
instead of in the exception class type name.

I thought that an alternative to Variant[string] would be to have some virtual
functions overrideable (getExceptionData(string dataName) or something).
but they would all have to return Object or Variant, so it's the same thing.

--jm


On 02/21/2012 01:39 PM, foobar wrote:
> On Tuesday, 21 February 2012 at 16:15:17 UTC, Juan Manuel Cabo wrote:
>>> FileNotFoundException is the super class of the others so the first catch clause is enough. in fact, the others will
>>> never be called if listed in the above order.
>>
>> Nice! I missed that. But what if you want to add ErrorCode and Rainbows?
>> And with your approach, one has to test for type and downcast, or
>> otherwise have multiple catch blocks (I don't want to miss plain
>> FileNotFoundExceptions). So it's square one.
>>
>> With Variant[string] (or something equivalent, nothing better comes to mind)
>> one does:
>>
>>
>>     try {
>>         ...
>>     } catch (FileNotFoundException ex) {
>>          if (ex.hasInfo(MyNameConstant)) {
>>              ... use that ...
>>          }
>>          ... common handling ...
>>     }
>>
>>
>> --jm
> 
> Regarding the downcast - you still perform a check in the code above! You gained nothing by replacing a type check with
> a check on a hash.
> 
> Regarding composition of several traits - even that simple snippet is enough:
> throw new WithRainbows!withErrorCode!withFoobar!FileNotFoundException(...);
> 
> That's without further design which could probably improve this further.
February 21, 2012
Re: The Right Approach to Exceptions
Walter Bright wrote:
> On 2/18/2012 3:13 PM, Andrei Alexandrescu wrote:
>> On 2/18/12 4:26 PM, Jonathan M Davis wrote (abridged):
>> GetOptException
>> FlagArgumentMissingException
>> InvalidFlagArgumentException
>> UnknownFlagException
>> FileException
>> FileNotFoundException
>> NotFileException
>> NotDirException
>> AccessDeniedException
>>
>> I died inside a little.
>
> I think typed exceptions are a good idea, but something looks wrong with
> these.
>
> (Also, having a large number of exception types is going to produce a
> lot of program size bloat. Remember, for EVERY class type, you've got
> the vtbl[], the .init data, and the TypeInfo. Going to town on exception
> types can really add this up.)

I feel that druntime might be optimized.

1) I think most of the bloat comes from .init data. For example this 
program:

class A
{
    ubyte[1024 * 1024 * 10] tenMegabytes;
}

class B : A {}
class C : B {}
class D : C {}

void main() { }

compiles to 40 MB exe under windows. Every class has copy of the same 10 
MB .init data.

I wonder why init is byte[] array. IMHO the better thing would be making 
it byte[][], i.e. array of byte arrays. Then some derived parts may be 
shared as byte array slices. This maybe little slower because it adds 
another level of indirection.

By the way, current byte[] array solution doesn't speed up void 
initializers, because it always overwrites void fields. This simple program:

import std.stdio;
class A { ubyte[8] test = void; }
void main() { writeln((new A()).test); }

always prints [0, 0, 0, 0, 0, 0, 0, 0].

With byte[][] array, some slices may have null ptrs so they may be 
initialized using memset() or not initialized at all (when using void 
initializer).

2) vtbl[] might also be shared for derivation chains that don't override 
any functions. But that may slow down program start, because vtables 
must be constructed at runtime.

Please correct me if I'm wrong :) I think that the use of classes should 
not be limited because of implementation issues. For me it's purely 
implementation issue, and I suppose it may be addressed with more 
optimized ABI or druntime.
February 21, 2012
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
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
38 39 40 41 42 43 44 45 46
Top | Discussion index | About this forum | D home