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

And here come an idea of mine :

If the Condition has a way to provide the Exception associated. So you can get the hierarchy from the Exception, and you don't need to creat two hierachy of classes just for the bloat.

You'll fond attached some sketching code. What do you think of it ?


February 21, 2012
I haven't really kept up reading this thread, but my only real caveat with Phobos exceptions is this:

std.file.FileException@std\file.d(453): : The system cannot find the
path specified.

This gives me no information whatsoever. I get a line number for an internal library method, and no information on which path I've passed. The still broken stack traces don't help either.
February 21, 2012
On 2/21/12, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
> std.file.FileException@std\file.d(453): : The system cannot find the
> path specified.

I see what's going on. It's an empty file string. I completely missed it! It would help if the string was delimited in the output, e.g.: std.file.FileException@std\file.d(453): ' ' : The system cannot find the
February 21, 2012
On 2012-02-21 17:57, Andrei Alexandrescu wrote:
> 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

That because you can't (shouldn't) push up implementations specific to a given subclass. Why don't we only have one class, Object, and add a Variant[string] there.

Do you see how stupid that is.

-- 
/Jacob Carlborg
February 21, 2012
On 02/21/12 17:56, H. S. Teoh wrote:
> On Tue, Feb 21, 2012 at 03:54:30PM +0100, Artur Skawina wrote:
>> On 02/21/12 09:15, H. S. Teoh wrote:
>>> 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.

I don't think something like this can reliably work - handling unknown
error conditions in code not expecting them is not a good idea. After all,
if the new error is of a similar nature as another one it could have been
mapped to that one, or handled internally. Note that with my scheme the
delegates can eg call another delegate provided in the exception from
the lower level code - so things like that are possible. It's just that
i don't think it's a good idea for low level code to use the exception
mechanism to ask "Should I retry this operation?". The code either knows
what to do (whether retrying makes sense) or could be provided with a
predefined policy. If retrying occurs often during normal operation then
throwing an exception every time is probably not the best way to handle
this. And if it's a rare event - this kind of error handling adds too much
overhead - programmer-wise, hence more bugs in the rarely executed parts
of the program and probably java-style catch-everything-just-to-silence-
-the-compiler situations, which are then never properly fixed...

artur
February 21, 2012
> That because you can't (shouldn't) push up implementations specific to a given subclass. Why don't we only have one class, Object, and add a Variant[string] there.
>
> Do you see how stupid that is.

As stupid as any database API which returns result items as Variant[string] or string[string], but it works. (the sad part is that one has to rely a bit on convention, but convention can be standardized (string constants) and measures taken when deviated so that it is done gracefuly).

Do you have an alternative solution that allows to extend an exception object with extra information, while keeping it the same class?

So if one removes the bad reasons to create new Exception types, then the ones that DO get created are solid, standard, reusable, and can withstand the test of time. Because they would be open for extension but closed for source code modification.

--jm


On 02/21/2012 03:03 PM, Jacob Carlborg wrote:
> On 2012-02-21 17:57, Andrei Alexandrescu wrote:
>> 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
> 
> That because you can't (shouldn't) push up implementations specific to a given subclass. Why don't we only have one class, Object, and add a Variant[string] there.
> 
> Do you see how stupid that is.
> 

