February 19, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhrave$2nas$1@digitalmars.com...
> On 2/19/12 4:30 AM, Juan Manuel Cabo wrote:
>> Now, expanding on the hierarchy: I said that it makes no sense to subclass UnrecoverableExceptions. Recoverable exceptions on the other hand, need to be subclassed _with_the_catch_on_your_mind_. You are passing info from the throw site to the catch site. The catch block is the interpreter of the info, the observer. You are communicating something to the catch block.
>>
>> So please, do not create a new types if there is no value in writing a catch that only cathes that exception and that can recover from that exception. Otherwise, use an existing type.
>
> I think this is a very sensible point. I wonder to what extent it clashes with a simple idea that just occurred to me. It would be very easy to define a hierarchy as follows:
>
> class ModuleException(string theModule) :
> PackageException(packageOf(theModule))
> {
>     ...
> }
>
> Then any module could issue
>
> throw new ModuleException!(.stringof);
>
> That way the exception hierarchy would parallel the module hierarchy. (A module is free to define more specific exception inheriting ModuleException!(.stringof)).
>
> The catcher would catch things like ModuleException!"acme.text.io" and such.
>
> In that approach, all exceptions thrown from Phobos would inherit PackageException!"std" and all exceptions thrown from std.getopt would be (or inherit) ModuleException!"std.getopt".
>
> This would save a lot on the boilerplate while still keeping open the option of defining fine-grained hierarchies. Any thoughts, please chime.
>

That wouldn't be as useful. What the catcher is typically interested in is *what* happened, not *where* it happened.

For example, if I want to do something upon a network error, minimum 99 times out of 100 I don't give a shit if it came from libNetworkFoo or libNetworkBar, and I don't *want* to care. What I care is whether or not there was a "network" error and possibly what *conceptual* type of network error.

Furthurmore, what if I change some implementation detail to use a different module? Then I have to go changing all my catch blocks even though it's conceptually the same fucking error handled the same way.

However, I wouldn't object to the idea of an "originatingModule" member being added to Exception that's automatically filled by the runtime (perhaps lazily). Although really, I think what would be more useful that that would be "Does xxx module/package exist in the portion of the callstack that's been unwound?"

As far as "when to add or not add an exception class", it's perfectly reasonable to err on the side of too many: If there's an unnecessary class, you can just ignore it. Problem solved. If there's a missing exception class, you're shit out of luck. Case closed.

I can't shake the feeling that we're desperately trying to reinvent the wheel here. The round wheel is solid technology with a proven track record, we don't need to waste time evaluating all these square and oval wheels just for the fuck of it.


February 19, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhr67g$2dup$4@digitalmars.com...
>
> The Java7 syntax looks meaningful to me, too - you want to catch the union type. A possibility that wouldn't change the language for us would be to catch Algebraic!(Ex1, Ex2).
>

I'd argue that a templated catch block would be better so you wouldn't have to query what type it is in order to use it:

// With a little compler magic:
catch(TypeTuple!(Ex1, Ex2) e)
{
    writeln(e.msg); // Look ma, no querying!
}

// Mimicking D1-style template contraints:
catch(E : Ex1, Ex2)(E e)
{
    writeln(e.msg);
}



February 19, 2012
On Sun, 19 Feb 2012 03:04:35 +0100, Alf P. Steinbach <alf.p.steinbach+usenet@gmail.com> wrote:

> However, C++ exceptions do have many problems, including:
>
>    * Until C++11, no support for propagating exceptions through
>      non-exception-aware code (in particular, from C callbacks).
>
>    * No support for wchar_t exception text, i.e. *nix-specific.
>
>    * A nonsensical exception class hierarchy with e.g.
>      std::logic_error and std::bad_exception.
>
>    * No differentiation between recoverable (soft, failure) and
>      unrecoverable (hard, error) exceptions, although some people have
>      argued that in the latter case one is screwed anyway.
>
>    * Involving std::string arguments, so that in low memory
>      conditions throwing an exception can itself throw.
>


Two even worse problems:

a) you see

  foo();

and have no idea whether and what it may throw. No, the documentation is never so detailed and I woudn't trust it anyway.


b) you see

try {
  foo();
} catch (some_exc& e) {
  handle(e);
}

and have no easy way to check whether the 'some_exc' can be really thrown from inside foo(). Maybe not and they forgot to remove the catch.


c) you see

