February 19, 2012
On Sun, Feb 19, 2012 at 04:36:29PM -0500, Patrick Down wrote: [...]
> 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.
[...]

It's not. This avoids the evil side-effect of resetting the stack trace when you rethrow an exception you didn't mean to catch inside the catch block. This is in fact just an alternative syntax for my "catch signature constraint" idea.

Another side issue, though: IMHO putting errno inside an exception is not a good thing. It introduces a strong coupling between the caller of write() and the low-level implementation of write(). This is bad because should we decide to change the underlying implementation of write(), it will cause a lot of breakage in all user code that catches errno-specific exceptions. This breaks encapsulation in an ugly way.

Instead, what we *should* do is to have Phobos define its own *logical* exception types, rather than implementation-dependent exception types. For example, conceptually speaking a write operation may fail because of the target device is full, or the target file already exists. The users of Phobos *shouldn't* care whether it's errno==EEXIST or it's a Windows-specific error number that describes this condition. Phobos needs to encapsulate this in a platform-independent way.

Of course, the implementation-specific info can still be there for system-dependent code that *wants* to check for system-specific statuses, for example, to check for a UNIX-specific error that doesn't exist on Windows, say. But this type of system-specific checking shouldn't be what Phobos users *generally* depend upon.


T

-- 
He who sacrifices functionality for ease of use, loses both and deserves neither. -- Slashdotter
February 19, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhrnn5$h1l$1@digitalmars.com...
> 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.
>

Right, and yet that's exactly what your suggestion of tying exceptions to modules would imply.

>> 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.
>

You're suggestion exacerbates the problem for no user benefit. This isn't the first time you've tried to push a negligably-time-saving scheme into Phobos at the expense of user code.

> 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.
>

But there *isn't* a 1:1 correspondence. A "file not found" is damn "file not found" no matter what lib you're using: phobos, tango, fooBarFileLib, what-the-hell-ever. You're "1:1" pretends that a non-existent file is somehow different from one lib to another.

>> 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.
>

Jesus christ. You pull out the pedantic "that's not a perfectly-stated argument" whacking stick at every opportunity, and yet you yourself put forth arguments like "I disagree"? What the fuck?

>> 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.
>

They're wanting? What's the problem with them? I see no problem, and I haven't seen you state any real problem.



February 19, 2012
On Sunday, February 19, 2012 14:57:08 Andrei Alexandrescu wrote:
> 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.

Yes and no. That's mostly true in something like the standard library, but it begins to blur even there. For instance, what if a function ends up doing some unicode related stuff internally (probably as an optimization for string or somesuch) and ends up needs an enforce for something related to that. What's it going to throw? It should throw a UTFException. But the function itself could be in a completely different module than std.utf. _Most_ of the stuff that deals with unicode in a manner that would require throwing a UTFExcepton is in std.utf, but that doesn't mean that it all is.

And if someone is writing code outside of Phobos, it could be that a Phobos exception is the best one to use - in fact with a really well designed exception hierarchy, that's often what's supposed to happen. Maybe they're doing something file-related and a FileException makes good sense. Then they should use that.

In none of these cases does the module have anything to do with the exception type. Because most modules in Phobos _are_ organized by functionality, the exceptions tend to be done on a per module basis, but that's not really where the focus should be, and in some cases, we've taken it too far, making exception types specifically for a module, because we're giving each module its own exception type rather than because that module happens to encompass a particular type of functionality that merits a specific exception type.

So, the focus needs to be on the type of exception, not where it came from. Having exception-handling code care about which module an exception came from would just be increasing coupling to no real benfit IMHO. It may be useful to the programmer, but not the program, and the stack trace should already have it.