February 21, 2012
Le 21/02/2012 03:33, Robert Jacques a écrit :
> On Mon, 20 Feb 2012 15:21:56 -0600, H. S. Teoh <hsteoh@quickfur.ath.cx>
> wrote:
>> On Mon, Feb 20, 2012 at 02:57:08PM -0600, Andrei Alexandrescu wrote:
>>> On 2/20/12 1:45 PM, Jonathan M Davis wrote:
>>> >On Monday, February 20, 2012 20:42:28 deadalnix wrote:
>>> >>Le 20/02/2012 20:27, Jonathan M Davis a écrit :
>>> >>>On Monday, February 20, 2012 11:15:08 H. S. Teoh wrote:
>>> >>>>That's why I proposed to use runtime reflection to scan the
>>> exception
>>> >>>>object for applicable fields. Then you get the best of both
>>> worlds: the
>>> >>>>message formatter doesn't need to know what the fields are, and
>>> you get
>>> >>>>full compile-time type checking for catching code that directly
>>> accesses
>>> >>>>the fields.
>>> >>>
>>> >>>That would certainly be better.
>>> >>>
>>> >>>- Jonathan M Davis
>>> >>
>>> >>This is way better than Variant[string], but unimplemented ATM.
>>> >
>>> >Yes, but you can use compile-time constructs to generate it. And as you
>>> >pointed out in another post, tupleof should do the trick.
>>> Regardless, the
>>> >point is that using reflection of some kind is a much better
>>> solution than
>>> >using variant.
>>>
>>> Agreed. Ideally adding a sort of mixin to any class would enable it
>>> for advanced run-time information:
>>>
>>> class DRoxException : Exception
>>> {
>>> mixin(enableRTTI);
>>> ... normal implementation ...
>>> }
>> [...]
>>
>> Doesn't this need compiler/language support?
>
> Nope. See (https://jshare.johnshopkins.edu/rjacque2/public_html/ )
>
> Variant e = new MyException();
> writeln( e.filename, e.line, e.column);
>
> Aren't __traits and opDispatch fun?

opDispatch is nice, but rather incomplete. It doesn't handle template methods for example.
February 21, 2012
On Tue, Feb 21, 2012 at 06:24:01PM +0100, deadalnix wrote:
> Le 21/02/2012 18:10, H. S. Teoh a écrit :
[...]
> >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.
[...]
> And here come an idea of mine :
> 
> If the Condition has a way to provide the Exception associated. So you can get the hierarchy from the Exception, and you don't need to creat two hierachy of classes just for the bloat.

You're right, that would be unnecessary duplication, especially since an unhandled Condition becomes a thrown Exception anyway, and it's a very bad idea to duplicate the entire Exception hierarchy in Condition.

Only thing is, then the handler will have to downcast the Exception to get to the useful info. This may lead to messy code. But it's something we should investigate.


> You'll fond attached some sketching code. What do you think of it ?

Is this the same code you attached in an earlier post? I did look over it, sorry, didn't have time to respond earlier. I like the idea of wrapping retryable code in the runTransient template, that way we minimize the amount of boilerplate needed to actually use this feature.

Oh wait, you've added some new stuff. Let's see...

Hmm, I like the idea of providing default handlers for some commonly-encountered situations. Reduces the amount of boilerplate.  And there's always the option of manually defining a handler if you need to. +1.


I was considering more last night how to implement this system. I think I change my mind about having a common Condition base class. The whole idea is that every major category would be defined by what kinds of actions are available, so they are quite distinct from each other. I don't want to reinvent another hierarchy to represent problems; we already have an Exception hierarchy for that. So the different Conditions need to be qualitatively different.

To maximize usability and minimize redundancy and bloat, I'm thinking we should define categories based on what recovery actions are *actually available*, rather than what actions are *potentially* available. So to that end, it's not really categorization per se, but more of a way of describing what recovery strategies are actually available.

In other words, an input error where you can recover by skipping the bad data is qualitatively different from an input error where skipping doesn't fix the problem. These two should be treated as distinct Conditions. Basically, I want to guarantee that for some Condition c, action A will *always* be available to the handler. Then the handler won't need excessive checking (if A1 is available but A2 is not, then use A1; else if A1 is not available but A2 is available, ... etc.). It can count on all options being actually present.

Back to your code. We can implement this idea by defining a template for each of the possible conditions. So code in runTransient always raises a transient condition if it fails, runReroutable always raises a reroutable condition if it fails (reroutable = failing component X can be replaced by component Y). I don't know if this is too inflexible, but trying to mix multiple conditions into a single operation seems to turn the code into spaghetti:

	int x, y, z;
	retry1:
		doSomething(x, y, z);
	retry2:
		if (problem1)
			raise(condition1);
		else if (problem2)
			raise(condition2);
		...
	handleCondition(condition1):
		if (recoveryAction1) {
			fiddleWith(x);
			goto retry1;
		} else if (recoveryAction2) {
			doSomethingElse(x,y,z);
			goto retry2;
		}
	handleCondition(condition2):
		if (recoveryAction3) {
			fiddleWith(y);
			goto retry1;
		} else if (recoveryAction4) {
			fiddleWith(z);
			doSomethingElse(x,y,z);
			goto retry2;
		}

So I don't think this is the right way to go. Each operation should have a single condition with a well-defined set of recovery methods, not some arbitrary combination of multiple conditions.

What do you think?


T

-- 
The day Microsoft makes something that doesn't suck is probably the day they start making vacuum cleaners... -- Slashdotter
February 21, 2012
On Tue, Feb 21, 2012 at 07:57:37PM +0100, deadalnix wrote:
> Le 21/02/2012 03:33, Robert Jacques a écrit :
[...]
> >Aren't __traits and opDispatch fun?
> 
> opDispatch is nice, but rather incomplete. It doesn't handle template methods for example.

Does RTTI handle template methods?


T

-- 
Answer: Because it breaks the logical sequence of discussion. / Question: Why is top posting bad?
February 21, 2012
On Tuesday, February 21, 2012 10:57:20 Andrei Alexandrescu wrote:
> 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.

Exceptions do _not_ lend themselves to polymorphism. Having them in a type hierarchy is useful. It allows you to deal with them at varying levels of abstractions. But ultimately, you deal with the concrete types, _not_ an abstract interface. In that sense, they're not OO _at all_.

Adding a Variant[string] property to allow adding on additional information if a particular application finds it useful may be a good thing to do. But it should be an _add on_, not the core design. Aside from printing strings, trying to deal with exceptions generically just does not make sense. At best, you might care about a common exception rather than a more specific one in particular case (e.g. catching IOException rather than FileException). But if you're trying to actually handle the exception in any real way rather than just print out a message, you need the concrete type, not an abstract interface.

I think that you're pushing the OO angle too hard onto exceptions. They're not completely separated from it, but they really aren't classic OO and shouldn't be treated as such. If anything, they're inverted, because you frequently try and deal with as concrete a type as possible rather than as abstract a type as possible. The hierarchy aspect is really the only truly OO aspect of exceptions IMHO. For the most part, polymorphism just doesn't enter into it. And Exception really already declares the few functions where it does.

- Jonathan M Davis