try {
  foo();
} catch (some_exc& e) {
  handle(e);
}

and there's no easy way to spot that the 'some_exc' is already fully handled inside and thus is useless here.



The Java way is overkill but complete ignorance by the compiler isn't good either.

/Pavel
February 19, 2012
On 2/19/2012 3:40 AM, deadalnix wrote:
>
> Well, I think you are messing up between contract and exception. Wrong
> parameters is a contract problem, not an exceptionnal situation.
>


Here is an example of what I'm getting at. Let's say you have a Dictionary class. This class has a function called Get(key) which will retrieve an item from the dictionary. But the item may not be found in the dictionary. Should you throw an exception if the item isn't found. Well, the “exceptional situations” crowed will say that since it's common for an item to not be found, this isn't an exceptional situation and you should therefore not throw an exception. An item not being in the dictionary is part of the normal program flow and you don't want exceptions firing off during normal program flow. But then what? You return false or a NotFound error code? But then do I need to check for this error every time I extract an item from the dictionary? You're losing the benefit that exceptions bring.

Here is the way I'm thinking exceptions are related to contracts. I think you need to better define what the function is expected to do and not do in order to know when an exception is thrown. I can define Get(key) in two ways. I can declare that the item associated with 'key' must exist in the dictionary. Therefore if the item is not in the dictionary then the contract of the caller is not fulfilled, it's an error, and an exception is thrown. But how is the caller supposed to know an item is not in the dictionary before calling Get(key) and thus avoid exceptions? One solution is to provide a second function called ItemExists(key). You can call this before calling Get(key). ItemExists(key) is then defined to return true of false depending on whether the item is in the dictionary. It doesn't throw an exception if the key doesn't exist because it's main job is to determine if the key exists or not.

Obviously this causes bad performance because the item must be looked up twice each time you retrieve it. So the second way to deal with the problem is to define the contract of Get(key) differently such that the argument 'key' is not required to exist in the dictionary. To distinguish this function, call it TryGet(key). This will return false if the item is not in the dictionary. (But it may still throw an exception for other errors such as a null argument or whatever.)

So I think whether an exception is thrown depends on the function's contract. It does not have to do with errors being “exceptional” or not. Perhaps I should not even be using the term contract at all and should just say that functions need to be clearly defined. I don't know. I'm always open to learning more about exceptions to create better designs, which is partly why I jumped on this topic (and now so selfishly pulled it off topic). If anyone thinks I'm totally off base then tell me why and I might learn something.

By the way, the example I was using actually exists in the .NET Framework and uses both approaches. Look at the System.Collections.Generic.Dictionary class. Instead of Get, ItemExists and TryGet, the functions are named respectively:

Item          (bracket syntax this[])
ContainsKey
TryGetValue

