February 21, 2012
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
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
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
> 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
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
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
February 21, 2012
On 2/21/12 10:50 AM, Juan Manuel Cabo wrote:
> 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.

Exactly. By and large, I think in the fire of the debate too many people in this thread have forgotten to apply a simple OO design principle: push policy up and implementation down. Any good primitive pushed up the exception hierarchy is a huge win, and any design that advocates reliance on concrete types is admitting defeat.

Andrei
February 21, 2012
Le 21/02/2012 17:56, H. S. Teoh a écrit :
> 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.
>

About this, I did some sketching, and I think LISP guys may have outsmarted you.

Let's consider a transiant condition. You can retry or give up and throw. But, if you want to retry or not often depend on what went wrong, no ?
February 21, 2012
On Tue, Feb 21, 2012 at 06:01:09PM +0100, deadalnix wrote:
> Le 21/02/2012 17:56, H. S. Teoh a écrit :
> >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.
> >
> 
> About this, I did some sketching, and I think LISP guys may have outsmarted you.
> 
> Let's consider a transiant condition. You can retry or give up and throw. But, if you want to retry or not often depend on what went wrong, no ?

True, and there's nothing to stop you from digging into the details of the raised Condition if you want to. I did consider implementing Conditions as some kind of class hierarchy, so that the generic categories are at the top, underneath Condition, then actual specific Conditions can extend them. If your handler knows of a specific Condition, then you can access any pertinent additional info, and make decisions based on that.

But what I wanted to know was, can you still do something meaningful even if you knew nothing beyond the top-level generic categories of Conditions? That way, your handler will still work with new Conditions that you've never seen before.


T

-- 
Never trust an operating system you don't have source for! -- Martin Schulze
February 21, 2012
I didn't know where I last read it, it got stuck in my head. I wrote:

> [...] doesn't mean
> that one must turn the advantages into disadvantages and start
> hammering screws because we love hammers.

I forget to be careful with metaphors, I realize that some must
be emotionally loaded and don't really help in debating since
they can be used either way and are astranged from reason.

(Hahaha, nothing more dangerous than landing on a new forum/community
without tip toeing, ohh the rush of excitement!!)

--jm


On Tuesday, 21 February 2012 at 16:49:37 UTC, Andrei Alexandrescu wrote:
> On 2/21/12 10:39 AM, foobar wrote:

...

> To quote a classic:
>
>> made generic. You seem to prove the old saying that when all you have
>> is a hammer everything looks like a nail.
>

...

>
> Andrei