- Jonathan M Davis
February 19, 2012
On Sun, Feb 19, 2012 at 08:35:08AM -0600, Andrei Alexandrescu wrote:
> On 2/19/12 6:35 AM, deadalnix wrote:
> >Le 19/02/2012 09:02, Andrei Alexandrescu a écrit :
> >>>>I'm thinking an error is transient if retrying the operation with the same exact data may succeed. That's a definition that's simple, useful, and easy to operate with.
> >>>[...]
> >>>
> >>>But if that's the case, what's the use of an exception at all?
> >>
> >>Centralization.
> >>
> >>Andrei
> >>
> >
> >Please stop answering like that. From the begining of this topic Jonathan M Davis, H. S. Teah (and myself ?) raised very valid points. What do you expect from that discussion if yourself you do not put any arguement on the table ?
> 
> The answer is meaningful. The purpose of exceptions is allowing for centralized error handling, and a capabilities-based system makes that simple (e.g. you get to make decisions about recoverability in one place, regardless of which part of the exception hierarchy the exception originated.
[...]

And how exactly do you propose centralized error handling to work in a GUI application, one of the many dialogs of which asks the user for a filename, and retries if the file doesn't exist? If the low-level filesystem code throws an exception, which could be anything from file not found to disk error to out of memory, then how exactly does your "centralized error handler" determine what course of action to take? How does it know which dialog to retry?

You're trying to have a centralized error handler that handles *all* exceptions regardless of where they come from and what triggered them, and independently of what operation might need to be retried if an exception does happen? I can't see how such a system could possibly be realized in a concrete way. This sounds like premature generalization to me.


T

-- 
Who told you to swim in Crocodile Lake without life insurance??
February 19, 2012
On Sunday, February 19, 2012 17:00:24 Nick Sabalausky wrote:
> "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.

While I think that Nick is getting a bit incensed and even a bit rude in some of his posts in this thread, I completely agree with his basic point here. In a well-designed exception hierarchy, the exception types are based on the types of things that went wrong. That way, you can catch them based on what went wrong, and they can provide information specific to that type of problem. As such, there is not necessarily any connection (let alone 1:1) betwen exception types and modules.

You could have multiple exception types per module or have several exceptions sharing a module. And if the exception hierarchy is done particularly well, then code which uses Phobos will also throw those exceptions when appropriate. For instance, in other languages, InvalidArgumentException is quite commonly used to indicate that a function was given bad arguments when an exception is thrown for that sort of thing - and code outside of the standard libraries uses it for that purpose all the time.

We have as much of a connection between modules and exceptions as we do, because Phobos is generally well-organized according to functionality, and different functionality tends to mean different exception types. But we've also taken it too far in some cases, creating an exception type in a module simply because most Phobos modules have their own exceptions.

Ultimately, it's what went wrong that matters, _not_ in which module it happened in. And if anything, we need to redesign what we have so that it better follows that, getting rid of some exceptions, merging others, and in some cases creating new ones. In many cases, you'll still have an exception per module, but in some cases you'll end up with multiple, and in others, you'll end up with none, because those modules are using exceptions from other modules.

- Jonathan M Davis
February 19, 2012
On Sunday, February 19, 2012 14:26:55 H. S. Teoh wrote:
> On Sun, Feb 19, 2012 at 08:35:08AM -0600, Andrei Alexandrescu wrote:
> > On 2/19/12 6:35 AM, deadalnix wrote:
> > >Le 19/02/2012 09:02, Andrei Alexandrescu a écrit :
> > >>>>I'm thinking an error is transient if retrying the operation with the same exact data may succeed. That's a definition that's simple, useful, and easy to operate with.
> > >>>
> > >>>[...]
> > >>>
> > >>>But if that's the case, what's the use of an exception at all?
> > >>
> > >>Centralization.
> > >>
> > >>Andrei
> > >
> > >Please stop answering like that. From the begining of this topic Jonathan M Davis, H. S. Teah (and myself ?) raised very valid points. What do you expect from that discussion if yourself you do not put any arguement on the table ?
> > 
> > The answer is meaningful. The purpose of exceptions is allowing for centralized error handling, and a capabilities-based system makes that simple (e.g. you get to make decisions about recoverability in one place, regardless of which part of the exception hierarchy the exception originated.
> 
> [...]
> 
> And how exactly do you propose centralized error handling to work in a GUI application, one of the many dialogs of which asks the user for a filename, and retries if the file doesn't exist? If the low-level filesystem code throws an exception, which could be anything from file not found to disk error to out of memory, then how exactly does your "centralized error handler" determine what course of action to take? How does it know which dialog to retry?
> 
> You're trying to have a centralized error handler that handles *all* exceptions regardless of where they come from and what triggered them, and independently of what operation might need to be retried if an exception does happen? I can't see how such a system could possibly be realized in a concrete way. This sounds like premature generalization to me.

I don't think that that's quite what he means. If you're using error codes in a function, you have to check them on practically every call to another function. This makes a mess of your code. If you use exceptions, on the other hand, you don't have any of those checks in your code. Rather, you wrap the code in a try-catch block, and put your error-handling code in one place within the function. _That_ is centralizing it.

In some cases, that means a try-catch block at a higher level without any in sub functions, because it makes the most sense for a function higher up to handle the exceptions there (e.g. if you're going to want to pop up a dialag to ask the user to give a different file, you want that higher up, not in std.file), and in some cases, you want the try-catch block to be much closer to where the throw occurred (sometimes even in the same function).

Now, maybe Andrei meant centralizing the error handling even more than that, but it makes so little sense to have _one_ place in your program handling all of the exceptions that I have a very hard time believing that that's what he really meant.

- Jonathan M Davis
February 19, 2012
On Sunday, February 19, 2012 16:07:27 Jacob Carlborg wrote:
> On 2012-02-19 10:26, Jonathan M Davis wrote:
> > On Sunday, February 19, 2012 19:00:20 Daniel Murphy wrote:
> >> I wasn't really serious about implicit fallthrough.
> > 
> > Lately, it seems like I can never tell whether anyone's being serious or not online. :)
> > 
> >> Out of the syntaxes I could come up with:
> >> catch(Ex1, Ex2 e)
> >> catch(e : Ex1, Ex2)
> >> catch(Ex1 | Ex2 e) // java 7 syntax, horrible
> >> 
> >> I like (e : list) the best.  Naturally it would also accept a type tuple
> >> of
> >> exceptions.
> >> 
> >> http://d.puremagic.com/issues/show_bug.cgi?id=7540
> > 
> > LOL. Personally, I actually think that the Java 7 syntax looks great (I'd
> > never seen it before), but catch(e : Ex1, Ex2) is just as good and more
> > consistent with the language as a whole, since it doesn't try to give any
> > operators a new meaning (as Java's does).
> > 
> > - Jonathan M Davis
> 
> How is "catch(e : Ex1, Ex2)" consistent with the language? It's completely backwards. catch-block are written as follows:
> 
> catch (Exception e) {}
> 
> Not
> 
> catch (e : Exception) {}

I meant the meaning of  the : operator vs the meaning of the | operator. : has to do with derived types already, whereas | is for bitwise operations. Doing something like

catch(Ex1, Ex2 : Ex0 e)

would be even more consistent though for the reasons that you point out.

- Jonathan m Davs
February 19, 2012
On Sunday, February 19, 2012 10:10:36 Andrei Alexandrescu wrote:
> On 2/19/12 3:15 AM, H. S. Teoh wrote:
> > I don't understand.  So instead of providing enough information to the caller about the nature of the problem, you're essentially handing them an anonymous note saying "A generic problem occurred, which _may_ go away if you retry. I have no further information for you. Do you want to retry?"?
> > 
> > But without further information, how *can* you even make that decision? Without any way of determining what caused the error or even what it is, how could you know whether it makes sense to retry it?
> > 
> > Or is that transient flag intended to mean that it *should* be retried since it "might" succeed the next time round? How should the caller decide whether or not to go ahead with the retry? Flip a coin? Always retry?  Always fail? I can't imagine any sane application where code would say "if this operation fails with a transient error, always fail" where any arbitrary set of exceptions might potentially be transient? What's a "transient error" anyway, from the application's POV anyway? What's a "transient error" from a database app's POV? A 3D shooter? A text editor? Is it even possible to have a consistent definition of "transient" that is meaningful across all applications?
> 
> I mentioned the definition a couple of times. Transient means retrying the operation with the same state may succeed. It is a property of the exception regardless of context. Of course, caller code may ignore it or use additional information.
> 
> Even network errors may be of both kinds. A network timeout error is transient. A DNS lookup error or malformed URL are not.
> 
> > It seems to me that if an error is "transient", then the called function might as well just always retry it in the first place, instead of throwing an exception.
> 
> Client code must decide on the appropriate action, including UI cues, a configurable number of retries, logging, and such.
> 
> > Such an exception is completely meaningless to
> > the caller without further information. Yet it seems that you are
> > suggesting that it's more meaningful than FileNotFoundException?
> 
> This is a misunderstanding. Transiency is a cross-cutting property. A FileNotFoundException is great (and non-transient btw).

Transient may be a useful addition for some situations, but I don't think that it can replace a well-designed exception hierarchy. In general, what you want to do is handle exceptions differently based on what went wrong, not whether simply executing the same function call again might work on the second try.

Also, in many cases, you don't care about trying again. Your code is simply reacting differently depending on what went wrong, and it's not going to retry regardless.

- Jonathan M Davis
February 19, 2012
Le 19/02/2012 17:48, Andrei Alexandrescu a écrit :
> On 2/19/12 10:40 AM, Jacob Carlborg wrote:
>> The command could be in the exception. Then you would have to look it up
>> against all valid commands. For the command to be in the exception you
>> would need some sort of hierarchy because, as others have said, a
>> command property wouldn't make sense for an exception like FileNotFound,
>> and wouldn't make sense in the base class Exception.
>
> Definitely, no argument there! To restate: all I want is to find ways to
> figure the "right" number of exception types, and the "right" primitives.
>
> Andrei

Well that must be decided on each case.

I think the guideline should be to create a new type when the cause of the exception is useful for the user when it come to handling it.

For exemple, on a remove function, you want to have FileNotFoundException specifically. Even if you do if(file_exists(file)) remove(file); a concurent delete can occur.

But if your goal is to delete the file, constating that the file is deleted may not be a major problem to you. However, if this operation fail because you don't have access to this file, it is a very different issue, because the file is still here and you still want to delete it.

The policy should be something along the line « if it make sense to handle this problem differently than other problems that can occur, then it deserve its own type »

I would add that, by thinking at your proposal of exception that may succed if you retry the same thing, phobos should propose a retry function that take as parameter a closure and and limit and will retry the operation until it succeed or that the limit is reached.

The more I think of it, the more it make sense to have a property on Exceptions to explicit if a retry may help.
February 19, 2012
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.
>
> Andrei
>

IMHO, I would not have made a GetOptException at all. It is too specific to the function. Since this is a type of parse error, I would prefer a ParseException base class. I think has a couple of advantages. One, the same exception can be used not only by the library itself but also by other users. Parsing is not an uncommon activity. Two, the exception hierarchy is orthogonal to the library. So changing the library functions---adding or removing them--- doesn't require changes in the exception hierarchy. Perhaps ParseException could then have a field that highlights the text that could not be parsed. This could be generally for all parsing type application.

In separating what should be a distinct type from what should be an attribute, I'd think you can use as a guideline whether the errors are likely to be handled in one place. I would guess that if you're going to try to analyze a parse error you would handle the possibilities of FlagArgumentMissingException, InvalidFlagArgumentException, and UnknownFlagException all in one place. You're not likely to catch one and let the others propagate upward. So those could be attribute you handle with a switch rather than separate types.


Jim