(see http://msdn.microsoft.com/en-us/library/s620ab8x.aspx)

The KeyNotFoundException is only thrown by the Item property. So, I just put this out there as an example of when a seemingly “non-exceptional” case can use exceptions depending on the contract of the function.

Jim
February 19, 2012
On 2/19/12 11:48 AM, Timon Gehr wrote:
> On 02/19/2012 05:00 PM, Andrei Alexandrescu wrote:
>> On 2/19/12 3:17 AM, Jonathan M Davis wrote:
>>> "As much information as possible" is way more than a transient
>>> property. If my
>>> code is going to retry or do something else or give up, it needs enough
>>> information to know what went wrong, not just whether the function
>>> which was
>>> called think it might work on a second try.
>>>
>>> Having an exception hierarchy provides some of that information simply
>>> with
>>> the types, and makes it easier to organize code based on what went
>>> wrong (e.g.
>>> separate catch blocks for each type of exception). And having that
>>> hierarchy
>>> also means that the derived types can have additional information
>>> beyond their
>>> type which could be useful but is specific to that problem and so
>>> wouldn't make
>>> sense on a general exception type.
>>>
>>> I really don't see what transient buys you in comparison to that.
>>
>> A notion of transiency planted fundamentally in all exceptions allows
>> one to act on it regardless of origin and hierarchy.
>>
>> Andrei
>>
>
> Transiency is a powerful concept at the handler side, but the interface
> it is difficult to fulfil at the point where the actual error occurs.
> What is important is probably not whether or not transiency is useful if
> it is there, but more whether or not a sufficient part of the useful
> exceptions are naturally transient. This is what I doubt. OTOH, as I
> understand it, introducing the concept would require additional
> boilerplate in most exception handlers.

I'm thinking of simply adding

@property transient() const pure { return false; }

to the base exception class. Most exceptions are not transient.


Andrei
February 19, 2012
On 2/19/12 12:48 PM, address_is@invalid.invalid wrote:
> I guess "transient" is more descriptive.
>
> Andrei

That was me indeed, just from my phone.

Andrei
February 19, 2012
On 2/19/12 12:49 PM, Nick Sabalausky wrote:
> No, exceptions need to be based around semantics, not modules.

Packages and modules are also organized around specific areas.

Andrei
February 19, 2012
On 2/19/12 1:19 PM, Nick Sabalausky wrote:
> That wouldn't be as useful. What the catcher is typically interested in is
> *what* happened, not *where* it happened.

But module organization is partitioned by functional areas.

> For example, if I want to do something upon a network error, minimum 99
> times out of 100 I don't give a shit if it came from libNetworkFoo or
> libNetworkBar, and I don't *want* to care. What I care is whether or not
> there was a "network" error and possibly what *conceptual* type of network
> error.

Then it wouldn't help if each defined its own hierarchy.

The way I see it is, a "well-designed" package and module hierarchy would naturally engender a "well-designed" exception hierarchy. This is because packages and modules are organized on functional areas, so e.g. there is an "std.net" package that has its own exception types etc. There would be some special cases indeed (e.g. a module initiating an exception defined in another), so it's good those are possible too. I want to automate the common case.

> Furthurmore, what if I change some implementation detail to use a different
> module? Then I have to go changing all my catch blocks even though it's
> conceptually the same fucking error handled the same way.

That is an issue regardless. Occasional exception translation is a fact of life.

> However, I wouldn't object to the idea of an "originatingModule" member
> being added to Exception that's automatically filled by the runtime (perhaps
> lazily). Although really, I think what would be more useful that that would
> be "Does xxx module/package exist in the portion of the callstack that's
> been unwound?"

That's why PackageException!"tango.io" inherits PackageException!"tango". That's all automatic. Essentially there's 1:1 correspondence between package/module hierarchy and exception hierarchy.

> As far as "when to add or not add an exception class", it's perfectly
> reasonable to err on the side of too many: If there's an unnecessary class,
> you can just ignore it. Problem solved. If there's a missing exception
> class, you're shit out of luck. Case closed.

I disagree that having too many exception types comes at no cost.

> I can't shake the feeling that we're desperately trying to reinvent the
> wheel here. The round wheel is solid technology with a proven track record,
> we don't need to waste time evaluating all these square and oval wheels just
> for the fuck of it.

The wheel is not round. We just got used to thinking it is. Exceptions are wanting and it's possible and desirable to improve them.


Andrei
February 19, 2012
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.
> 
> Andrei
> 

As a lurker watching this debate with interest I just had an idea out of the blue idea that may or may not have value but might reduce the desire to create a new exception type Exception type for every error.

What if the catch syntax was extended to take both a type and a condition.

try {
  write(filename, ...);
}
catch(FileException e; e.errno == EEXIST) {
}
catch(FileException e; e.errno == EROFS) {
}

I realize that this is just syntactic sugar for catch(...) { if(...) {}} feel free to shoot it down.


February 19, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhrn3r$fie$3@digitalmars.com...
> On 2/19/12 12:49 PM, Nick Sabalausky wrote:
>> No, exceptions need to be based around semantics, not modules.
>
> Packages and modules are also organized around specific areas.
>

And those specific areas do NOT necessarily correspond 1-to-1 to error conditions.

1. A module can still reasonably generate more than one conceptual type of exception. Ex: std.file can be expected to generate both "file not found" and "disk full" conditions (those may both be IO exceptions, but they're still separate issues that are *not* always to be dealt with the same way). You can argue till your keyboard wears out that you don't see the usefulness, but the fact is, many, many, many people DO find it useful.

2. As I *already* explained in further detail, it's perfectly reasonable to expect one specific area to be convered by more than module.

Therefore, "Modules" to "Conceptual types of exceptions" are not 1-to-1. They're not even 1-to-many or many-to-1. They're many-to-many. Therefore, module is the wrong basis for an exception.

Seriously, how is this not *already* crystal-clear? I feel as if every few weeks you're just coming up with deliberately random shit to argue so the rest of us have to waste our time spelling out the obvious in insanely pedantic detail.