www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - The Right Approach to Exceptions

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
There's a discussion that started in a pull request:

https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

Let's come up with a good doctrine for exception defining and handling 
in Phobos. From experience I humbly submit that catching by type is most 
of the time useless.


Andrei
Feb 18 2012
next sibling parent reply "Martin Nowak" <dawg dawgfoto.de> writes:
On Sat, 18 Feb 2012 19:52:05 +0100, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

 Let's come up with a good doctrine for exception defining and handling  
 in Phobos. From experience I humbly submit that catching by type is most  
 of the time useless.


 Andrei
Exception are useful for handling unknown errors and probably to bundle error handling at a bigger scope. You don't want to use specific exceptions because it couples unrelated code. The distinction of recoverable Exceptions and non-recoverable Errors is good enough in most cases. Typed exception being used for local error recovery is about the same as using error codes but at a bigger expense. On the plus side exception can carry more specific error messages, which could be solved for error codes too.
Feb 18 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/02/2012 20:12, Martin Nowak a crit :
 Typed exception being used for local error recovery is about the same as
 using
 error codes but at a bigger expense. On the plus side exception can
 carry more
 specific error messages, which could be solved for error codes too.
The problem with error code is that you have to handle all problems, or none. A class hierarchy of exceptions allow you to handle some type of errors, and not some others. At this point, I don't think we should think in terms of expansive or not. Exception, as it is named, are for exceptionnal cases. If not, you are probably using them the wrong way. And if it is exceptionnal, then it seems reasoanble to sacrifice some CPU cycles to handle things properly.
Feb 18 2012
parent "Martin Nowak" <dawg dawgfoto.de> writes:
On Sat, 18 Feb 2012 20:24:16 +0100, deadalnix <deadalnix gmail.com> wrot=
e:

 Le 18/02/2012 20:12, Martin Nowak a =C3=A9crit :
 Typed exception being used for local error recovery is about the same=
as
 using
 error codes but at a bigger expense. On the plus side exception can
 carry more
 specific error messages, which could be solved for error codes too.
The problem with error code is that you have to handle all problems, o=
r =
 none. A class hierarchy of exceptions allow you to handle some type of=
=
 errors, and not some others.

 At this point, I don't think we should think in terms of expansive or =
=
 not. Exception, as it is named, are for exceptionnal cases. If not, yo=
u =
 are probably using them the wrong way. And if it is exceptionnal, then=
=
 it seems reasoanble to sacrifice some CPU cycles to handle things  =
 properly.
std.file.remove is a good example of how exceptions are used wrong. It does not allow me to recover from an error, i.e. by changing the file= = attributes. Using a cenforce like translator is a much better approach IMO.
Feb 18 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 08:12:43PM +0100, Martin Nowak wrote:
[...]
 Exception are useful for handling unknown errors and probably to
 bundle error handling at a bigger scope.
But the way exceptions are used in D, they are more like errors than exceptions. The approach in principle is on the right track, because you want to be able to write: Result_t complicatedFunc(T args) { doComplexProcessing(args[0]); doMoreComplexProcessing(args[1]); doYetMoreComplexProcessing(args[2]); ... return result; } instead of: Result_t complicatedFunc(T args) { if (doComplexProcessing(args[0]) != OK) { return Result_t.FAILURE_A; } if (doMoreComplexProcessing(args[1]) != OK) { return Result_t.FAILURE_B; } if (doYetMoreComplexProcessing(args[2]) != OK) { return Result_t.FAILURE_C; } ... return result; } The assumption is that *usually* the computation should succeed, but exceptional conditions do occur, and sometimes you need to distinguish between them. For example: void doComputation() { try { result = complicatedFunc(...); } catch(FailureA e) { /* recover from FailureA */ } /* FailureB and FailureC considered fatal */ }
 You don't want to use specific exceptions because it couples unrelated
 code.  The distinction of recoverable Exceptions and non-recoverable
 Errors is good enough in most cases.
Not true, especially in library code. If you try to open a file, FileNotFoundException should be recoverable, but DiskFailureException should not. You need to distinguish between them.
 Typed exception being used for local error recovery is about the same
 as using error codes but at a bigger expense. On the plus side
 exception can carry more specific error messages, which could be
 solved for error codes too.
The assumption is that exceptional conditions are, well, exceptional, so it shouldn't matter if you require extra CPU/memory to handle them. The worst thing is if we enforce generic exceptions instead of specific exceptions, and then code that *needs* to distinguish between different types of errors must pattern-match the error message to tell which one it is. That is unmaintainable, full of opportunities for bugs and i18n problems, and is absolutely NOT the way we want to go. T -- The day Microsoft makes something that doesn't suck is probably the day they start making vacuum cleaners... -- Slashdotter
Feb 18 2012
parent "Martin Nowak" <dawg dawgfoto.de> writes:
 You don't want to use specific exceptions because it couples unrelated
 code.  The distinction of recoverable Exceptions and non-recoverable
 Errors is good enough in most cases.
Not true, especially in library code. If you try to open a file, FileNotFoundException should be recoverable, but DiskFailureException should not. You need to distinguish between them.
That's what I said. Exception and Error.
 Typed exception being used for local error recovery is about the same
 as using error codes but at a bigger expense. On the plus side
 exception can carry more specific error messages, which could be
 solved for error codes too.
The assumption is that exceptional conditions are, well, exceptional, so it shouldn't matter if you require extra CPU/memory to handle them. The worst thing is if we enforce generic exceptions instead of specific exceptions, and then code that *needs* to distinguish between different types of errors must pattern-match the error message to tell which one it is. That is unmaintainable, full of opportunities for bugs and i18n problems, and is absolutely NOT the way we want to go.
If you can recover from errors you know beforehand what kind of errors may occur. That remove may fail if you haven't checked the path is clear. What I'm saying is that error returns are more flexible for interfaces that have know error cases. You can then either directly handle them or you may translate them to exceptions using enforce. Always using exceptions forces a certain programming model on you. uint toUint(int value) { if (value < 0) throw new ConvUnderflowException("Negative Value"); return cast(uint)value; } void main(string[] args) { int ival = parse!int(args[1]); uint uval; try { uval = toUint(ival); } catch (ConvUnderflowException) { uval = 0; } }
Feb 18 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/02/2012 19:52, Andrei Alexandrescu a crit :
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca


 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is most
 of the time useless.


 Andrei
I think your oppinion here is shaped by C++. For what I experienced, in C++, exception are only usefull for very important problem you cannot possibly solve, and at the best log the error and exit. An exemple is std::bad_alloc . However, in D, I think this is more the role of an Errors. Exception are something softer . It will alert you on problems your program encounter, but that are recoverable. You cannot recover from any exception at any place (sometime, you just cannot at all). Let's get an exemple : your program ask a file to the user and do some operations with this file. If the file doesn't exists, you can prompt for another file to the user with a meaningful message and start again. However, the first version of your program can just ignore that case and fail with a less specific handler in firsts versions. You cannot achieve something like that if you don't have a useful type to rely on. Here something like FileNotFoundException is what you want. The type of the exception must depend on the problem you are facing, not on the module that trhow it. I see a lot of people doing the MyProgramException or MyLibException but that doesn't make sense. In this case, you are just making things harder. Back on the original subject, GetOptException is not what you want. As getopt is supposed to handle command line parameters, you'll use exception like : MissingParameterException, WrongFormatException, UnknowParameterException or anything that is meaningful. Those Exceptions would inherit from something like CommandLineException. This is useful because it describe what you are facing. Because you cant to know what is the problem, not which piece of code face the problem. If this politic is choosen, then It would make sense to have several modules of phobos throwing exceptions of the same type, or inheriting from the same base class. Exception type is a convenient way to filter what you catch and what you don't know how to handle at this point.
Feb 18 2012
next sibling parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 18-02-2012 20:20, deadalnix wrote:
 Le 18/02/2012 19:52, Andrei Alexandrescu a crit :
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca



 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is most
 of the time useless.


 Andrei
I think your oppinion here is shaped by C++. For what I experienced, in C++, exception are only usefull for very important problem you cannot possibly solve, and at the best log the error and exit. An exemple is std::bad_alloc . However, in D, I think this is more the role of an Errors. Exception are something softer . It will alert you on problems your program encounter, but that are recoverable. You cannot recover from any exception at any place (sometime, you just cannot at all). Let's get an exemple : your program ask a file to the user and do some operations with this file. If the file doesn't exists, you can prompt for another file to the user with a meaningful message and start again. However, the first version of your program can just ignore that case and fail with a less specific handler in firsts versions. You cannot achieve something like that if you don't have a useful type to rely on. Here something like FileNotFoundException is what you want. The type of the exception must depend on the problem you are facing, not on the module that trhow it. I see a lot of people doing the MyProgramException or MyLibException but that doesn't make sense. In this case, you are just making things harder. Back on the original subject, GetOptException is not what you want. As getopt is supposed to handle command line parameters, you'll use exception like : MissingParameterException, WrongFormatException, UnknowParameterException or anything that is meaningful. Those Exceptions would inherit from something like CommandLineException. This is useful because it describe what you are facing. Because you cant to know what is the problem, not which piece of code face the problem.
Point taken. I suggested GetOptException initially because, based on usage patterns of std.getopt, it seems to be most common to just catch the exception (be it a parsing error, type coercion error, or whatever) and print it.
 If this politic is choosen, then It would make sense to have several
 modules of phobos throwing exceptions of the same type, or inheriting
 from the same base class.

 Exception type is a convenient way to filter what you catch and what you
 don't know how to handle at this point.
-- - Alex
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 1:27 PM, Alex Rnne Petersen wrote:
 Point taken. I suggested GetOptException initially because, based on
 usage patterns of std.getopt, it seems to be most common to just catch
 the exception (be it a parsing error, type coercion error, or whatever)
 and print it.
In fact for the vast majority of exceptions it seems to be most common to just catch the exception and print it. Tutorial examples never help, either. They go something like: try { ... stuff ... } catch (DatabaseException e) { print("There was a database exception: ", e); } catch (NetworkException e) { print("There was a network exception: ", e); } etc. Andrei
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 15:14:04 Andrei Alexandrescu wrote:
 On 2/18/12 1:27 PM, Alex R=C3=B8nne Petersen wrote:
 Point taken. I suggested GetOptException initially because, based o=
n
 usage patterns of std.getopt, it seems to be most common to just ca=
tch
 the exception (be it a parsing error, type coercion error, or whate=
ver)
 and print it.
=20 In fact for the vast majority of exceptions it seems to be most commo=
n
 to just catch the exception and print it.
=20
 Tutorial examples never help, either. They go something like:
=20
 try {
    ... stuff ...
 } catch (DatabaseException e) {
    print("There was a database exception: ", e);
 } catch (NetworkException e) {
    print("There was a network exception: ", e);
 }
=20
 etc.
If that's what you're doing, then you can just catch Exception. But if = you're=20 actually going to _use_ the fact that you have typed Exceptions, then y= ou can=20 do error handling specific to what went wrong, and it can be very usefu= l. It becomes even more important with more complex programs. If I call a=20= function, and it throws a FileException, then I know that the operation= failed=20 to operate on the file properly and can handle it appropriately, wherea= s if it=20 threw a ParserException, then I know that it failed in parsing the file= and can=20 handle _that_ appropriately. It gets even better when you get more spec= ific=20 exceptions such as FileNotFoundException or FileOpenFailedException, be= cause=20 then you can handle stuff very specific to what exactly what went wrong= . Exception can't give you any of that. All it tells you is that somethin= g went=20 wrong, and the best that you can do is print a message like your exampl= e does. I'd hate to see us have a crippled exception hierarchy just because som= e=20 people mishandle exceptions and simply print out messages on failure in= all=20 cases. Obviously sometimes that's what you have to do, but often you ca= n=20 handle them much better, and that's part of the reason that we have=20= _Exception_ and not just Error. And a proper exception hierarchy can be= very=20 benificial to programmers who actually use it correctly. - Jonathan M Davis
Feb 18 2012
parent "Ben Hanson" <Ben.Hanson tikit.com> writes:
On Saturday, 18 February 2012 at 22:35:58 UTC, Jonathan M Davis
wrote:
 I'd hate to see us have a crippled exception hierarchy just 
 because some
 people mishandle exceptions and simply print out messages on 
 failure in all
 cases. Obviously sometimes that's what you have to do, but 
 often you can
 handle them much better, and that's part of the reason that we  
 have
 _Exception_ and not just Error. And a proper exception 
 hierarchy can be very
 benificial to programmers who actually use it correctly.
+1 Inheritance in general can (and is) misused too. Does that mean that should also be banned? Regards, Ben
Mar 05 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/02/2012 22:14, Andrei Alexandrescu a crit :
 On 2/18/12 1:27 PM, Alex Rnne Petersen wrote:
 Point taken. I suggested GetOptException initially because, based on
 usage patterns of std.getopt, it seems to be most common to just catch
 the exception (be it a parsing error, type coercion error, or whatever)
 and print it.
In fact for the vast majority of exceptions it seems to be most common to just catch the exception and print it. Tutorial examples never help, either. They go something like: try { ... stuff ... } catch (DatabaseException e) { print("There was a database exception: ", e); } catch (NetworkException e) { print("There was a network exception: ", e); } etc. Andrei
This is a dumb example. This show more the fact that Java forcing you to catch every single expression is a bad idea than the fact that having several types of exception is bad. If you have nothing to do with that exception and don't know how to recover from it, just don't catch it. Or catch them all (Pokemon !!!) using catch(Exception e). You know that real life programming is different than a tutorial.
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 5:30 PM, deadalnix wrote:
 You know that  real life  programming is different than a tutorial.
Problem is (a) all tutorials suggests you have a world of things to do; (b) all real code does one thing. Andrei
Feb 18 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 00:26, Andrei Alexandrescu a crit :
 On 2/18/12 5:30 PM, deadalnix wrote:
 You know that  real life  programming is different than a tutorial.
Problem is (a) all tutorials suggests you have a world of things to do; (b) all real code does one thing. Andrei
And usually, one thing has several way to fail.
Feb 18 2012
prev sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Saturday, 18 February 2012 at 23:26:14 UTC, Andrei 
Alexandrescu wrote:
 (b) all real code does one thing.

 Andrei
No. It's conceivable that *most* cases simply log the exception (e.g. by printing it) then either move back or move on (!). But it's most definitely not *all* and it's when you have a meaningful fall-back or recovery mechanism that exceptions shine and it's what they're designed for (because you are basically arguing against exceptions here, not their proper use, which should be obvious from how they function). The more complex your program becomes, the farther away from uncontrollable factors like user input and otherwise unexpected environments you get, and this is when interesting catch blocks are born. Even so, exceptions are still useful in shallow parts of your program too - you could catch a GetOptException (more specific exception types than this with attached data would be even better, as noted by everyone before me) and re-query the user for new input (not very Unix-y, but that's irrelevant). You can't do that with just an Exception - what if the exception came from one of the callbacks you passed to getopt, or from a bug in std.getopt? All that said, I don't think std.getopt is a good example because it's comparatively trivial code. std.file would be a much better area to examine for examples of interesting catch blocks.
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 5:47 PM, Jakob Ovrum wrote:
 you are basically arguing against exceptions here
I must have argued my question and point very poorly. Andrei
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu wrote:
 On 2/18/12 5:47 PM, Jakob Ovrum wrote:
 you are basically arguing against exceptions here
I must have argued my question and point very poorly.
You definitely seem to be arguing for a single Exception type, which does _not_ make for good error handling code, since all it tells you is that _something_ went wrong, not what. And that goes against how Exceptions are designed. They purposely are designed to have an inheritance hierarchy. I grant you that simply having an exception type per module which is identical to Exception save for its name and the fact that it's derived from Exception rather than Throwable is not the best way to go. I do think that it's somewhat better than just using Exception everywhere, since it gives you a better idea of went wrong (e.g. UTFException rather than a TimeException), but ideally, it would give much better information than that, and we'd have a better exception hierarchy with related exceptions being derived from one another or from a common exception type rather than simply being based on what module they're in. C++ is a horrible example of how exceptions should be done, so if you're basing what you want off of that, then that makes me think that you should be better familiar with how other, more recent languages use them (though maybe
From using Java, I think that how it handles exceptions in general is _far_ 
superior to how they're frequently dealt with in C++ (though that does tend to depend on who's doing the developing, since you _can_ have a decent exception hierarchy in C++). - Jonathan M Davis
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 6:28 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu wrote:
 On 2/18/12 5:47 PM, Jakob Ovrum wrote:
 you are basically arguing against exceptions here
I must have argued my question and point very poorly.
You definitely seem to be arguing for a single Exception type
No. Andrei
Feb 18 2012
parent reply "Bernard Helyer" <b.helyer gmail.com> writes:
On Sunday, 19 February 2012 at 02:27:07 UTC, Andrei Alexandrescu 
wrote:
 On 2/18/12 6:28 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu 
 wrote:
 On 2/18/12 5:47 PM, Jakob Ovrum wrote:
 you are basically arguing against exceptions here
I must have argued my question and point very poorly.
You definitely seem to be arguing for a single Exception type
No.
Yes. Bernard (This game is fun!)
Feb 18 2012
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Bernard Helyer" <b.helyer gmail.com> wrote in message 
news:npnjfynxdnpykaemlqrm forum.dlang.org...
 On Sunday, 19 February 2012 at 02:27:07 UTC, Andrei Alexandrescu wrote:
 On 2/18/12 6:28 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu wrote:
 On 2/18/12 5:47 PM, Jakob Ovrum wrote:
 you are basically arguing against exceptions here
I must have argued my question and point very poorly.
You definitely seem to be arguing for a single Exception type
No.
Yes. Bernard (This game is fun!)
No it isn't ;)
Feb 18 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 9:50 PM, Bernard Helyer wrote:
 On Sunday, 19 February 2012 at 02:27:07 UTC, Andrei Alexandrescu wrote:
 On 2/18/12 6:28 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu wrote:
 On 2/18/12 5:47 PM, Jakob Ovrum wrote:
 you are basically arguing against exceptions here
I must have argued my question and point very poorly.
You definitely seem to be arguing for a single Exception type
No.
Yes.
Wait, as the guy who actually has the ground truth, don't I have some sort of priority? I have no interest in lying about this. Andrei
Feb 18 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 04:28:25PM -0800, Jonathan M Davis wrote:
[...]
 C++ is a horrible example of how exceptions should be done, so if
 you're basing what you want off of that, then that makes me think that
 you should be better familiar with how other, more recent languages

 use Exceptions, I don't know).  From using Java, I think that how it
 handles exceptions in general is _far_ superior to how they're
 frequently dealt with in C++ (though that does tend to depend on who's
 doing the developing, since you _can_ have a decent exception
 hierarchy in C++).
[...] The basic problem with C++ exceptions is that it allows you to throw literally *anything*. Which is nice and generous, but not having a common root to all exceptions cripples the system, because there is no functionality that can be depended upon, like toString when what you want is an error message. Furthermore, not having a properly designed skeletal hierarchy to inherit from also adds to the problem. With no existing standard hierarchy to serve as a reference point, library writers just invent their own systems, most of which don't interoperate properly with each other. And thus the mess that is the C++ exception hierarchy. I certainly hope D can do better than that. T -- If Java had true garbage collection, most programs would delete themselves upon execution. -- Robert Sewell
Feb 18 2012
next sibling parent reply "Alf P. Steinbach" <alf.p.steinbach+usenet gmail.com> writes:
On 19.02.2012 02:17, H. S. Teoh wrote:
 The basic problem with C++ exceptions is that it allows you to throw
 literally *anything*. Which is nice and generous, but not having a
 common root to all exceptions cripples the system, because there is no
 functionality that can be depended upon, like toString when what you
 want is an error message.
No, that is a misunderstanding (completely). 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. Especially the last point is pretty silly. :-) And then when C++11 added support for propagating error codes, it was designed as *nix-specific (char-based messages only) and overly complex. I haven't heard about anyone actually using that stuff.
 Furthermore, not having a properly designed skeletal hierarchy to
 inherit from also adds to the problem. With no existing standard
 hierarchy to serve as a reference point, library writers just invent
 their own systems, most of which don't interoperate properly with each
 other. And thus the mess that is the C++ exception hierarchy.

 I certainly hope D can do better than that.
Agreed. Cheers, - Alf
Feb 18 2012
parent "Pavel Vozenilek" <pavel_vozenilek hotmail.com> writes:
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
Feb 19 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 7:17 PM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 04:28:25PM -0800, Jonathan M Davis wrote:
 [...]
 C++ is a horrible example of how exceptions should be done, so if
 you're basing what you want off of that, then that makes me think that
 you should be better familiar with how other, more recent languages

 use Exceptions, I don't know).  From using Java, I think that how it
 handles exceptions in general is _far_ superior to how they're
 frequently dealt with in C++ (though that does tend to depend on who's
 doing the developing, since you _can_ have a decent exception
 hierarchy in C++).
[...] The basic problem with C++ exceptions is that it allows you to throw literally *anything*.
I agree that that was a design mistake, but it's easy to opt out the unnecessary part. All C++ applications I've worked with define exception hierarchies. In fact at Facebook we have a lint rule that prohibits throwing arbitrary stuff. Andrei
Feb 18 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 08:20:23PM +0100, deadalnix wrote:
[...]
 However, in D, I think this is more the role of an Errors. Exception
 are something  softer . It will alert you on problems your program
 encounter, but that are recoverable.
I agree. [...]
 Let's get an exemple : your program ask a file to the user and do some
 operations with this file. If the file doesn't exists, you can prompt
 for another file to the user with a meaningful message and start
 again. However, the first version of your program can just ignore that
 case and fail with a less specific handler in firsts versions.
 
 You cannot achieve something like that if you don't have a useful
 type to rely on. Here something like FileNotFoundException is what
 you want.
Exactly. It's important to distinguish between exceptions sometimes. For example: File promptSaveFile() { do { string filename = readInput(); try { return File(filename); } catch(FileNotFoundException e) { writeln("No such file, please try " "again"); } } while(true); assert(false); } It would not make sense for that catch to be *any* Exception; for example, if the exception was ReadFailureException, you do *not* want to catch that, but you want to propagate it. But sometimes, you *want* to handle ReadFailureException, for example: void salvageBadSector(out ubyte[] sector) { int n_attempts = 10; while(n_attempts > 0) { try { disk.read(sector); } catch(ReadFailureException e) { disk.recalibrate(); n_attempts--; continue; // try again } // If another error occurs, say OutOfMemory, // or some other unexpected problems, you do NOT // want to continue. } writeln("Cannot salvage data from bad sector"); } Having distinct exception types allows you to do this without resorting to hacks, or bypassing the library altogether (because of the use of overly generic exception types). Having a proper exception class hierarchy is also necessary. For example, most programs don't care about the difference between FileNotFoundException and ReadFailureException; they just want to print an error message and cleanup if I/O fails: Data loadData() { try { return readFromFile(); } catch(IOException e) { writeln("I/O error occurred, aborting"); cleanup(); exit(1); } assert(false); } It would be wrong to just catch Exception here, because if it wasn't an I/O error, but something else, it needs to be propagated!
 The type of the exception must depend on the problem you are facing,
 not on the module that trhow it. I see a lot of people doing the 
 MyProgramException  or  MyLibException  but that doesn't make
 sense. In this case, you are just making things harder.
Agreed. [...]
 If this politic is choosen, then It would make sense to have several
 modules of phobos throwing exceptions of the same type, or inheriting
 from the same base class.
Phobos NEEDS to have a sane exception hierarchy. ESPECIALLY since it is the standard library. So you cannot assume your users cares or don't care about distinguishing between exception types. Some applications just catch Exception, cleanup and exit, but other applications need to know the difference between FileNotFoundException and ReadErrorException. So we need to design a good exception hierarchy for Phobos. The worst thing is if Phobos exceptions are too generic, then applications that need to tell between different problems will have to bypass Phobos and hack their own solution. Which is not good. The standard library should be maximally useful to the extent that it can be.
 Exception type is a convenient way to filter what you catch and what
 you don't know how to handle at this point.
Agreed. T -- The right half of the brain controls the left half of the body. This means that only left-handed people are in their right mind. -- Manoj Srivastava
Feb 18 2012
parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 18-02-2012 21:36, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 08:20:23PM +0100, deadalnix wrote:
 [...]
 However, in D, I think this is more the role of an Errors. Exception
 are something  softer . It will alert you on problems your program
 encounter, but that are recoverable.
I agree. [...]
 Let's get an exemple : your program ask a file to the user and do some
 operations with this file. If the file doesn't exists, you can prompt
 for another file to the user with a meaningful message and start
 again. However, the first version of your program can just ignore that
 case and fail with a less specific handler in firsts versions.

 You cannot achieve something like that if you don't have a useful
 type to rely on. Here something like FileNotFoundException is what
 you want.
Exactly. It's important to distinguish between exceptions sometimes. For example: File promptSaveFile() { do { string filename = readInput(); try { return File(filename); } catch(FileNotFoundException e) { writeln("No such file, please try " "again"); } } while(true); assert(false); } It would not make sense for that catch to be *any* Exception; for example, if the exception was ReadFailureException, you do *not* want to catch that, but you want to propagate it. But sometimes, you *want* to handle ReadFailureException, for example: void salvageBadSector(out ubyte[] sector) { int n_attempts = 10; while(n_attempts> 0) { try { disk.read(sector); } catch(ReadFailureException e) { disk.recalibrate(); n_attempts--; continue; // try again } // If another error occurs, say OutOfMemory, // or some other unexpected problems, you do NOT // want to continue. } writeln("Cannot salvage data from bad sector"); } Having distinct exception types allows you to do this without resorting to hacks, or bypassing the library altogether (because of the use of overly generic exception types). Having a proper exception class hierarchy is also necessary. For example, most programs don't care about the difference between FileNotFoundException and ReadFailureException; they just want to print an error message and cleanup if I/O fails: Data loadData() { try { return readFromFile(); } catch(IOException e) { writeln("I/O error occurred, aborting"); cleanup(); exit(1); } assert(false); } It would be wrong to just catch Exception here, because if it wasn't an I/O error, but something else, it needs to be propagated!
 The type of the exception must depend on the problem you are facing,
 not on the module that trhow it. I see a lot of people doing the 
 MyProgramException  or  MyLibException  but that doesn't make
 sense. In this case, you are just making things harder.
Agreed. [...]
 If this politic is choosen, then It would make sense to have several
 modules of phobos throwing exceptions of the same type, or inheriting
 from the same base class.
Phobos NEEDS to have a sane exception hierarchy. ESPECIALLY since it is the standard library. So you cannot assume your users cares or don't care about distinguishing between exception types. Some applications just catch Exception, cleanup and exit, but other applications need to know the difference between FileNotFoundException and ReadErrorException. So we need to design a good exception hierarchy for Phobos. The worst thing is if Phobos exceptions are too generic, then applications that need to tell between different problems will have to bypass Phobos and hack their own solution. Which is not good. The standard library should be maximally useful to the extent that it can be.
I'd just like to add to this that we don't lose anything per se by introducing specific exception types. People can still catch Exception if they really, really want.
 Exception type is a convenient way to filter what you catch and what
 you don't know how to handle at this point.
Agreed. T
-- - Alex
Feb 18 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/18/2012 10:25 PM, Alex Rnne Petersen wrote:
 I'd just like to add to this that we don't lose anything per se by
 introducing specific exception types. People can still catch Exception
 if they really, really want.
We gain executable bloat. I don't know how how significant the amount would be.
Feb 18 2012
parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 18-02-2012 22:30, Timon Gehr wrote:
 On 02/18/2012 10:25 PM, Alex Rnne Petersen wrote:
 I'd just like to add to this that we don't lose anything per se by
 introducing specific exception types. People can still catch Exception
 if they really, really want.
We gain executable bloat. I don't know how how significant the amount would be.
For a single class that has nothing but a constructor? Negligible IMHO. -- - Alex
Feb 18 2012
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 10:38:06PM +0100, Alex Rønne Petersen wrote:
 On 18-02-2012 22:30, Timon Gehr wrote:
On 02/18/2012 10:25 PM, Alex Rønne Petersen wrote:
I'd just like to add to this that we don't lose anything per se by
introducing specific exception types. People can still catch Exception
if they really, really want.
We gain executable bloat. I don't know how how significant the amount would be.
For a single class that has nothing but a constructor? Negligible IMHO.
[...] Especially a ctor that does nothing but forwards arguments to the base class. It will just be inlined 100% of the time. T -- Любишь кататься - люби и саночки возить.
Feb 18 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/18/2012 10:38 PM, Alex Rnne Petersen wrote:
 On 18-02-2012 22:30, Timon Gehr wrote:
 On 02/18/2012 10:25 PM, Alex Rnne Petersen wrote:
 I'd just like to add to this that we don't lose anything per se by
 introducing specific exception types. People can still catch Exception
 if they really, really want.
We gain executable bloat. I don't know how how significant the amount would be.
For a single class
You want to add only one specific exception type?
 that has nothing but a constructor?
They also have vtbls and TypeInfos.
 Negligible IMHO.
Possible.
Feb 18 2012
parent =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 18-02-2012 23:35, Timon Gehr wrote:
 On 02/18/2012 10:38 PM, Alex Rnne Petersen wrote:
 On 18-02-2012 22:30, Timon Gehr wrote:
 On 02/18/2012 10:25 PM, Alex Rnne Petersen wrote:
 I'd just like to add to this that we don't lose anything per se by
 introducing specific exception types. People can still catch Exception
 if they really, really want.
We gain executable bloat. I don't know how how significant the amount would be.
For a single class
You want to add only one specific exception type?
No, but I have a hard time imagining that even the accumulated amount of exceptions will matter.
 that has nothing but a constructor?
They also have vtbls and TypeInfos.
True. But aren't we micro-optimizing here?
 Negligible IMHO.
Possible.
-- - Alex
Feb 18 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 3:25 PM, Alex Rnne Petersen wrote:
 I'd just like to add to this that we don't lose anything per se by
 introducing specific exception types.
1. Technical debt 2. Set an example that will be followed 3. Boilerplate Andrei
Feb 18 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 03:36:40PM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 3:25 PM, Alex Rnne Petersen wrote:
I'd just like to add to this that we don't lose anything per se by
introducing specific exception types.
1. Technical debt
I don't understand. Could you elaborate?
 2. Set an example that will be followed
If it's a *good* example, it should be followed, no? ;-)
 3. Boilerplate
[...] Can be alleviated if there's a way to use templates to make exception subclasses. But all of this is just the mechanics of the system. We should rather be debating about the conceptual/theoretic merits at this point, than get hung up about the nitty gritty details. That can come later once we've settled on the right conceptual model. T -- Almost all proofs have bugs, but almost all theorems are true. -- Paul Pedersen
Feb 18 2012
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 20:20:23 deadalnix wrote:
 I think your oppinion here is shaped by C++. For what I experienced, =
in
 C++, exception are only usefull for very important problem you cannot=
 possibly solve, and at the best log the error and exit.
=20
 An exemple is std::bad_alloc .
=20
 However, in D, I think this is more the role of an Errors. Exception =
are
 something =C2=AB softer =C2=BB. It will alert you on problems your pr=
ogram
 encounter, but that are recoverable.
=20
 You cannot recover from any exception at any place (sometime, you jus=
t
 cannot at all).
=20
 Let's get an exemple : your program ask a file to the user and do som=
e
 operations with this file. If the file doesn't exists, you can prompt=
 for another file to the user with a meaningful message and start agai=
n.
 However, the first version of your program can just ignore that case =
and
 fail with a less specific handler in firsts versions.
=20
 You cannot achieve something like that if you don't have a useful typ=
e
 to rely on. Here something like FileNotFoundException is what you wan=
t.
=20
 The type of the exception must depend on the problem you are facing, =
not
 on the module that trhow it. I see a lot of people doing the =C2=AB
 MyProgramException =C2=BB or =C2=AB MyLibException =C2=BB but that do=
esn't make sense.
 In this case, you are just making things harder.
=20
 Back on the original subject, GetOptException is not what you want. A=
s
 getopt is supposed to handle command line parameters, you'll use
 exception like : MissingParameterException, WrongFormatException,
 UnknowParameterException or anything that is meaningful.
=20
 Those Exceptions would inherit from something like CommandLineExcepti=
on.
 This is useful because it describe what you are facing. Because you c=
ant
 to know what is the problem, not which piece of code face the problem=
.
=20
 If this politic is choosen, then It would make sense to have several
 modules of phobos throwing exceptions of the same type, or inheriting=
 from the same base class.
=20
 Exception type is a convenient way to filter what you catch and what =
you
 don't know how to handle at this point.
In general, I agree with all of this. I very much think that having typ= ed=20 exceptions makes a lot of sense. In general, I think that Java got exce= ptions=20 right (ignoring the issue with checked exceptions). Having typed except= ions=20 works really well. In the case of getopt, what we want is a GetOptException which is for a= nything=20 which goes wrong with getopt so that someone can catch that exception t= ype if=20 they want to just handle the case wheree getopt throws but don't want t= o=20 swallow other exception types by calling exception and pretty much just= want=20 to print an error and exit the program or continue without any comand-l= ine=20 arguments if that's what they prefer to do. Then you have other exception types derived from GetOptException which = deal=20 with specific types of issues - such as FlagArgumentMissingException,=20= InvalidFlagArgumentException, UnknownFlagException. Each of those excep= tion=20 types could then give information which specifically pertains to those = errors -=20 such as the flag that had a problem, the type that the flag is supposed= to=20 receive, the type that the flag actually received, the invalid value th= at the=20 flag was given, etc. Such exceptions can then allow you to properly han= dle and=20 report problems with command-line arguments. Right now, all you know is= that=20 something went wrong, and pretty much the best that you can do is print= out=20 that something went wrong. You can do any decent error handling at all.= You=20 need specific exception types which give you the appropriate informatio= n in=20 order to do that. Another example is FileException. It would be benificial to have except= ions=20 like FileNotFoundException, NotFileException, NotDirException,=20 AccessDeniedException, etc which are derived from FileException. Then p= rograms=20 could handle the specific instance of what went wrong nice and cleanly = rather=20 than having to look at error codes. At least FileException provides an = error=20 code, but that makes for much uglier handling code assuming that you ev= en have=20 any clue what the error codes mean. And since the error codes can vary = from OS=20 to OS, you end up with system-specific error handling code if you try a= nd use=20 errno. Whereas if std.file just translated the error code to the correc= t=20 exception type, you're then very cleanly informed as to what the proble= m was=20 and can catch the exception based on which situtations you can recover = from=20 and which you can't as well as having different catch blocks to handle = different=20 problems differently. Simply having an exception type per module is somewhat better than just= having=20 Exception, because it gives you a better idea of what went wrong (e.g. = you got=20 a UTFException rather than a FileException), but it's still way too gen= eral in=20 a lot of cases, and I can see why some would think that it creates need= less=20 boilerplate code. Also, in some cases, having exception types derive fr= om more=20 general exceptions than what the module focuses on can be useful. For=20= instance, Java has IOException as the base for all IOExceptions. FileEx= ception=20 could be derived from that, and then std.stream and std.stdio could cou= ld have=20 their exception types derive from that as well. Then you could specific= ally=20 handle all exceptions related to I/O together. I'm completely sold on typed exceptions, but I think that we could do b= etter=20 with them than we're currently doing. - Jonathan M Davis
Feb 18 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 17:13:16 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.
If you actually want to _handle_ exceptions, how else do you expect to do it? Simply put a code of some kind on the exceptions and then have switch statement to handle them? If you want to go beyond simply printing out an error message when an exception occurs, having a solid exception hierarchy is the way to go. Otherwise, all that your program is has is the knowledge that something went wrong and a message that it could print out to the user which might inform them as to want went wrong, but it has _nothing_ which will help the program itself handle the problem appropriately. Honestly, I think that on the whole, this is something that Java got right. They might use exceptions for more than they should (e.g. if the odds of a function failing are very high, don't use an exception, have it return whether it succeeded or not - Java pretty much never does that). But on the whole, I think that they have the correct approach. - Jonathan M Davis
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 5:20 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:13:16 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.
If you actually want to _handle_ exceptions, how else do you expect to do it? Simply put a code of some kind on the exceptions and then have switch statement to handle them?
The alternative is with virtuals. Do you see a lot of virtuals in base exceptions? Do you see dramatically different interface for different exception types? Andrei
Feb 18 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 00:30, Andrei Alexandrescu a écrit :
 On 2/18/12 5:20 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:13:16 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.
If you actually want to _handle_ exceptions, how else do you expect to do it? Simply put a code of some kind on the exceptions and then have switch statement to handle them?
The alternative is with virtuals. Do you see a lot of virtuals in base exceptions? Do you see dramatically different interface for different exception types? Andrei
What do you have in mind ?
Feb 18 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 On 2/18/12 5:20 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:13:16 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.
If you actually want to _handle_ exceptions, how else do you expect to do it? Simply put a code of some kind on the exceptions and then have switch statement to handle them?
The alternative is with virtuals. Do you see a lot of virtuals in base exceptions? Do you see dramatically different interface for different exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now. And none of that makes sense as virtual functions. And yes, in this case, you're still printing out in every case rather than doing more dynamic handling, but you get much better error messages here, and it may be that a program would want to do something fancier than what I'm doing here (particularly if you're using delegates with getopt). And of course other exception types lend themselves better to doing extra handling rather than printing messages (e.g. parsing exceptions or file handling exceptions might make it so that the program tries again or tries something else, and the user doesn't get involved at all). And even if all of the exception types that you were dealing with were identical save for their type, they could still be very useful. Knowing that you got an IOException when handling a stream rather than a UTFException gives you some idea of what's wrong even if the exception types aren't giving much in the way of additional useful information (though hopefully, they would). In order to actually handle exceptions rather than just print out messages (or to print out more useful messages like in the case of getopt), you need to have exceptions with types of _some_ kind so that code can know what it's dealing with. Messages are really only of any use for humans. So, you either end up with an exception with a field indicating its type (which really doesn't scale very well - particularly when you don't control all of the code you're dealing with and someone could screw up and reuse the same value for as an existing type) which you then switch on, or you have the exceptions in an inheritance hierarchy to indicate their type, which works much better for giving additional information, because then that information can be in fields in the derived exception type rather than the equivalent of a void* in a field in Exception which then must be cast appropriately based on the type field. - Jonathan M Davis
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
Feb 18 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 18:09:30 Andrei Alexandrescu wrote:
 Of course I can. They call it toString(). The code above pretty much
 proves my point with so much wonderful irony.
How?!! toString doesn't help _at all_. It just gives whatever getopt can come up with, not what's appropriate for your program. And right now, since there's no GetOptException, you just end up with a ConvException, so you don't even get a semi-decent toString. You have _no idea_ what flag had the error. The best that you can do is print out that _something_ went wrong, not _what_. And if you want to do fancier handling (_especially_ if you want recover from an exception rather than just print a message), you need _far_ more than toString. Maybe I want to look at the flag that you gave me which was invalid and then suggest what you might have meant. Without _at least_ knowing what the flag was that you used (which I can't do with the current getopt), I can't do that. How could you do something even close to my example with getopt as it is now? Perhaps getopt is not the best example, because it's likely to end up with you giving an error to the user regardless. But other exception handling wouldn't necessarily involve the user at all. The primary issue is giving the program the information that it needs to actually handle and potentially recover from the exception. A string doesn't do that. Giving the exception a specific type according to what went wrong and additional member variables with information on what went wrong _can_ do that. What if something goes wrong when processing a file? Did something go wrong with the file operations or in processing the information? A FileException vs another exception type (e.g. UTFException or ParseException) would tell you that. And if you want to recover from the exception and continue processing the file, knowing what type of ParseException it was could help you do that by giving you the information that you need to continue - which could vary quite a bit depending on what went wrong. This gets even more critical the larger your program is. The farther you get away from the code that threw the exception, the more worthless a generic exception type is (be it Exception or even something somewhat more specific like FileException), and the program can't really handle it. It's forced to either ignore it (possibly logging something), retry what it's trying to do, or give up on what it's trying to do. In general, handling the exceptions closer to their source is better, which helps, but that's not always possible. Even a slightly more advanced exception hierarchy (e.g. having GetOptException with a flag, even if you don't actually derive any other exception types from it) would be an improvement over what we have now. - Jonathan M Davis
Feb 18 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 06:09:30PM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
[...]
If we have GetOptException, it should have a variable for which flag failed.
Exception doesn't have that. And what if the flag failed because it was given a
bad argument? Then the exception needs a field for that argument. Then you can
get something like

try
     getopt(args, ...)
catch(MissingArgumentException mae)
{
     stderr.writefln("%s is missing an argument", mae.flag);
     return -1;
}
catch(InvalidArgumentException iae)
{
     stderr.writelfln("%s is not a valid argument for %s. You must give it a
%s.", mae.arg, mae.flag, mae.expectedType);
     return -1;
}
catch(UnknownFlagException ufe)
{
     stderr.writefln("%s is not a known flag.", ufe.ufe);
     return -1;
}
catch(GetOptException goe)
{
     stderr.writefln("There was an error with %s",  goe.flag);
     return -1;
}
//A delegate that you passed to getopt threw an exception.
catch(YourException ye)
{
     //...
}
catch(Exception e)
{
     stderr.writeln("An unexpected error occured.");
     return -1;
}

You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony.
[...] One word: internationalization. Then toString() falls flat on its face. You can't even fix this by having Phobos do the translation internally. You can't expect users of Phobos to only ever support languages that Phobos has been previously translated to, for one thing. That would be a crippling disability. T -- Valentine's Day: an occasion for florists to reach into the wallets of nominal lovers in dire need of being reminded to profess their hypothetical love for their long-forgotten.
Feb 18 2012
next sibling parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Sat, 18 Feb 2012 18:40:50 -0600, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:
 On Sat, Feb 18, 2012 at 06:09:30PM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
[...]
If we have GetOptException, it should have a variable for which flag failed.
Exception doesn't have that. And what if the flag failed because it was given a
bad argument? Then the exception needs a field for that argument. Then you can
get something like

try
     getopt(args, ...)
catch(MissingArgumentException mae)
{
     stderr.writefln("%s is missing an argument", mae.flag);
     return -1;
}
catch(InvalidArgumentException iae)
{
     stderr.writelfln("%s is not a valid argument for %s. You must give it a
%s.", mae.arg, mae.flag, mae.expectedType);
     return -1;
}
catch(UnknownFlagException ufe)
{
     stderr.writefln("%s is not a known flag.", ufe.ufe);
     return -1;
}
catch(GetOptException goe)
{
     stderr.writefln("There was an error with %s",  goe.flag);
     return -1;
}
//A delegate that you passed to getopt threw an exception.
catch(YourException ye)
{
     //...
}
catch(Exception e)
{
     stderr.writeln("An unexpected error occured.");
     return -1;
}

You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony.
[...] One word: internationalization. Then toString() falls flat on its face. You can't even fix this by having Phobos do the translation internally. You can't expect users of Phobos to only ever support languages that Phobos has been previously translated to, for one thing. That would be a crippling disability.
That an argument of an internationalization module as part of the standard library, not for or against a particular exception module. I don't know what a good/robust module would look like, but many open source projects simply use a global string[string] and it seems to work okay for them.
Feb 18 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 19:01:31 Robert Jacques wrote:
 That an argument of an internationalization module as part of the standard
 library, not for or against a particular exception module. I don't know
 what a good/robust module would look like, but many open source projects
 simply use a global string[string] and it seems to work okay for them.
It's an argument against making it so that the only way to get the error information from an exception is toString. Exception handling is far more flexible when the exception's type and member variables gives you the information you need to actually handle the exception - or in the case of internationalization, generate your own error message. But ideally, users wouldn't see the result of an exception's toString regardless (especially since that includes stuff like the stack trace). Not even the msg field is really acceptable for that IMHO. It's great for debugging and maybe log messages, but not dealing with the user. To deal with the user appropriately, you need to know _what_ went wrong in a way that your program can process it and react appropriately, which strings just don't do. - Jonathan M Davis
Feb 18 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Sat, 18 Feb 2012 19:13:02 -0600, Jonathan M Davis <jmdavisProg gmx.com>
wrote:

 On Saturday, February 18, 2012 19:01:31 Robert Jacques wrote:
 That an argument of an internationalization module as part of the standard
 library, not for or against a particular exception module. I don't know
 what a good/robust module would look like, but many open source projects
 simply use a global string[string] and it seems to work okay for them.
It's an argument against making it so that the only way to get the error information from an exception is toString. Exception handling is far more flexible when the exception's type and member variables gives you the information you need to actually handle the exception - or in the case of internationalization, generate your own error message. But ideally, users wouldn't see the result of an exception's toString regardless (especially since that includes stuff like the stack trace). Not even the msg field is really acceptable for that IMHO. It's great for debugging and maybe log messages, but not dealing with the user. To deal with the user appropriately, you need to know _what_ went wrong in a way that your program can process it and react appropriately, which strings just don't do. - Jonathan M Davis
But you _always_ know what went wrong: An unexpected error occurred while trying to do X, where X is whatever is inside the try-catch block. Exceptions are for exceptional situations and not supposed to be used as a form of out of band information return (or at least that's what every programming book tells me). I see typed exceptions as having a strong coupling problem; exception handling that is aware enough to handle a typed exception, is so close to the exception generation code that most of the information conveyed by the 'type' is also conveyed by the context. Now, the further away exception handling gets from exception generation, the less information context coveys, but the ability of the handler to do anything smarter than log the exception and retry / cancel becomes less and less. The other issue I see with typed exceptions is that the classic 'is a' relationship seems weak; to be informative and useful and exception must map to a specific or small set of error/unexpected conditions. However, this specificity naturally leads to large number of final classes, which isn't really leveraging OO principals. It's hard to see what, if any, benefit the intermediate base classes give over a generic exception.
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 19:59:52 Robert Jacques wrote:
 But you _always_ know what went wrong:
No you don't. Take getopt for instance. If I want to appropriately handle what went wrong that caused getopt to throw an exception, I need information on what went wrong. I need to know what flag failed and why it failed (e.g. unknown or it was given an invalid value). Exception doesn't give you any of that. You need a more specific exception type to get that information.
 An unexpected error occurred while
 trying to do X, where X is whatever is inside the try-catch block.
 Exceptions are for exceptional situations and not supposed to be used as a
 form of out of band information return (or at least that's what every
 programming book tells me).
Exceptions are for handling when something goes wrong in your program - frequently from interacting with users or I/O. It's much cleaner to write code which assumes that opening a file and reading for it will work than to check every operation to see whether it succeeded or not. Then you can just set up a try-catch block around it, and handle the error if it occurs. Whether the exception is "unexpected" is debatable. It _is_ expected in the sense that if something goes wrong with opening and reading the file, you're going to get an exception, and that's not a bug in your program, but it's not expected to happen in the common case. It's still expected to happen at least some of the time though. It's not like "something went wrong and we don't know what to do." If that's the case, you're moving towards Error territory, though that depends on if you can recover from a failed operation even if you have no clue what went wrong. When you avoid using exceptions is when there's a high chance that the operation will fail. Then you have the function return whether it succeeded or not, and exceptions would just slow the program down (I believe that a number of socket operations fall in this category for instance). But if the normal case is that it will work but occasionally it won't, an exception is the better way to go, since it leads to cleaner code. What's truly horrible is when you throw exceptions for normal situations (an extreme example being that you throw when an operation succeeds). _That_ is completely misusing exceptions, but most people don't do that. Exceptions are for error handling. If anything, I think that talk about "exceptional" cases leads people to completely misuse exceptions, because they often end up using them only for cases where Error should be used - that is unrecoverable situations. Personally, I'm against the use of error codes and returning whether an operation succeeded instead of using exceptions in the general case and that those should only be used when failure is _likely_. As long as it's at all reasonable to assume that an operation will succeed, using an exception to handle when it doesn't is far better than using the return value to indicate failure. - Jonathan M Davis
Feb 18 2012
parent "Robert Jacques" <sandford jhu.edu> writes:
On Sat, 18 Feb 2012 20:21:46 -0600, Jonathan M Davis <jmdavisProg gmx.com>
wrote:

 On Saturday, February 18, 2012 19:59:52 Robert Jacques wrote:
 But you _always_ know what went wrong:
No you don't. Take getopt for instance. If I want to appropriately handle what went wrong that caused getopt to throw an exception, I need information on what went wrong. I need to know what flag failed and why it failed (e.g. unknown or it was given an invalid value). Exception doesn't give you any of that. You need a more specific exception type to get that information.
There's a difference between knowing what went wrong and knowing how to fix it. That said, I fully agree with your excellent post. My intent wasn't to propose or support the use of error codes, but to observe that effective use typed exceptions tends to look a lot like error code. For instance, the function calling getop knows how to use getop, what getop's exceptions are and can (hopefully) use the exception's extra information to recover gracefully. But now consider the function that calls the function that calls getop. It probably doesn't even know that getop can be called and that its exceptions can be thrown. Even if it did, it probably doesn't have the access to getop needed utilize that information in the recovery process. Worse, if function A could leverage the type exception, it probably means A and B's implementations are heavily coupled leading to long term maintenance issues. With the number of times 'bad exception hierarchy' has come up in this discussion and the number of times I've seen MyClassException be a simple wrapper around whatever happens to throw inside MyClass, I guess I'm just ready to explorer solutions out of the box. All my brains have been able to come up with so far is a) beef up exception to aid generic recovery code and b) add a TupleException/VariantException that would allow you to express enforce(isFlagGood[i], i, flags[i], "My flag error message"); or something. i.e. a way to provide extra info when available in the leanest possible manner.
Feb 18 2012
prev sibling parent reply Jim Hewes <jimhewes gmail.com> writes:
On 2/18/2012 5:59 PM, Robert Jacques wrote:
 But you _always_ know what went wrong: An unexpected error occurred
 while trying to do X, where X is whatever is inside the try-catch block.
 Exceptions are for exceptional situations...
Not to jump on you in particular for using the phrase "exceptions are for exceptional situations”, but I've always heard people say this in arguments about exceptions vs. returning error codes. Unfortunately those people usually never go on to define what exceptional situations are, so those trying to learn about using exceptions are left no better off. If exceptional cases are like divide-by-zero or hardware failures, then surely a bad parameter to a function is not “exceptional”. Then what? Is out-of-memory exceptional or something you should expect might happen given that memory is finite? I think of exception handling as tied to contract programming. A function has a specific job that it's supposed to do. If for any reason it cannot do that job successfully, an exception should be thrown. That can include even bad parameters (although if you have bad parameters to internal functions I'd think that is a design bug and could be handled by asserts). Look at the .NET library; it seems to work this way. So I think the term 'exceptional situations' has been kind of useless.
 and not supposed to be used as
 a form of out of band information return (or at least that's what every
 programming book tells me).
If what you mean is that exceptions should not be used to return information when the function is successful, I agree. But it can be used to return extra details about errors when a function fails.
 I see typed exceptions as having a strong
 coupling problem; exception handling that is aware enough to handle a
 typed exception, is so close to the exception generation code that most
 of the information conveyed by the 'type' is also conveyed by the
 context. Now, the further away exception handling gets from exception
 generation, the less information context coveys, but the ability of the
 handler to do anything smarter than log the exception and retry / cancel
 becomes less and less.
I can't say agree there, if I'm understand you right. Not every exception coming out of a function was generated by that function. It may have come from several levels below that. Maybe you can handle the former because it is more immediate but not the latter. So without exception types how would you know the difference between them? You could use error codes and switch on them but it defeats one of the main purposes of exception handling. So I think if there are exceptions generated from further away, it's an argument _for_ exception types rather than against it. I can think of a example. At work I wrote a driver that communicates to a device over USB. If the device is suddenly unplugged, the lowest level of code throws a “connection” exception. And this can happen in several places. Most levels can't do anything about this (other than just be exception safe) and the exception just percolates up where things get closed and cleaned up. However, it would be possible, though not currently implemented, to recover from this such that the driver waits for the device to get reconnected and refreshes handles and all that, and the application and user above never need to know. Where that recovery happens isn't necessarily at the lowest level but somewhere in a middle layer. So I think having a particular exception type is useful here. But I don't want to have to check error codes because under normal circumstances there are just too many places to check for this type of error.
 The other issue I see with typed exceptions is
 that the classic 'is a' relationship seems weak; to be informative and
 useful and exception must map to a specific or small set of
 error/unexpected conditions. However, this specificity naturally leads
 to large number of final classes, which isn't really leveraging OO
 principals. It's hard to see what, if any, benefit the intermediate base
 classes give over a generic exception.
Well, I think what H.S Tech has been saying in this thread makes sense to me. You can have an exception hierarchy where base classes represent groups or classes of errors that you can check for without having to catch all exception. But you don't have thousands of classes where you map each possible error code to a unique class. You can just have more general types and then put the specific information in the exception. For example, just have one BadParameter exception and then store information about which parameter is bad and why in the exception. Jim
Feb 18 2012
next sibling parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Sat, 18 Feb 2012 23:09:17 -0600, Jim Hewes <jimhewes gmail.com> wrote:
 On 2/18/2012 5:59 PM, Robert Jacques wrote:
 But you _always_ know what went wrong: An unexpected error occurred
 while trying to do X, where X is whatever is inside the try-catch block.
 Exceptions are for exceptional situations...
Not to jump on you in particular for using the phrase "exceptions are for exceptional situations”, but I've always heard people say this in arguments about exceptions vs. returning error codes. Unfortunately those people usually never go on to define what exceptional situations are, so those trying to learn about using exceptions are left no better off. If exceptional cases are like divide-by-zero or hardware failures, then surely a bad parameter to a function is not “exceptional”. Then what? Is out-of-memory exceptional or something you should expect might happen given that memory is finite? I think of exception handling as tied to contract programming. A function has a specific job that it's supposed to do. If for any reason it cannot do that job successfully, an exception should be thrown. That can include even bad parameters (although if you have bad parameters to internal functions I'd think that is a design bug and could be handled by asserts). Look at the .NET library; it seems to work this way. So I think the term 'exceptional situations' has been kind of useless.
Not to jump on you in particular :) but bad user parameters should never be treated as exceptional. Even bad 'internal' parameters that are passed via the external API aren't exceptional. Programmers being lazy about input parameter checking is how hackers make their money. [snip]
 I see typed exceptions as having a strong
 coupling problem; exception handling that is aware enough to handle a
 typed exception, is so close to the exception generation code that most
 of the information conveyed by the 'type' is also conveyed by the
 context. Now, the further away exception handling gets from exception
 generation, the less information context coveys, but the ability of the
 handler to do anything smarter than log the exception and retry / cancel
 becomes less and less.
I can't say agree there, if I'm understand you right. Not every exception coming out of a function was generated by that function. It may have come from several levels below that. Maybe you can handle the former because it is more immediate but not the latter. So without exception types how would you know the difference between them? You could use error codes and switch on them but it defeats one of the main purposes of exception handling. So I think if there are exceptions generated from further away, it's an argument _for_ exception types rather than against it. I can think of a example. At work I wrote a driver that communicates to a device over USB. If the device is suddenly unplugged, the lowest level of code throws a “connection” exception. And this can happen in several places. Most levels can't do anything about this (other than just be exception safe) and the exception just percolates up where things get closed and cleaned up. However, it would be possible, though not currently implemented, to recover from this such that the driver waits for the device to get reconnected and refreshes handles and all that, and the application and user above never need to know. Where that recovery happens isn't necessarily at the lowest level but somewhere in a middle layer. So I think having a particular exception type is useful here. But I don't want to have to check error codes because under normal circumstances there are just too many places to check for this type of error.
Yes, the USB stack has a high-level layer that can recover from a connection loss, but the rest of the protocol stack above and below it can't understand it and presumably ignore it. And you have just introduced a fairly long range dependency between the lowest level of your code and middle layer. (To say nothing of the implicit dependences implied with the intervening layers). This might be the best/only solution to the problem, but it also makes the code base larger and more fragile. I wasn't speaking in absolutes, but in relative difficulties.
  > The other issue I see with typed exceptions is
 that the classic 'is a' relationship seems weak; to be informative and
 useful and exception must map to a specific or small set of
 error/unexpected conditions. However, this specificity naturally leads
 to large number of final classes, which isn't really leveraging OO
 principals. It's hard to see what, if any, benefit the intermediate base
 classes give over a generic exception.
Well, I think what H.S Tech has been saying in this thread makes sense to me. You can have an exception hierarchy where base classes represent groups or classes of errors that you can check for without having to catch all exception. But you don't have thousands of classes where you map each possible error code to a unique class. You can just have more general types and then put the specific information in the exception. For example, just have one BadParameter exception and then store information about which parameter is bad and why in the exception. Jim
Traditional error codes are enums and so what your describe as BadParameter is an exception wrapping an error code. I really hope no one has been proposing to map error code values to exceptions on a 1:1 basis. But your making many of my points; exceptions are (better, faster, stronger) error codes. My issue regarding the weak 'is a' relationship stems from the extra information in any given final typed exception being very specific to that particular exception. So its hard for me to see the rational between treating some of these as a group while not all of them: if MyGroupException is providing no more information then Exception, why can a function recover a MyGroupException and not a general Exception?
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 00:21:38 Robert Jacques wrote:
 Not to jump on you in particular :) but bad user parameters should never be
 treated as exceptional. Even bad 'internal' parameters that are passed via
 the external API aren't exceptional. Programmers being lazy about input
 parameter checking is how hackers make their money.
I completely disagree. I think that there are many cases where throwing an exception on bad parameters is exactly what you should do. Phobos does this in many places. Often, the function knows best what it wants, and it's the best position to check it. So, having it check the input makes a _lot_ of sense in many cases. For instance, take fromISOExtString on std.datetime.SysTime. Is it at all reasonably to expect that the caller is going to verify that the string is well-formed according to the ISO standard? No. And it's highly probable tha the string being given came from I/O of some kind - be it a file or a socket or whatever. So, it makes _way_ more sense for fromISOExtString to check the string itself and throw on failure. It's also more efficient to do that, because if you checked that the string was valid before passing it to fromISOExtString, then you'd be processing the string twice. There are other cases where you want to use DbC and require that a function is given valid arguments. In those cases, you use assertions, so the checks go away in release mode. But that's not always the best way to go about. And in defensive programming, you end up checking exceptions quite heavily - even in release mode - and throwing when they're bad. The best approach depends on what you're doing, and what the functions are used for. But approaches have their place. - Jonathan M Davis
Feb 18 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Sun, 19 Feb 2012 01:07:44 -0600, Jonathan M Davis <jmdavisProg gmx.com>
wrote:

 On Sunday, February 19, 2012 00:21:38 Robert Jacques wrote:
 Not to jump on you in particular :) but bad user parameters should never be
 treated as exceptional. Even bad 'internal' parameters that are passed via
 the external API aren't exceptional. Programmers being lazy about input
 parameter checking is how hackers make their money.
I completely disagree. I think that there are many cases where throwing an exception on bad parameters is exactly what you should do. Phobos does this in many places. Often, the function knows best what it wants, and it's the best position to check it. So, having it check the input makes a _lot_ of sense in many cases. For instance, take fromISOExtString on std.datetime.SysTime. Is it at all reasonably to expect that the caller is going to verify that the string is well-formed according to the ISO standard? No. And it's highly probable tha the string being given came from I/O of some kind - be it a file or a socket or whatever. So, it makes _way_ more sense for fromISOExtString to check the string itself and throw on failure. It's also more efficient to do that, because if you checked that the string was valid before passing it to fromISOExtString, then you'd be processing the string twice. There are other cases where you want to use DbC and require that a function is given valid arguments. In those cases, you use assertions, so the checks go away in release mode. But that's not always the best way to go about. And in defensive programming, you end up checking exceptions quite heavily - even in release mode - and throwing when they're bad. The best approach depends on what you're doing, and what the functions are used for. But approaches have their place. - Jonathan M Davis
Most of Phobos is an internal library. I think parsing routines are a bad counter-example to my point; combined validation and conversion is generally part of their mandate and we expect them to fail often. We also don't expect the parsing of user input to live deep inside code base; it's almost always done as close to the input as possible (i.e. in the input text box). All too often you end up with code like: try{ parse(...); } catch {...} Also, although less of a problem in D, not ensuring that in input string actually is a string was the source of many a C exploits.
Feb 19 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 11:13:46 Robert Jacques wrote:
 Most of Phobos is an internal library. I think parsing routines are a bad
 counter-example to my point; combined validation and conversion is
 generally part of their mandate and we expect them to fail often. We also
 don't expect the parsing of user input to live deep inside code base; it's
 almost always done as close to the input as possible (i.e. in the input
 text box). All too often you end up with code like: try{ parse(...); }
 catch {...} Also, although less of a problem in D,
It very much depends on the function. In some cases, it makes the most sense to use assertions. In others, it makes the most sense to use exceptions. You have to examine it on a case-by-case basis. For instance, most of std.algorithm asserts on stuff, whereas the unicode stuff generally enforces.
 not ensuring that in
 input string actually is a string was the source of many a C exploits.
That may be, but that really doesn't apply to D aside from how you should be interacting with C functions that involve char*. - Jonathna M Davis
Feb 19 2012
prev sibling parent Jim Hewes <jimhewes gmail.com> writes:
On 2/18/2012 10:21 PM, Robert Jacques wrote:
 Not to jump on you in particular :) but bad user parameters should never
 be treated as exceptional. Even bad 'internal' parameters that are
 passed via the external API aren't exceptional. Programmers being lazy
 about input parameter checking is how hackers make their money.

 [snip]
I imagine that you would disagree with the design of the .NET Framework then, which uses ArgumentException all over. http://msdn.microsoft.com/en-us/library/system.argumentexception.aspx There's also a similar exception defined in the C++ Std Lib. So what do you do when a bad parameter is passed? Return an error code in a return value? So you use both exception handling and return codes? How about bad parameters to constructors (in C++)? You may say that bad parameters are not “exceptional”. OK. But my whole point of that paragraph there was that if you're going to declare one particular error is “exceptional” and another is not in a seemingly arbitrary way, then you need to tell us, for all errors, which are “exceptional” and which are not. You need a more strict definition of “exceptional” so that people know when to use an exception and when not to. Then in the cases they are not supposed to use an exception, what do they do?
 Yes, the USB stack has a high-level layer that can recover from a
 connection loss, but the rest of the protocol stack above and below it
 can't understand it and presumably ignore it. And you have just
 introduced a fairly long range dependency between the lowest level of
 your code and middle layer. (To say nothing of the implicit dependences
 implied with the intervening layers). This might be the best/only
 solution to the problem, but it also makes the code base larger and more
 fragile. I wasn't speaking in absolutes, but in relative difficulties.
I guess I don't understand what you're saying here. It sounds like you're arguing against exceptions in general, which I'm sure you're not. My stack *does* know what a connection loss is at any level. Just like it would know what an out-of-memory error is at any level. It just doesn't want to deal with it everywhere. By not handling this exception in the intermediate layers and rather just making them exception safe, the code is smaller not larger. Even at the topmost level it's known what a connection loss is, and the corrective action is to close the interface and reopen when the device is plugged in again.
 Traditional error codes are enums and so what your describe as
 BadParameter is an exception wrapping an error code. I really hope no
 one has been proposing to map error code values to exceptions on a 1:1
 basis.
Maybe not 1:1, but you talked about the need to have a large number of final classes. That's what I was thinking of.
 But your making many of my points; exceptions are (better,
 faster, stronger) error codes.
Yes. But not just that. Exceptions allow you to *not* write error handling code everywhere so that the normal program flow is easier to read. This is assuming you're writing exception safe code.
 My issue regarding the weak 'is a'
 relationship stems from the extra information in any given final typed
 exception being very specific to that particular exception. So its hard
 for me to see the rational between treating some of these as a group
 while not all of them: if MyGroupException is providing no more
 information then Exception, why can a function recover a
 MyGroupException and not a general Exception?
Borrowing part of the the exception hierarchy from another post in this thread... Error +--Exception +--GetOptException (bad name, I prefer CommandLineException) +--FileException +--IOException (OK, I added this branch, just to show the idea) +--ReadErrorException +--WriteErrorException A ReadErrorException 'is a ' IOException which 'is a' Exception.At some point or level in the code, I may want to handle IOExceptions because I know what to do with those, but not FileExceptions because I don't know what to do with those. If I were to catch Exception, then I would be responsible for re-throwing FileException and any other non-IOException, which I don't want to have to do all over. I think a class hierarchy for exceptions is justified. As an analogue, say that you have a GUI class hierarchy where you have a Window class and various kinds of derived window subclasses. You will probably write a function at some point that takes a WindowPtr as a parameter and works on any type of window (but not any class in the whole library). A catch() would work on a similar idea as the function handling Windows, catching all IOExceptions but not any and all exceptions in the hierarchy. I think it is about having a balance between the number of different exception types and the attributes or error codes you put into them. You may decide that there are too many different types derived from IOException and you always end up catching them all anyway. So you can get rid of the ReadErrorException and WriteErrorException and instead just make these as error codes you can extract from the IOException class. But still, IOException is different from Exception and FileException. And I'm assuming that MyGroupException would be different from Exception in a similar way. Maybe it's not a perfect solution, but what is better? Jim
Feb 19 2012
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 11:09 PM, Jim Hewes wrote:
 I think of exception handling as tied to contract programming. A
 function has a specific job that it's supposed to do. If for any reason
 it cannot do that job successfully, an exception should be thrown. That
 can include even bad parameters (although if you have bad parameters to
 internal functions I'd think that is a design bug and could be handled
 by asserts). Look at the .NET library; it seems to work this way. So I
 think the term 'exceptional situations' has been kind of useless.
I think there's a bit of a confusion there. In fact, I dedicated two distinct chapters to error handling and contract programming in TDPL in an attempt to dispel it. Andrei
Feb 18 2012
parent Jim Hewes <jimhewes gmail.com> writes:
On 2/18/2012 10:50 PM, Andrei Alexandrescu wrote:
 On 2/18/12 11:09 PM, Jim Hewes wrote:

 I think there's a bit of a confusion there. In fact, I dedicated two
 distinct chapters to error handling and contract programming in TDPL in
 an attempt to dispel it.

 Andrei
Sorry, I have not read your book. At least I can say that if you felt there was a need to dispel something then I must not be the only one who thinks this. :) Jim
Feb 19 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 06:09, Jim Hewes a écrit :
 On 2/18/2012 5:59 PM, Robert Jacques wrote:
 But you _always_ know what went wrong: An unexpected error occurred
 while trying to do X, where X is whatever is inside the try-catch block.
 Exceptions are for exceptional situations...
Not to jump on you in particular for using the phrase "exceptions are for exceptional situations”, but I've always heard people say this in arguments about exceptions vs. returning error codes. Unfortunately those people usually never go on to define what exceptional situations are, so those trying to learn about using exceptions are left no better off. If exceptional cases are like divide-by-zero or hardware failures, then surely a bad parameter to a function is not “exceptional”. Then what? Is out-of-memory exceptional or something you should expect might happen given that memory is finite? I think of exception handling as tied to contract programming. A function has a specific job that it's supposed to do. If for any reason it cannot do that job successfully, an exception should be thrown. That can include even bad parameters (although if you have bad parameters to internal functions I'd think that is a design bug and could be handled by asserts). Look at the .NET library; it seems to work this way. So I think the term 'exceptional situations' has been kind of useless.
Well, I think you are messing up between contract and exception. Wrong parameters is a contract problem, not an exceptionnal situation. The exemple you cited below is way more suited for an exception : someone unplug the USB device, then I trhow an Exception and it will cross all layers of my code to reach a point where it is recoverable. Except that contract detail, your post make perfect sense.
Feb 19 2012
next sibling parent Jim Hewes <jimhewes gmail.com> writes:
On 2/19/2012 3:40 AM, deadalnix wrote:
 Le 19/02/2012 06:09, Jim Hewes a écrit :

 Well, I think you are messing up between contract and exception. Wrong
 parameters is a contract problem, not an exceptionnal situation.

 The exemple you cited below is way more suited for an exception :
 someone unplug the USB device, then I trhow an Exception and it will
 cross all layers of my code to reach a point where it is recoverable.

 Except that contract detail, your post make perfect sense.
Then what do you do when bad parameters are passed? Do you return error codes using the function return mechanism? Do you use error codes for some errors and exceptions for others? Why? What's the strictly defined criteria for “exceptional” so that anyone can apply it to their own development and know when to use one or the other? If you use return codes for bad parameters, then does the caller always need to write error handling code to check for the bad parameter error? I did point out that internal and external bad parameters are a different case. If you're talking about an exposed API for others to use, and it's something like a C interface from a DLL where you don't want to propagate exceptions, then of course you would return an error code or something similar. But if you're writing a function that you're going to call with your own code, then I'd say asserts can be used. And if it's a library that's meant to be called by others from the outside, but by the same language, then I say you can use exceptions. Examples are the argument exceptions in the C++ Std Lib and the .NET Framework. I think the fact that these things are called exceptions, and that people then derive the word "exceptional" from that is misleading. But I know these types of things are always debated as religion and I suppose they will be for a while. :) Jim P.S. Sorry, I tend to use 'parameter' and 'argument' interchangeably and probably should not.
Feb 19 2012
prev sibling parent Jim Hewes <jimhewes gmail.com> writes:
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
Feb 19 2012
prev sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 02/18/2012 09:09 PM, Jim Hewes wrote:

 I think of exception handling as tied to contract programming.
I think your use of the word 'contract' is colliding with the contract programming feature. What you describe later does not match with the contract programming and I guess is the reason why Andrei is pointing out two chapters from TDPL. I will reread those chapters later today but I think Andrei is referring to the distinction between assert() and std.exception.enforce().
 A
 function has a specific job that it's supposed to do. If for any reason
 it cannot do that job successfully, an exception should be thrown.
Agreed. That is how we have been using exceptions in C++ successfully at where I work. It makes everything simple.
 That
 can include even bad parameters
Yes, enforce() in D is great for that. I think Andrei agrees.
 (although if you have bad parameters to
 internal functions I'd think that is a design bug and could be handled
 by asserts).
Yes. Contract programming uses asserts().
 If what you mean is that exceptions should not be used to return
 information when the function is successful, I agree. But it can be used
 to return extra details about errors when a function fails.
Agreed. Again, this is how we have been using exceptions. We have the equivalents of detailed exception types that Jonathan M Davis has been mentioning. Come to think of it, less than a dozen.
 Not every
 exception coming out of a function was generated by that function. It
 may have come from several levels below that.
Of course. It is very useful.
 Maybe you can handle the
 former because it is more immediate but not the latter. So without
 exception types how would you know the difference between them? You
 could use error codes and switch on them but it defeats one of the main
 purposes of exception handling. So I think if there are exceptions
 generated from further away, it's an argument _for_ exception types
 rather than against it.
Agreed.
 For example, just have one BadParameter exception and then store
 information about which parameter is bad and why in the exception.
Agreed. I would like to add that exceptions are thrown by code that doesn't know how the exception will be useful. For that reason, especially a library function must provide as much information as possible. Going with the same examples in this thread, FileException is not a good exception to throw when the actual problem is a FilePermissionException or a FileNotFoundException. Ali
Feb 21 2012
parent reply Jim Hewes <jimhewes gmail.com> writes:
On 2/21/2012 2:29 PM, Ali Çehreli wrote:
 On 02/18/2012 09:09 PM, Jim Hewes wrote:

  > I think of exception handling as tied to contract programming.

 I think your use of the word 'contract' is colliding with the contract
 programming feature. What you describe later does not match with the
 contract programming and I guess is the reason why Andrei is pointing
 out two chapters from TDPL.

 I will reread those chapters later today but I think Andrei is referring
 to the distinction between assert() and std.exception.enforce().
Thanks. I assume the objection is about the bad parameters. In design by contract, a function should not be checking the input, correct? It assumes it's correct. But I was mostly thinking of the case when the functions are more of a public API and you can't trust the input. I did mention using assert for internal functions. But I guess if you are strict, you should never check input. I just shouldn't mention design by contract at all then. :)
Feb 22 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 22:33:47 Jim Hewes wrote:
 On 2/21/2012 2:29 PM, Ali =C3=87ehreli wrote:
 On 02/18/2012 09:09 PM, Jim Hewes wrote:
  > I think of exception handling as tied to contract programming.
=20
 I think your use of the word 'contract' is colliding with the contr=
act
 programming feature. What you describe later does not match with th=
e
 contract programming and I guess is the reason why Andrei is pointi=
ng
 out two chapters from TDPL.
=20
 I will reread those chapters later today but I think Andrei is refe=
rring
 to the distinction between assert() and std.exception.enforce().
=20 Thanks. I assume the objection is about the bad parameters. In design=
by
 contract, a function should not be checking the input, correct? It
 assumes it's correct. But I was mostly thinking of the case when the
 functions are more of a public API and you can't trust the input. I d=
id
 mention using assert for internal functions. But I guess if you are
 strict, you should never check input. I just shouldn't mention design=
by
 contract at all then. :)
In DbC, you use assertions to verify arguments, because it's up to the = caller=20 to ensure that the arguments are valid - otherwise it's breaking the co= ntract.=20 So, in release mode, there are no checks, and in non-release mode, your= =20 program gets killed if the contract is violated. It's considered a bug = in the=20 code if bad arguments are passed to the function. In defensive programming, however, you use exceptions, which is much sa= fer,=20 because the function will never actually execute with bad arguments, bu= t it's=20 slower, because it's always checking, whereas assertions are compiled o= ut in=20 release mode. Also, it's not necessarily a bug when you pass an invalid= =20 argument to a function using defensive programming, because throwing an= =20 failure is part of its normal behavior (and won't be compiled out in re= lease=20 mode) and is recoverable, unlike an AssertError. Rather, the function i= s=20 merely informing the caller that it was given bad arguments and lets th= e=20 caller sort it out. DbC tends to work better with internal stuff where you control both the= caller=20 and the callee, whereas defensive programming works better with public = APIs.=20 But regardless, which is best to use depends on the situtation and what= you're=20 goals are. - Jonathan M Davis
Feb 23 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Feb 23, 2012 at 02:57:43AM -0800, Jonathan M Davis wrote:
[...]
 DbC tends to work better with internal stuff where you control both
 the caller and the callee, whereas defensive programming works better
 with public APIs.  But regardless, which is best to use depends on the
 situtation and what you're goals are.
[...] The way I understand it, DbC is used for ensuring *program* correctness (ensure that program logic does not get itself into a bad state); defensive programming is for sanitizing *user input* (ensure that no matter what the user does, the program doesn't get into a bad state). That's why DbC is compiled out in release mode -- the assumption is that you have thoroughly tested your program logic and verified there are no logic problems. Input sanitizing is never compiled out, because you never know what users will do, so you always have to check. The two do somewhat overlap, of course. For example, failing to sanitize user input may eventually lead to passing invalid arguments to an internal function. T -- Only boring people get bored. -- JM
Feb 23 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 6:40 PM, H. S. Teoh wrote:
 One word: internationalization. Then toString() falls flat on its face.
No. I happen to have some expertise in the area as I've participated to two large and heavily internationalized systems. i18n has everything to do with string tables and formatting templates and emphatically nothing to do with exception hierarchies. The only possible link is that exceptions should provide the necessary hooks. Andrei
Feb 18 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 20:30:45 Andrei Alexandrescu wrote:
 On 2/18/12 6:40 PM, H. S. Teoh wrote:
 One word: internationalization. Then toString() falls flat on its face.
No. I happen to have some expertise in the area as I've participated to two large and heavily internationalized systems. i18n has everything to do with string tables and formatting templates and emphatically nothing to do with exception hierarchies. The only possible link is that exceptions should provide the necessary hooks.
They do in that if you want to print a message because of that exception, you need to print something in the correct language, and that won't work with toString unless the exception type has built-in internationalization of some kind - regardless of what that internationalization mechanism might be. Regardless, it's going to generally be better to generate an application- specific error message than just print out whatever Exception outputs with toString (internationalized or otherwise). And you can't do that if all you have to work with is toString. To do that, you need actual data on what went wrong, which requires additional member variables, which means using a specific exception type. - Jonathan M Davis
Feb 18 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 8:38 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 20:30:45 Andrei Alexandrescu wrote:
 On 2/18/12 6:40 PM, H. S. Teoh wrote:
 One word: internationalization. Then toString() falls flat on its face.
No. I happen to have some expertise in the area as I've participated to two large and heavily internationalized systems. i18n has everything to do with string tables and formatting templates and emphatically nothing to do with exception hierarchies. The only possible link is that exceptions should provide the necessary hooks.
They do in that if you want to print a message because of that exception, you need to print something in the correct language, and that won't work with toString unless the exception type has built-in internationalization of some kind - regardless of what that internationalization mechanism might be.
Of course. toString is not a solution to internationalizing exceptions (or any complex messages). Andrei
Feb 18 2012
prev sibling next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 01:09, Andrei Alexandrescu a écrit :
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
Tgat is not even remotely close to a solution. And you'd notice it if you try to write some code about that.
Feb 18 2012
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-19 01:09, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
If you actually want to try and recover from the error rather than just simply print the error message, toString is out of the window. -- /Jacob Carlborg
Feb 19 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 6:48 AM, Jacob Carlborg wrote:
 On 2012-02-19 01:09, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
If you actually want to try and recover from the error rather than just simply print the error message, toString is out of the window.
Absolutely. I was answering to the example given. Andrei
Feb 19 2012
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-19 01:09, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
Say you want to do something like what Git does: $ git statu git: 'statu' is not a git command. See 'git --help'. Did you mean this? status $ That would be quite hard with just toString. -- /Jacob Carlborg
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 6:49 AM, Jacob Carlborg wrote:
 On 2012-02-19 01:09, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
Say you want to do something like what Git does: $ git statu git: 'statu' is not a git command. See 'git --help'. Did you mean this? status $ That would be quite hard with just toString.
An exception hierarchy won't make that easier or more modular. What's needed there is the command and a table of all commands. Andrei
Feb 19 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-19 15:38, Andrei Alexandrescu wrote:
 On 2/19/12 6:49 AM, Jacob Carlborg wrote:
 On 2012-02-19 01:09, Andrei Alexandrescu wrote:
 On 2/18/12 6:03 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
 The alternative is with virtuals. Do you see a lot of virtuals in base
 exceptions? Do you see dramatically different interface for different
 exception types?
You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does. If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } You can't do _anything_ like that right now.
Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony. Andrei
Say you want to do something like what Git does: $ git statu git: 'statu' is not a git command. See 'git --help'. Did you mean this? status $ That would be quite hard with just toString.
An exception hierarchy won't make that easier or more modular. What's needed there is the command and a table of all commands. Andrei
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. -- /Jacob Carlborg
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 19 2012
parent reply deadalnix <deadalnix gmail.com> writes:
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.
Feb 19 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 11:58:19PM +0100, deadalnix wrote:
[...]
 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.
This is starting more and more to sound like what's described in the link that bearophile posted elsewhere in this discussion: http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html I stand by my objection that if something might succeed if it can be retried, then it needs to be retried in the called function, not the caller. I've already posted a summary of the above link, but I'd like to repeat some of the salient points: - With the current try/catch mechanism, we are limited to two modes of recovery: (1) don't bother, just abort; (2) restart the operation from scratch, and hope it won't fail this time. - To have more intelligent error recovery requires that such recovery take place *in the scope of the function that throws the exception*. - However, the code that throws the exception is usually low-level, and as such does not have the global, high-level context to decide which course of action to take. - By the time a thrown exception gets to said high-level code, the stack is already unwound, the original execution context is long gone, and the only recovery left is to restart the entire operation from scratch. Perhaps the "ideal exception handling facility" that Andrei is looking for is a Lispian model, where: - The low-level function that throws the exception also indicates a list of possible recovery strategies; - The high-level code that eventually calls the low-level function registers exception recovery policies with the runtime (in the form of delegates that choose between recovery strategies or prompts the runtime to unwind the stack); - When an exception is thrown, the runtime matches the thrown exception with the recovery policy, and corrects the problem based on the decision of said policy *in the execution context of the low-level function where the problem occurred*. The stack is only unwound if no recovery policy is available to correct the problem, or if the policy says to abort the operation. Note that the registration of recovery delegates needs to be out-of-band (not passed as function parameters) because there can potentially be a very long chain of calls before the low-level code is reached. Manually propagating lists of recovery delegates through the entire call chain is not practical. It also adds lots of noise to the code (clutters normal code with exception-related code) and adds unnecessary CPU overhead for the usual case when no problems happen. That's why this needs to be done by the runtime system. You also want language support for this mechanism, otherwise you end up with tons of boilerplate code. T -- It only takes one twig to burn down a forest. It only takes one twit to burn down a project.
Feb 19 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 7:53 PM, H. S. Teoh wrote:
 I stand by my objection that if something might succeed if it can be
 retried, then it needs to be retried in the called function, not the
 caller.
If read fails from a socket, it's of no use to try it again. One must close the socket, reconnect, and attempt the whole operation once again. Andrei
Feb 19 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 08:33:04PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 7:53 PM, H. S. Teoh wrote:
I stand by my objection that if something might succeed if it can be
retried, then it needs to be retried in the called function, not the
caller.
If read fails from a socket, it's of no use to try it again. One must close the socket, reconnect, and attempt the whole operation once again.
[...] Correct, so that would be a recovery strategy at the operation level, say at sendHttpRequest or something like that. There is not enough information available to sendHttpRequest to know whether or not the caller wants the request to be retried if it fails. But if the higher-level code could indicate this by way of a recovery policy delegate, then this retry can be done at the sendHttpRequest level, instead of percolating up the call stack all the way to submitHttpForm, which then has to reparse user data, convert into JSON, say, and then retry the entire operation all over again. I'm really liking the Lisp approach. It nicely combines stack unwinding with in-context recovery by using a high-level delegate to make sound decisions based on factors outside the scope of the low-level function. You can unwind up to the level where a high-level delegate can step in and select a recovery strategy, and then continue on your way, rather than unwinding all the way up to the top and having to restart from square 1. The delegate that makes decisions doesn't have to be at the absolute top level either (that wouldn't make sense, since it would break encapsulation: top-level code needs to know about inner workings of low-level code so that it can decide how to recover from low-level operations). You can have a chain of delegates that make decisions at various levels, and the one nearest to where the exception is generated is invoked. It can then decide to defer to the next handler if it doesn't have enough information to proceed. T -- Дерево держится корнями, а человек - друзьями.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 8:52 PM, H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 08:33:04PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 7:53 PM, H. S. Teoh wrote:
 I stand by my objection that if something might succeed if it can be
 retried, then it needs to be retried in the called function, not the
 caller.
If read fails from a socket, it's of no use to try it again. One must close the socket, reconnect, and attempt the whole operation once again.
[...] Correct, so that would be a recovery strategy at the operation level, say at sendHttpRequest or something like that. There is not enough information available to sendHttpRequest to know whether or not the caller wants the request to be retried if it fails. But if the higher-level code could indicate this by way of a recovery policy delegate, then this retry can be done at the sendHttpRequest level, instead of percolating up the call stack all the way to submitHttpForm, which then has to reparse user data, convert into JSON, say, and then retry the entire operation all over again. I'm really liking the Lisp approach.
Now we're talking. Ideas. Outside the box. Andrei
Feb 19 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
Content-Disposition: inline

On Sun, Feb 19, 2012 at 09:12:25PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 8:52 PM, H. S. Teoh wrote:
[...]
Correct, so that would be a recovery strategy at the operation level,
say at sendHttpRequest or something like that. There is not enough
information available to sendHttpRequest to know whether or not the
caller wants the request to be retried if it fails.

But if the higher-level code could indicate this by way of a recovery
policy delegate, then this retry can be done at the sendHttpRequest
level, instead of percolating up the call stack all the way to
submitHttpForm, which then has to reparse user data, convert into
JSON, say, and then retry the entire operation all over again.

I'm really liking the Lisp approach.
Now we're talking. Ideas. Outside the box.
[...] Alright. This thread has gone on for too long with lots of talk but no down-to-earth, real code. So I decided to code up a quick-n-dirty proof-of-concept implementation of a Lispian scheme of exception handling. The source files are attached. This implementation is more to demonstrate the *semantics* of such a system, rather than a sample code of the real thing. Before anyone jumps on me for writing very bad code. ;-) In a real implementation, I probably wouldn't inherit Condition from Exception, for example. A real implementation would have language-level support for declaring recovery strategies within the function that handles them, rather than the kludge of declaring them separately and abusing the exception system to handle recovery actions. Also, registered handlers would need to be cleaned up upon scope exit, so that they don't cause crazy side-effects in unrelated code later on in the program. But anyway. The point of this code is to demonstrate: (1) How an exception generated in low-level code is recovered within the low-level code without unwinding the stack all the way back to the top-level caller; (2) How the top-level caller can provide a delegate that makes decisions that only the top-level code is qualified to make, yet does so in the context of low-level code so that recovery can proceed without unwinding the stack any more than necessary. I deliberately made the delegate prompt the user for a fixed filename, even allowing the option of aborting if the user wants to give up. This is to show how the system handles different recovery actions. This, of course, isn't the only way to do things; you can programmatically decide on a recovery strategy instead of prompting the user, for example. What do y'all think? T -- Nobody is perfect. I am Nobody. -- pepoluan, GKC forum
Feb 19 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 06:57, H. S. Teoh a écrit :
 On Sun, Feb 19, 2012 at 09:12:25PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 8:52 PM, H. S. Teoh wrote:
[...]
 Correct, so that would be a recovery strategy at the operation level,
 say at sendHttpRequest or something like that. There is not enough
 information available to sendHttpRequest to know whether or not the
 caller wants the request to be retried if it fails.

 But if the higher-level code could indicate this by way of a recovery
 policy delegate, then this retry can be done at the sendHttpRequest
 level, instead of percolating up the call stack all the way to
 submitHttpForm, which then has to reparse user data, convert into
 JSON, say, and then retry the entire operation all over again.

 I'm really liking the Lisp approach.
Now we're talking. Ideas. Outside the box.
[...] Alright. This thread has gone on for too long with lots of talk but no down-to-earth, real code. So I decided to code up a quick-n-dirty proof-of-concept implementation of a Lispian scheme of exception handling. The source files are attached. This implementation is more to demonstrate the *semantics* of such a system, rather than a sample code of the real thing. Before anyone jumps on me for writing very bad code. ;-) In a real implementation, I probably wouldn't inherit Condition from Exception, for example. A real implementation would have language-level support for declaring recovery strategies within the function that handles them, rather than the kludge of declaring them separately and abusing the exception system to handle recovery actions. Also, registered handlers would need to be cleaned up upon scope exit, so that they don't cause crazy side-effects in unrelated code later on in the program. But anyway. The point of this code is to demonstrate: (1) How an exception generated in low-level code is recovered within the low-level code without unwinding the stack all the way back to the top-level caller; (2) How the top-level caller can provide a delegate that makes decisions that only the top-level code is qualified to make, yet does so in the context of low-level code so that recovery can proceed without unwinding the stack any more than necessary. I deliberately made the delegate prompt the user for a fixed filename, even allowing the option of aborting if the user wants to give up. This is to show how the system handles different recovery actions. This, of course, isn't the only way to do things; you can programmatically decide on a recovery strategy instead of prompting the user, for example. What do y'all think? T
I read your code with great interest ! I'm not sure about this. This is a lot of code to not achieve that much. OK, the stack hasn't been destroyed, but your delegate has its own scope and cannot do a lot. Isn't a retry function provided by phobos and a transeient property as proposed by Andrei a better alternative ?
Feb 20 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 06:37:07PM +0100, deadalnix wrote:
 Le 20/02/2012 06:57, H. S. Teoh a crit :
[...]
Alright. This thread has gone on for too long with lots of talk but
no down-to-earth, real code. So I decided to code up a quick-n-dirty
proof-of-concept implementation of a Lispian scheme of exception
handling. The source files are attached.
[...]
 I read your code with great interest ! I'm not sure about this. This
 is a lot of code to not achieve that much. OK, the stack hasn't been
 destroyed, but your delegate has its own scope and cannot do a lot.
The delegate has full access to the scope of main(), or wherever it was registered from, which the low-level error recovery code has no access to.
 Isn't a retry function provided by phobos and a transeient property as
 proposed by Andrei a better alternative ?
The problem is that by the time you regain control at the catch, the stack has already unwound to main(), so if you need to retry, you need to call openDataFile() all over again. In this case it's not too bad, because openDataFile() is very simple. But imagine if this openDataFile() was nested deeply inside a few layers of functions called from main(), then by the time you unwind the stack to main(), all the previous work done is lost, and you have to restart from the beginning. Also, how does the transient property help main() know that it should prompt the user for a different filename? What if a different exception was caught, like NetworkDriveUnreachable? It wouldn't make sense to prompt the user for another filename in this case. T -- Nearly all men can stand adversity, but if you want to test a man's character, give him power. -- Abraham Lincoln
Feb 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 19:53, H. S. Teoh a crit :
 On Mon, Feb 20, 2012 at 06:37:07PM +0100, deadalnix wrote:
 Le 20/02/2012 06:57, H. S. Teoh a crit :
[...]
 Alright. This thread has gone on for too long with lots of talk but
 no down-to-earth, real code. So I decided to code up a quick-n-dirty
 proof-of-concept implementation of a Lispian scheme of exception
 handling. The source files are attached.
[...]
 I read your code with great interest ! I'm not sure about this. This
 is a lot of code to not achieve that much. OK, the stack hasn't been
 destroyed, but your delegate has its own scope and cannot do a lot.
The delegate has full access to the scope of main(), or wherever it was registered from, which the low-level error recovery code has no access to.
 Isn't a retry function provided by phobos and a transeient property as
 proposed by Andrei a better alternative ?
The problem is that by the time you regain control at the catch, the stack has already unwound to main(), so if you need to retry, you need to call openDataFile() all over again. In this case it's not too bad, because openDataFile() is very simple. But imagine if this openDataFile() was nested deeply inside a few layers of functions called from main(), then by the time you unwind the stack to main(), all the previous work done is lost, and you have to restart from the beginning. Also, how does the transient property help main() know that it should prompt the user for a different filename? What if a different exception was caught, like NetworkDriveUnreachable? It wouldn't make sense to prompt the user for another filename in this case. T
Yes, I see the point. This has clearly some advantages. Considering the handler, This would be great to see it as scoped. This imply using a scope delegate, and probably something as RAII to create it and delete it when we go out of scope. I'm more doubtfull about what can be done on the throw side. This need to be refined, and to be honnest, the idea is too new in my head to come up with something significant. The concept needs some decantation. I wonder however, how to do something clean. The delegate will need some knowledge of the thrower onternal to do something really usefull, but then, we make the codebase more fragile (because the caler will depend on implementation of the calee, something that we want to avoid).
Feb 20 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 08:11:28PM +0100, deadalnix wrote:
[...]
 Considering the handler, This would be great to see it as scoped.
 This imply using a scope delegate, and probably something as RAII to
 create it and delete it when we go out of scope.
Yeah, that definitely should be done, otherwise a handler installed by a function that's no longer in scope will still kick in when the same exception is triggered by unrelated code. That would be very bad.
 I'm more doubtfull about what can be done on the throw side. This need
 to be refined, and to be honnest, the idea is too new in my head to
 come up with something significant. The concept needs some
 decantation.
 
 I wonder however, how to do something clean. The delegate will need
 some knowledge of the thrower onternal to do something really usefull,
 but then, we make the codebase more fragile (because the caler will
 depend on implementation of the calee, something that we want to
 avoid).
I've tried to do this by encapsulating the recovery options in the Condition object. So the delegate knows, here are the options I have of recovering, but it doesn't need to know how the low-level function implements those recovery strategies. For example, the FileNotFoundCond object has a restart() method, and an inherited abort() method. So these are the options available to the delegate. These methods just return an opaque object to the recovery system, which passes it to the low-level function. Then the low-level function tries to recover according to which object it receives. So the delegate doesn't actually know how each option is implemented, it just knows these are available options to recover. Also, the restart() method takes a new filename as parameter, so the delegate even knows what data is needed to provide to a particular recovery option in order to help the low-level function recover. Of course, the current implementation is very skeletal, and there are still many open issues: - How does the delegate know *which* low-level function throws a particular Condition? For example, what if main() calls some function that calls openDataFile() many times? What if openDataFile() is called at different points in the function call hierarchy? How does the delegate know which recovery choice is appropriate for every FileNotFoundCond() that it catches? - Every Condition the program might encounter requires a lot of code: you need to define a Condition subclass, populate it with recovery strategies, and then have the throwing code check and handle each option. Is there a way to reduce the amount of code you need to write for every Condition? - Also, Conditions seem to specific to a particular situation. It's not too bad for generic problems like FileNotFoundCond; we know what it means when the OS says "file not found", and we generally know what can be done to fix that. But what about ParseErrorCond? The delegate would need to know what kind of parse error, or even which parser issued that error, otherwise how would it know how to correct the parse error? But this means that every different parser in the program will require its own ParseError subclass, along with the associated recovery options. Seems like a lot of work for only occasional benefit. - Are there general recovery options that apply generally across the majority of Conditions? If so, we can alleviate delegates from having to know the intimate details of a particular operation, which introduces too much coupling between high-level and low-level code. A more minor issue is that the current implementation is not very well written. :) I'll have to think about this more, to know how to better implement it. But I wanted to get the semantics of it out first, so that at least we can start discussing the conceptual aspects of the idea. T -- They say that "guns don't kill people, people kill people." Well I think the gun helps. If you just stood there and yelled BANG, I don't think you'd kill too many people. -- Eddie Izzard, Dressed to Kill
Feb 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 20:34, H. S. Teoh a crit :
 On Mon, Feb 20, 2012 at 08:11:28PM +0100, deadalnix wrote:
 [...]
 Considering the handler, This would be great to see it as scoped.
 This imply using a scope delegate, and probably something as RAII to
 create it and delete it when we go out of scope.
Yeah, that definitely should be done, otherwise a handler installed by a function that's no longer in scope will still kick in when the same exception is triggered by unrelated code. That would be very bad.
 I'm more doubtfull about what can be done on the throw side. This need
 to be refined, and to be honnest, the idea is too new in my head to
 come up with something significant. The concept needs some
 decantation.

 I wonder however, how to do something clean. The delegate will need
 some knowledge of the thrower onternal to do something really usefull,
 but then, we make the codebase more fragile (because the caler will
 depend on implementation of the calee, something that we want to
 avoid).
I've tried to do this by encapsulating the recovery options in the Condition object. So the delegate knows, here are the options I have of recovering, but it doesn't need to know how the low-level function implements those recovery strategies. For example, the FileNotFoundCond object has a restart() method, and an inherited abort() method. So these are the options available to the delegate. These methods just return an opaque object to the recovery system, which passes it to the low-level function. Then the low-level function tries to recover according to which object it receives. So the delegate doesn't actually know how each option is implemented, it just knows these are available options to recover. Also, the restart() method takes a new filename as parameter, so the delegate even knows what data is needed to provide to a particular recovery option in order to help the low-level function recover. Of course, the current implementation is very skeletal, and there are still many open issues: - How does the delegate know *which* low-level function throws a particular Condition? For example, what if main() calls some function that calls openDataFile() many times? What if openDataFile() is called at different points in the function call hierarchy? How does the delegate know which recovery choice is appropriate for every FileNotFoundCond() that it catches? - Every Condition the program might encounter requires a lot of code: you need to define a Condition subclass, populate it with recovery strategies, and then have the throwing code check and handle each option. Is there a way to reduce the amount of code you need to write for every Condition? - Also, Conditions seem to specific to a particular situation. It's not too bad for generic problems like FileNotFoundCond; we know what it means when the OS says "file not found", and we generally know what can be done to fix that. But what about ParseErrorCond? The delegate would need to know what kind of parse error, or even which parser issued that error, otherwise how would it know how to correct the parse error? But this means that every different parser in the program will require its own ParseError subclass, along with the associated recovery options. Seems like a lot of work for only occasional benefit. - Are there general recovery options that apply generally across the majority of Conditions? If so, we can alleviate delegates from having to know the intimate details of a particular operation, which introduces too much coupling between high-level and low-level code. A more minor issue is that the current implementation is not very well written. :) I'll have to think about this more, to know how to better implement it. But I wanted to get the semantics of it out first, so that at least we can start discussing the conceptual aspects of the idea. T
So if I sum up things a bit (and refactor the design a bit according to what is best as I understand it) : 1/ lib don't throw exception, they raise a Condition. 2/ If an handler exists for that condition, then the handler is called, and the condition is passed as parameter. The condition provide an interface so the handler can specify how to handle that condition. 3/ The condition provide a way to throw in its interface. This part of the interface of Condition. 4/ If no handler is found, the throw method of the Condition is called. 5/ The handler is provided in a scoped manner. 6/ This mecanism is provided as a lib. 7/ The mecanism is provided as a richer way to use Exceptions. Is it a solid base to start on ? I seems we need to start working on std.condition ;) BTW, do we need language support to implement that ? I don't think so, but I'm quite new to the issue.
Feb 20 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 08:53:24PM +0100, deadalnix wrote:
[...]
 So if I sum up things a bit (and refactor the design a bit according
 to what is best as I understand it) :
 
 1/ lib don't throw exception, they raise a Condition.
 
 2/ If an handler exists for that condition, then the handler is
 called, and the condition is passed as parameter. The condition
 provide an interface so the handler can specify how to handle that
 condition.
 
 3/ The condition provide a way to throw in its interface. This part
 of the interface of Condition.
 
 4/ If no handler is found, the throw method of the Condition is called.
 
 5/ The handler is provided in a scoped manner.
Looks good.
 6/ This mecanism is provided as a lib.
Not necessarily; it would be nice to have language support. But at least it's implementable as a lib (as I've shown), so we don't have to invest the time and effort to implement language support until we're sure it's a good idea, after thorough testing with the lib.
 7/ The mecanism is provided as a richer way to use Exceptions.
 
 Is it a solid base to start on ? I seems we need to start working on
 std.condition ;) BTW, do we need language support to implement that ?
 I don't think so, but I'm quite new to the issue.
I'm not sure yet, but probably language support is be needed to reduce the amount of syntax you need to write just to define Conditions. Unless we can figure out a clever way of using templates or mixins or some such to make it easier to write. Ideally, you'd want to declare Conditions inside the catch block of the function, instead of making a separate class definition outside. But I'm not sure how to make such a thing work if the handler needs to know what methods are available in the first place. Perhaps a compromise is to use some templates with mixins to generate code for the Condition subclass and for the custom catch block for handling recovery strategies. Also, another issue that's still unclear to me is, if no handler is found and the Condition gets thrown, does it still make sense to throw the Condition object? Because once the stack unwinds, the recovery methods are probably no longer meaningful. Also, the fact that no handler was found means that no handler will ever be found, so the stack will just unwind to a catch block. And it's doubtful that at that level, the recovery options encoded in the Condition are usable at all. So some parts of this system need some rethinking. T -- Don't modify spaghetti code unless you can eat the consequences.
Feb 20 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
I don't think the Condition should be an Exception. I think it should 
provide a method that throw.

class Condition {
     void throw() pure {           // Or make it abstract
         throw new Exception();
     }
}

If no handler is found, this method is called. The handler can also call it.

It is very important to separate the Condition and the Exception. 
Several Condition may use the same Exception.

BTW, I don't think that something that can be implemented as lib should 
be added in the core language.

Condition may be use using something like raise(new 
MyCondition(params)); with raise from std.condition .

Alternatively, we could go metaprogramming for Condition, just like we 
did for Range.

Le 20/02/2012 21:18, H. S. Teoh a crit :
 6/ This mecanism is provided as a lib.
Not necessarily; it would be nice to have language support. But at least it's implementable as a lib (as I've shown), so we don't have to invest the time and effort to implement language support until we're sure it's a good idea, after thorough testing with the lib.
 7/ The mecanism is provided as a richer way to use Exceptions.

 Is it a solid base to start on ? I seems we need to start working on
 std.condition ;) BTW, do we need language support to implement that ?
 I don't think so, but I'm quite new to the issue.
I'm not sure yet, but probably language support is be needed to reduce the amount of syntax you need to write just to define Conditions. Unless we can figure out a clever way of using templates or mixins or some such to make it easier to write. Ideally, you'd want to declare Conditions inside the catch block of the function, instead of making a separate class definition outside. But I'm not sure how to make such a thing work if the handler needs to know what methods are available in the first place. Perhaps a compromise is to use some templates with mixins to generate code for the Condition subclass and for the custom catch block for handling recovery strategies. Also, another issue that's still unclear to me is, if no handler is found and the Condition gets thrown, does it still make sense to throw the Condition object? Because once the stack unwinds, the recovery methods are probably no longer meaningful. Also, the fact that no handler was found means that no handler will ever be found, so the stack will just unwind to a catch block. And it's doubtful that at that level, the recovery options encoded in the Condition are usable at all. So some parts of this system need some rethinking. T
Feb 20 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 09:45:50PM +0100, deadalnix wrote:
 I don't think the Condition should be an Exception. I think it
 should provide a method that throw.
 
 class Condition {
     void throw() pure {           // Or make it abstract
         throw new Exception();
     }
 }
 
 If no handler is found, this method is called. The handler can also call it.
 
 It is very important to separate the Condition and the Exception.
 Several Condition may use the same Exception.
Good point. Conflating the two will only lead to bad design and confusion later on. Best to keep them separate. So Conditions will be an alternative to Exceptions, and if they are not handled, then revert back to throwing an Exception like before.
 BTW, I don't think that something that can be implemented as lib
 should be added in the core language.
OK. Although if we implement it as a lib, then we'll need to "abuse" the exception throwing mechanism in order to handle error recovery strategies. For example: auto func() { try { retryOperation1: ... raise(new MyCondition); ... retryOperation2: ... raise(new MyOtherCondition); ... } catch(MyConditionRetry e) { goto retryOperation1; } catch(MyOtherConditionRetry e) { goto retryOperation2; } } Alternatively, we can use some mixins that generate goto's, maybe something like this: template BeginRetryBlock(...) {...} template EndRetryBlock(...) {...} template RecoveryBlock(...) {...} template RestartBlock(...) {...} auto func() { BeginRetryBlock!("retryblock1"); int some_parameter; if (!operation1()) raise(new MyCondition); RecoveryBlock!(MyCondition.Strategy1) { RestartBlock!("retryblock1"); } RecoveryBlock!(MyCondition.Strategy2) { fiddleWith(some_parameter); RestartBlock!("retryblock1"); } EndRetryBlock!(); return result; } The templates translate func() into something like this: // generated code auto func() { retryblock1: RecoveryStrategy __rs = void; int some_parameter; if (!operation1()) { __rs = __raise(new MyCondition); goto __recoveryBlock; } goto __success; __recoveryBlock: if (cast(MyCondition.Strategy1) __rs) { goto retryblock1; } if (cast(MyCondition.Strategy2) __rs) { fiddleWith(some_parameter); goto retryblock1; } // No matching strategy, give up throw __currentCondition.getException(); __success: return result; } This way, we don't need to use throw until we actually have to.
 Condition may be use using something like raise(new
 MyCondition(params)); with raise from std.condition .
 
 Alternatively, we could go metaprogramming for Condition, just like we
 did for Range.
[...] I think metaprogramming is the way to go, otherwise this feature will require too much typing to achieve something simple, and people won't like to use it. T -- We are in class, we are supposed to be learning, we have a teacher... Is it too much that I expect him to teach me??? -- RL
Feb 20 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
PS: The more I think of this solution, the more I like it. It is really 
great !

It is more performant, avoid throwing when not necessary, and allow to 
do more. This is definitively something we should dig.
Feb 20 2012
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 02/20/2012 02:53 AM, H. S. Teoh wrote:
 Perhaps the "ideal exception handling facility" that Andrei is looking
 for is a Lispian model,
+1.
Feb 20 2012
prev sibling parent reply Gerrit Wichert <gwichert yahoo.com> writes:
On 19.02.2012 01:40, H. S. Teoh wrote:
 One word: internationalization. Then toString() falls flat on its 
 face. You can't even fix this by having Phobos do the translation 
 internally. You can't expect users of Phobos to only ever support 
 languages that Phobos has been previously translated to, for one 
 thing. That would be a crippling disability. T 
Its even better, i want the exception message to be shown to the user in the current locales language. But for the logs i want it to be in english since i have to maintain systems in other countries we sold our software to. Its easy to connect to a remote system on the other side of the world, but its not so easy to read log files in a foreign language.
Feb 20 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 21:12, Gerrit Wichert a crit :
 On 19.02.2012 01:40, H. S. Teoh wrote:
 One word: internationalization. Then toString() falls flat on its
 face. You can't even fix this by having Phobos do the translation
 internally. You can't expect users of Phobos to only ever support
 languages that Phobos has been previously translated to, for one
 thing. That would be a crippling disability. T
Its even better, i want the exception message to be shown to the user in the current locales language. But for the logs i want it to be in english since i have to maintain systems in other countries we sold our software to. Its easy to connect to a remote system on the other side of the world, but its not so easy to read log files in a foreign language.
grep
Feb 20 2012
prev sibling parent Sean Kelly <sean invisibleduck.org> writes:
Only a few virtuals--mostly for printing the exception to a log. Any specifi=
cs would require catching the exact type.=20

On Feb 18, 2012, at 3:30 PM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.=
org> wrote:

 On 2/18/12 5:20 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 17:13:16 Andrei Alexandrescu wrote:
 On 2/18/12 4:26 PM, Jonathan M Davis wrote (abridged):
 GetOptException
 FlagArgumentMissingException
 InvalidFlagArgumentException
 UnknownFlagException
 FileException
 FileNotFoundException
 NotFileException
 NotDirException
 AccessDeniedException
=20
 I died inside a little.
=20 If you actually want to _handle_ exceptions, how else do you expect to do=
it?
 Simply put a code of some kind on the exceptions and then have switch
 statement to handle them?
=20 The alternative is with virtuals. Do you see a lot of virtuals in base exc=
eptions? Do you see dramatically different interface for different exception= types?
=20
=20
 Andrei
=20
=20
Feb 19 2012
prev sibling next sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Sat, Feb 18, 2012 at 9:13 PM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> 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.
Yeah. This gets even worse by the fact that some times you have to import vendor specific modules with their own set of exceptions. For example the JDBC exception model was just a nightmare. Depending on which database driver you used you could have a completely different set of exceptions you had to catch. The Spring Framework solved this problem by just throwing just one exception: SQLException (if my memory serves me right). The developer then had to switch based on the driver type and the error code. I don't think this is perfect but I think it works much better in practice. The other nice thing about this model is that it is easier to know which error you want to handle. Database vendor document their errors using error code not language specific exception. If the Oracle manual says that the database gives error XXXX for such and such condition how in the world do you know to which JDBC exception does that match or D exception, etc.? I'll be the first to suggestion something. One possible solution is to have one exception and inside have the concept of a namespace and a error code. Assuming that D had pattern matching you could do: try { errorProneCode(); } catch (Exception(IONamespace, errorCode)) { // errorCode is a variable with the error and the exception must match the value in IONamespace } catch (Exception(DBNamespace, AccessDenied) { // matches the db namespace and the access denied error code } Destroy. Thanks, -Jose
 Andrei
Feb 18 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 05:13:16PM -0600, 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.
[...] That's because you're missing the hierarchy, which may look more like this: Error +--Exception +--GetOptException (bad name, I prefer CommandLineException) | +--FlagArgumentMissingException | +--InvalidFlagArgumentException | +--UnknownFlagException +--FileException | +--FileNotFoundException | +--NotFileException | +--NotDirException | +--AccessDeniedException +--IOException (OK, I added this branch, just to show the idea) +--ReadErrorException +--WriteErrorException So if all you care about is to handle command-line option parse errors, and don't care which error it is, you could just catch GetOptException. If all you care about is file access exceptions, you can just catch FileException without having to worry which specific exception it is. Alternatively, if you don't care which exception it is at all, then just catch Exception. That's the whole point of a (well-designed) exception hierarchy. It lets you be as generic or as specific as you want. Note also, that an elaborated exception hierarchy lets you attach additional specific information that might be useful in error recovery. For example, FileException may have a field containing the filename that caused the error, so that if the program is processing a list of filenames, it knows which one went wrong. You would not want such information in Exception, because it only applies to FileException's. Similarly, GetOptException (or CommandLineException) may have additional fields to indicate which option is malformed. Such information, obviously, shouldn't be in other kinds of exceptions but those that inherit GetOptException. T -- Which is worse: ignorance or apathy? Who knows? Who cares? -- Erich Schubert
Feb 18 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 01:36, H. S. Teoh a crit :
 On Sat, Feb 18, 2012 at 05:13:16PM -0600, 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.
[...] That's because you're missing the hierarchy, which may look more like this: Error +--Exception +--GetOptException (bad name, I prefer CommandLineException) | +--FlagArgumentMissingException | +--InvalidFlagArgumentException | +--UnknownFlagException +--FileException | +--FileNotFoundException | +--NotFileException | +--NotDirException | +--AccessDeniedException +--IOException (OK, I added this branch, just to show the idea) +--ReadErrorException +--WriteErrorException So if all you care about is to handle command-line option parse errors, and don't care which error it is, you could just catch GetOptException. If all you care about is file access exceptions, you can just catch FileException without having to worry which specific exception it is. Alternatively, if you don't care which exception it is at all, then just catch Exception. That's the whole point of a (well-designed) exception hierarchy. It lets you be as generic or as specific as you want. Note also, that an elaborated exception hierarchy lets you attach additional specific information that might be useful in error recovery. For example, FileException may have a field containing the filename that caused the error, so that if the program is processing a list of filenames, it knows which one went wrong. You would not want such information in Exception, because it only applies to FileException's. Similarly, GetOptException (or CommandLineException) may have additional fields to indicate which option is malformed. Such information, obviously, shouldn't be in other kinds of exceptions but those that inherit GetOptException. T
I hope somebody will listen to you !
Feb 18 2012
prev sibling next sibling parent bearophile <bearophileHUGS lycos.com> writes:
H. S. Teoh:

 Error
  +--Exception
      +--GetOptException (bad name, I prefer CommandLineException)
      |   +--FlagArgumentMissingException
      |   +--InvalidFlagArgumentException
      |   +--UnknownFlagException
      +--FileException
      |   +--FileNotFoundException
      |   +--NotFileException
      |   +--NotDirException
      |   +--AccessDeniedException
      +--IOException (OK, I added this branch, just to show the idea)
          +--ReadErrorException
 	 +--WriteErrorException
In Python 3.2 the class hierarchy for built-in exceptions is: BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EnvironmentError | +-- IOError | +-- OSError | +-- WindowsError (Windows) | +-- VMSError (VMS) +-- EOFError +-- ImportError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning Bye, bearophile
Feb 18 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 6:36 PM, H. S. Teoh wrote:
 Note also, that an elaborated exception hierarchy lets you attach
 additional specific information that might be useful in error recovery.
 For example, FileException may have a field containing the filename that
 caused the error, so that if the program is processing a list of
 filenames, it knows which one went wrong. You would not want such
 information in Exception, because it only applies to FileException's.
If an exception adds state and/or interface to the table, I agree that may justify its existence as a distinct type. Andrei
Feb 18 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 20:28:32 Andrei Alexandrescu wrote:
 On 2/18/12 6:36 PM, H. S. Teoh wrote:
 Note also, that an elaborated exception hierarchy lets you attach
 additional specific information that might be useful in error recovery.
 For example, FileException may have a field containing the filename that
 caused the error, so that if the program is processing a list of
 filenames, it knows which one went wrong. You would not want such
 information in Exception, because it only applies to FileException's.
If an exception adds state and/or interface to the table, I agree that may justify its existence as a distinct type.
If all you're arguing is that something like StringException shouldn't exist because it doesn't add any additional member fields, then that's a much more reasonable thing to debate. I'm not quite sure I agree, but some of your responses seem to indicate that you don't want a rich exception hierarchy. In the case of getopt, at _least_ adding a GetOptException with a field for the failed flag would be very valuable, and having additional derived types which indicate _why_ it failed would also be valuable. And in some cases at least, that would lead to more member fields in the derived exceptions (like the value of the flag if it were given a bad value). I would argue however that there _are_ times when having a derived exception which has no additional data beyond its type rather than simply Exception can be useful - _especially_ when it's at the base of a larger hierarchy. For instance, if we had an IOException, you could know that whatever operation you were trying to do went badly because of an IO problem. You would probably need to handle it as a subclass of IOException to get any particularly useful information on how to handle the problem, but just knowing that it was an I/O problem could be enough in some cases. I really think that whether adding an exception type which adds no additional member fields is a good idea should be evaluated on a case-by-case basis. We don't want to needlessly create a bunch of useless, uninformative exception types, but be we also want a rich enough exception hierarchy that it's possible to intelligently recover from exceptions. - Jonathan M Davis
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 8:45 PM, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 20:28:32 Andrei Alexandrescu wrote:
 On 2/18/12 6:36 PM, H. S. Teoh wrote:
 Note also, that an elaborated exception hierarchy lets you attach
 additional specific information that might be useful in error recovery.
 For example, FileException may have a field containing the filename that
 caused the error, so that if the program is processing a list of
 filenames, it knows which one went wrong. You would not want such
 information in Exception, because it only applies to FileException's.
If an exception adds state and/or interface to the table, I agree that may justify its existence as a distinct type.
If all you're arguing is that something like StringException shouldn't exist because it doesn't add any additional member fields, then that's a much more reasonable thing to debate. I'm not quite sure I agree, but some of your responses seem to indicate that you don't want a rich exception hierarchy.
Ideally we'd have the right number of types - not more, not less. The main purpose of this thread is to figure how many exception types are "just right". There has been a tendency in Phobos to just add a module-specific exception to certain modules, without a good reason. I'm trying to figure out the reasons.
 In the case of getopt, at _least_ adding a GetOptException with a field for the
 failed flag would be very valuable, and having additional derived types which
 indicate _why_ it failed would also be valuable.
The additional state sounds fine, but I'm not so sure about adding one type per failure reason.
 And in some cases at least,
 that would lead to more member fields in the derived exceptions (like the value
 of the flag if it were given a bad value).
And aside from that? The universe of possible errors in getopt is finite, closed, and actually fairly small. Can't we do with fewer types?
 I would argue however that there _are_ times when having a derived exception
 which has no additional data beyond its type rather than simply Exception can
 be useful - _especially_ when it's at the base of a larger hierarchy. For
 instance, if we had an IOException, you could know that whatever operation you
 were trying to do went badly because of an IO problem. You would probably need
 to handle it as a subclass of IOException to get any particularly useful
 information on how to handle the problem, but just knowing that it was an I/O
 problem could be enough in some cases.
How about a system in which you can say whether an exception is I/O related, network related, recoverable or not, should be displayed to the user or not, etc. Such is difficult to represent with inheritance alone.
 I really think that whether adding an exception type which adds no additional
 member fields is a good idea should be evaluated on a case-by-case basis. We
 don't want to needlessly create a bunch of useless, uninformative exception
 types, but be we also want a rich enough exception hierarchy that it's
 possible to intelligently recover from exceptions.
I think right now we're erring a bit on the side of defining useless and uninformative exception types. As far as the originating module goes, it makes perfect sense to make that a field in the base class. Anyway, a simple action items right now is improve Exception's offering with things like e.g. custom formatting for i18n, more origin information, cloning, attributes and capabilities (transitory, user-visible etc). Andrei
Feb 18 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 01:05:25AM -0600, Andrei Alexandrescu wrote:
[...]
 Ideally we'd have the right number of types - not more, not less.  The
 main purpose of this thread is to figure how many exception types are
 "just right". There has been a tendency in Phobos to just add a
 module-specific exception to certain modules, without a good reason.
 I'm trying to figure out the reasons.
As a first stab at some reasons, what about something along these lines: - Would the *user* of the module care to catch that particular (group of) exception(s) as opposed to just catching all exceptions in general? Does that exception give semantically valuable information that the user might want to act on, or it is just programmer convenience (it throws X just because it belongs to module Y)? - Does it add domain-specific error information that is useful (errno, Oracle error codes, etc.)?
In the case of getopt, at _least_ adding a GetOptException with a
field for the failed flag would be very valuable, and having
additional derived types which indicate _why_ it failed would also be
valuable.
The additional state sounds fine, but I'm not so sure about adding one type per failure reason.
When to stop introducing leaf nodes in the hierarchy is something worth thinking about in more depth. There are some pros and cons to consider. To make the discussion grounded in reality, let's consider the example of file I/O. We could either (1) introduce a base class, say IOException, along with as many subclasses as necessary to capture all possible I/O errors (with possibly multiple layers of exceptions); or (2) introduce a base class IOException with no subclasses, but some attributes like errno or something equivalent, that describes the error. Or (3), some compromise between (1) and (2). For (1): - Pros: - You can be as general or as specific as needed. If you want to catch a specific error, you can. If you want to catch all I/O exceptions, you can. - Encapsulates OS and platform-specific errors in generic cross platform types that can be handled generically. (I.e., you don't have to know that std.io uses errno in interacting with the OS, and you don't have to know what errno values are. A generic FileNotFoundException properly maps to whatever OS you compile on.) - Cons: - Too many tiny classes, many differing only in type with no real additional information beyond the type itself. - Does not necessarily capture everything the user may want to catch. Say under IOException you have FileException and SocketException, with more classes underneath. If you want to catch a subset of classes directly under FileException, there's no clean way to do so unless we expand the type-matching in catch() blocks. - May require a lot of effort to implement but only with diminishing returns (most programs won't bother with the distinction between two finely-differentiated exceptions). For (2): - Pros: - No class bloat. - Don't have to wade through reams of documentation to know what to catch. Just catch IOException and decide what to do based on the data encoded (say, errno value, or equivalent). - Cons: - Risks exposing OS-specific and implementation-specific details to caller unnecessarily. E.g., if you use errno, then caller is tied to C's stdio. Then you can't change the underlying implementation to, say, iostream, or something else altogether, without massive breakage. - To only catch a specific error, you'd have to catch IOException, check the error code, and rethrow if it isn't what you want. Ugly. (It's like saying, oh, I can handle all IOExceptions, oh here's an IOException! Er, nevermind, I was kidding, I don't handle this particular IOException after all.) I don't have the answers. Just want to put the pros and cons down so that we have something concrete to debate about.
And in some cases at least, that would lead to more member fields in
the derived exceptions (like the value of the flag if it were given a
bad value).
And aside from that? The universe of possible errors in getopt is finite, closed, and actually fairly small. Can't we do with fewer types?
Getopt isn't really the best use case for elaborate exception hierarchies. Perhaps we should use file I/O or network/socket I/O instead. [...]
 How about a system in which you can say whether an exception is I/O
 related, network related, recoverable or not, should be displayed to
 the user or not, etc. Such is difficult to represent with inheritance
 alone.
The problem is, the thrower can't make the decision of whether or not something should be displayed to the user. You simply don't have the information nor context to decide that. That is completely dependent on what the calling app is trying to accomplish. A disk diagnostic program will want to display everything to the user, all the full gory details. A 3D shooter just wants to say "yo, something's wrong with da filesystem, I be quittin'!" and abort. Displaying some obscure error about some obscure problem with some obscure data file is worthless to the player. A database server backend has no *way* to display any error at all, just log it to some logfile. And server logfiles generally want to record ALL exceptions, not just some subset arbitrarily assigned by Phobos developers. Same thing with whether something is recoverable. The most horrendous I/O errors are "recoverable" from the POV of a disk repair program. The same errors should cause a word-processing app to die horribly instead of blindly charging forward and causing more data loss. You simply have no way of deciding this at the library level. As for marking whether an exception is network-related: you're just bloating the base class with mostly-irrelevant information. Why does every file I/O exception have to indicate they are not network errors? Why does every parser error have to say they are not network errors? Lexer errors? Regex errors? Database file corruption errors? The whole point of using a *class hierarchy* is so that all network errors derive from NetworkException, which in turn derives from Exception. The value of is_network_related is automatically, cleanly, and logically encapsulated by the act of defining a subclass of Exception. You don't need to explicitly represent it. It's implied by the existence of the subtype (which translates to a kind of representation in the form of the associated Typeinfo, if you'd like to think of it that way). [...]
 I think right now we're erring a bit on the side of defining useless
 and uninformative exception types. As far as the originating module
 goes, it makes perfect sense to make that a field in the base class.
But "useless" and "uninformative" is relative to what the *caller* wants to achieve. The difference between FileNotFound and ReadError is irrelevant to a program that's trying to load startup configuration files. But a program that's trying to decide whether or not to prompt the user for a different filename needs to know this distinction. The *same* program won't care for this distinction when *it* is trying to load configuration files. Basically, there's no sane way you can decide this for the calling app at the library level.
 Anyway, a simple action items right now is improve Exception's
 offering with things like e.g. custom formatting for i18n, more origin
 information, cloning, attributes and capabilities (transitory,
 user-visible etc).
[...] But putting too much into the Exception base class risks having a whole bunch of fields that nobody uses. Why should you need to specify 10 different attributes, most of which you don't care about, every time you throw an exception? Why not only introduce additional fields as necessary? I.e., use a class hierarchy! That's what a class hierarchy is for, to keep the base class providing only *basic* functionality, and derived classes to add additional info for those subsets of exceptions that need that info. Again, I'm not saying that an elaborated exception hierarchy is the be all and end all of exception handling. It's just that I have yet to see a better system that provides those attributes I listed in another reply: the ability to be as general/specific as needed, and a way to programmatically recover from an exception based on information provided by the exception object (which entails domain-specific errors encoded in a sane way). If you can come up with a better system, by all means, please do. I'm listening. T -- If you look at a thing nine hundred and ninety-nine times, you are perfectly safe; if you look at it the thousandth time, you are in frightful danger of seeing it for the first time. -- G. K. Chesterton
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 2:21 AM, H. S. Teoh wrote:
 Getopt isn't really the best use case for elaborate exception
 hierarchies. Perhaps we should use file I/O or network/socket I/O
 instead.
This is exactly what we should avoid: regressing to well-trodden ground. We shouldn't discuss OOP only in terms of Animal and Mammal, and we shouldn't discuss exceptions only in terms of IOException and NetworkException. We should look at the "uncomfortable" cases. Andrei
Feb 19 2012
parent dennis luehring <dl.soluz gmx.net> writes:
Am 19.02.2012 17:27, schrieb Andrei Alexandrescu:
 On 2/19/12 2:21 AM, H. S. Teoh wrote:
  Getopt isn't really the best use case for elaborate exception
  hierarchies. Perhaps we should use file I/O or network/socket I/O
  instead.
This is exactly what we should avoid: regressing to well-trodden ground. We shouldn't discuss OOP only in terms of Animal and Mammal, and we shouldn't discuss exceptions only in terms of IOException and NetworkException. We should look at the "uncomfortable" cases. Andrei
so there is a need to start with collecting the uncomfortable cases - that would ease the complete discussion a lot
Feb 19 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 08:05, Andrei Alexandrescu a écrit :
 How about a system in which you can say whether an exception is I/O
 related, network related, recoverable or not, should be displayed to the
 user or not, etc. Such is difficult to represent with inheritance alone.
That may sound great on the paper, but it isn't. The fact that an exception is recoverable or not depend often on your program and not on the cause of the exception. The piece of code throwing the exception have no clue if you can recover from that or not. Exception that cannot be recovered for sure already exists in D. They are called errors. Additionnaly, you'll find recoverable exception with different recovery point in the program.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 5:22 AM, deadalnix wrote:
 Le 19/02/2012 08:05, Andrei Alexandrescu a écrit :
 How about a system in which you can say whether an exception is I/O
 related, network related, recoverable or not, should be displayed to the
 user or not, etc. Such is difficult to represent with inheritance alone.
That may sound great on the paper, but it isn't. The fact that an exception is recoverable or not depend often on your program and not on the cause of the exception.
This is self-evident. Again, the meaning of "recoverable" is "operation may succeed if retried with the same input". It's a hint for the catch code. Of course the program is free to ignore that aspect, retry a number of times, log, display user feedback, and so on. But as far as definition goes the notion is cut and dried. Andrei
Feb 19 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhr0vq$24t0$1 digitalmars.com...
 On 2/19/12 5:22 AM, deadalnix wrote:
 Le 19/02/2012 08:05, Andrei Alexandrescu a crit :
 How about a system in which you can say whether an exception is I/O
 related, network related, recoverable or not, should be displayed to the
 user or not, etc. Such is difficult to represent with inheritance alone.
That may sound great on the paper, but it isn't. The fact that an exception is recoverable or not depend often on your program and not on the cause of the exception.
This is self-evident. Again, the meaning of "recoverable" is "operation may succeed if retried with the same input". It's a hint for the catch code. Of course the program is free to ignore that aspect, retry a number of times, log, display user feedback, and so on. But as far as definition goes the notion is cut and dried.
WTF? "Recoverable" means "can be recovered from". Period. The term doesn't have a damn thing to do with "how", even in the context of exceptions. It *never* has. If you meant it as "operation may succeed if retried with the same input", then fine, but don't pretend that *your* arbitrary definition is "cut and dried".
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 9:56 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhr0vq$24t0$1 digitalmars.com...
 On 2/19/12 5:22 AM, deadalnix wrote:
 Le 19/02/2012 08:05, Andrei Alexandrescu a crit :
 How about a system in which you can say whether an exception is I/O
 related, network related, recoverable or not, should be displayed to the
 user or not, etc. Such is difficult to represent with inheritance alone.
That may sound great on the paper, but it isn't. The fact that an exception is recoverable or not depend often on your program and not on the cause of the exception.
This is self-evident. Again, the meaning of "recoverable" is "operation may succeed if retried with the same input". It's a hint for the catch code. Of course the program is free to ignore that aspect, retry a number of times, log, display user feedback, and so on. But as far as definition goes the notion is cut and dried.
WTF? "Recoverable" means "can be recovered from". Period. The term doesn't have a damn thing to do with "how", even in the context of exceptions. It *never* has. If you meant it as "operation may succeed if retried with the same input", then fine, but don't pretend that *your* arbitrary definition is "cut and dried".
I think it's a reasonable definition of "can be recovered from" in the context of exceptions. Andrei
Feb 19 2012
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-19 17:29, Andrei Alexandrescu wrote:
 On 2/19/12 9:56 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org> wrote in message
 news:jhr0vq$24t0$1 digitalmars.com...
 On 2/19/12 5:22 AM, deadalnix wrote:
 Le 19/02/2012 08:05, Andrei Alexandrescu a crit :
 How about a system in which you can say whether an exception is I/O
 related, network related, recoverable or not, should be displayed
 to the
 user or not, etc. Such is difficult to represent with inheritance
 alone.
That may sound great on the paper, but it isn't. The fact that an exception is recoverable or not depend often on your program and not on the cause of the exception.
This is self-evident. Again, the meaning of "recoverable" is "operation may succeed if retried with the same input". It's a hint for the catch code. Of course the program is free to ignore that aspect, retry a number of times, log, display user feedback, and so on. But as far as definition goes the notion is cut and dried.
WTF? "Recoverable" means "can be recovered from". Period. The term doesn't have a damn thing to do with "how", even in the context of exceptions. It *never* has. If you meant it as "operation may succeed if retried with the same input", then fine, but don't pretend that *your* arbitrary definition is "cut and dried".
I think it's a reasonable definition of "can be recovered from" in the context of exceptions.
No it's not. Say you want to save a file. You're saving it in "/" which you don't have permission to write to. The system throws an exception PermissionException, this is a recoverable exception since the application could prompt the user to save the file in a different location. -- /Jacob Carlborg
Feb 19 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 10:43 AM, Jacob Carlborg wrote:
 No it's not. Say you want to save a file. You're saving it in "/" which
 you don't have permission to write to. The system throws an exception
 PermissionException, this is a recoverable exception since the
 application could prompt the user to save the file in a different location.
That would be a retry with different state. I agree you could define transiency/recoverability differently, but I think my definition is more useful. This is because it provides a notion of transiency that can be handled regardless of the actual problem. Andrei
Feb 19 2012
prev sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhr81v$2i3r$3 digitalmars.com...
 On 2/19/12 9:56 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhr0vq$24t0$1 digitalmars.com...
 This is self-evident. Again, the meaning of "recoverable" is "operation
 may succeed if retried with the same input". It's a hint for the catch
 code. Of course the program is free to ignore that aspect, retry a 
 number
 of times, log, display user feedback, and so on. But as far as 
 definition
 goes the notion is cut and dried.
WTF? "Recoverable" means "can be recovered from". Period. The term doesn't have a damn thing to do with "how", even in the context of exceptions. It *never* has. If you meant it as "operation may succeed if retried with the same input", then fine, but don't pretend that *your* arbitrary definition is "cut and dried".
I think it's a reasonable definition of "can be recovered from" in the context of exceptions.
Reasonable maybe, but not obvious. That's all I'm trying to say.
Feb 19 2012
parent reply <address_is invalid.invalid> writes:
"Nick Sabalausky" <a a.a> wrote:
 "Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
 news:jhr81v$2i3r$3 digitalmars.com...
 On 2/19/12 9:56 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhr0vq$24t0$1 digitalmars.com...
 
 This is self-evident. Again, the meaning of "recoverable" is "operation
 may succeed if retried with the same input". It's a hint for the catch
 code. Of course the program is free to ignore that aspect, retry a 
 number
 of times, log, display user feedback, and so on. But as far as 
 definition
 goes the notion is cut and dried.
 
WTF? "Recoverable" means "can be recovered from". Period. The term doesn't have a damn thing to do with "how", even in the context of exceptions. It *never* has. If you meant it as "operation may succeed if retried with the same input", then fine, but don't pretend that *your* arbitrary definition is "cut and dried".
I think it's a reasonable definition of "can be recovered from" in the context of exceptions.
Reasonable maybe, but not obvious. That's all I'm trying to say.
I guess "transient" is more descriptive. Andrei
Feb 19 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 19 2012
prev sibling next sibling parent Jim Hewes <jimhewes gmail.com> writes:
On 2/19/2012 10:48 AM, address_is invalid.invalid wrote:

 I guess "transient" is more descriptive.

 Andrei
I suppose “transient” mingles with recoverability and may get confused with it. But the interrelated issue that comes to mind for me is whether a “failure” is a common and typical result of the particular operation or not. I'm thinking of the example of acquiring a mutex. Because the nature of a mutex is to prevent two parties from using a resource at the same time, getting a result of “mutex already in use” is a normal and expected result and probably should not throw an exception. It's transient in that you can try again and it might work. But this behavior is already built in because you can provide a timeout argument which means, “keep trying until this much time elapses, then give up”. Perhaps a network connection would be different because when you send a packet, you expect it to get where it's going. That would be normal program flow. Otherwise it's an error and an exception. Jim
Feb 19 2012
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 18:48:02 address_is invalid.invalid wrote:
 I guess "transient" is more descriptive.
Actually, thinking on it some more, I don't think that transient will work at all, and the reason is simple. _Which_ operation should you retry? You don't even necessarily know which function the exception came from out of the functions that you called within the try block - let alone which function actually threw the exception. Maybe it was thrown 3 functions deep from the function that you called, and while retrying that specific call 3 functions down might have made sense, retrying the function 3 functions up doesn't necessarily make sense at all. Whether or not you can retry or retrying makes any sense at all is _highly_ dependent on who actually catches the exception. In many cases, it may be a function which could retry it, but in many it won't be, and so having the exception tell the caller that it could retry would just be misleading. - Jonathan M Davis
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 5:28 PM, Jonathan M Davis wrote:
 On Sunday, February 19, 2012 18:48:02 address_is invalid.invalid wrote:
 I guess "transient" is more descriptive.
Actually, thinking on it some more, I don't think that transient will work at all, and the reason is simple. _Which_ operation should you retry?
The application decides.
 You don't
 even necessarily know which function the exception came from out of the
 functions that you called within the try block - let alone which function
 actually threw the exception. Maybe it was thrown 3 functions deep from the
 function that you called, and while retrying that specific call 3 functions
 down might have made sense, retrying the function 3 functions up doesn't
 necessarily make sense at all.

 Whether or not you can retry or retrying makes any sense at all is _highly_
 dependent on who actually catches the exception. In many cases, it may be a
 function which could retry it, but in many it won't be, and so having the
 exception tell the caller that it could retry would just be misleading.
No dependence on context. The bit simply tells you "operation has failed, but due to a transitory matter". That is information local to the thrower. Andrei
Feb 19 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 05:38:23PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 5:28 PM, Jonathan M Davis wrote:
On Sunday, February 19, 2012 18:48:02 address_is invalid.invalid wrote:
I guess "transient" is more descriptive.
Actually, thinking on it some more, I don't think that transient will work at all, and the reason is simple. _Which_ operation should you retry?
The application decides.
You don't even necessarily know which function the exception came
from out of the functions that you called within the try block - let
alone which function actually threw the exception. Maybe it was
thrown 3 functions deep from the function that you called, and while
retrying that specific call 3 functions down might have made sense,
retrying the function 3 functions up doesn't necessarily make sense
at all.

Whether or not you can retry or retrying makes any sense at all is
_highly_ dependent on who actually catches the exception. In many
cases, it may be a function which could retry it, but in many it
won't be, and so having the exception tell the caller that it could
retry would just be misleading.
No dependence on context. The bit simply tells you "operation has failed, but due to a transitory matter". That is information local to the thrower.
[...] But *which* transitory matter? A temporary outage on the network? A timeout due to excessive CPU load? A full disk (which is transitory because some other process might remove a large file in the interim)? Without knowing the context, this information is of little use. I'm really starting to like the Lisp system more, the more I think about this. Let the low-level code provide a list of recovery strategies, and let the high-level code register recovery policies that select between these recovery strategies *in the context of the low-level code*. The runtime matches policy to strategy, and the stack is only unwound when no recovery is possible. T -- Frank disagreement binds closer than feigned agreement.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 7:58 PM, H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 05:38:23PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 5:28 PM, Jonathan M Davis wrote:
 On Sunday, February 19, 2012 18:48:02 address_is invalid.invalid wrote:
 I guess "transient" is more descriptive.
Actually, thinking on it some more, I don't think that transient will work at all, and the reason is simple. _Which_ operation should you retry?
The application decides.
 You don't even necessarily know which function the exception came
from out of the functions that you called within the try block - let
 alone which function actually threw the exception. Maybe it was
 thrown 3 functions deep from the function that you called, and while
 retrying that specific call 3 functions down might have made sense,
 retrying the function 3 functions up doesn't necessarily make sense
 at all.

 Whether or not you can retry or retrying makes any sense at all is
 _highly_ dependent on who actually catches the exception. In many
 cases, it may be a function which could retry it, but in many it
 won't be, and so having the exception tell the caller that it could
 retry would just be misleading.
No dependence on context. The bit simply tells you "operation has failed, but due to a transitory matter". That is information local to the thrower.
[...] But *which* transitory matter? A temporary outage on the network? A timeout due to excessive CPU load? A full disk (which is transitory because some other process might remove a large file in the interim)?
Doesn't matter.
 Without knowing the context, this information is of little use.
It is. User code may decide to retry the high-level operation (of which the low-level failure has no knowledge). Andrei
Feb 19 2012
next sibling parent James Miller <james aatch.net> writes:
I agree that the "Lispian" model works well, though I had issues
trying to get my head around it when I encountered it.

I don't know how you'd make a simpler version for D (D lacking Lisps
ridiculous macros) but maybe something that essentially "returns" a
list of recovery codes (which would unfortunately have to be
documented) that can be called depending on the context of the error.

But error-handling is hard, programmers are naturally lazy, and
checking errors is not something exiting. Exceptions are always going
to be a source of contention amongst people. I know people (mostly C
programmers) that hate them, and other people swear by them. I agree
that incorrect parameters should not be Exceptions, and are
contract-level issues, check your parameters before passing them if
there are conditions on them!

its a difficult topic, hence the ridiculously long thread we have
going here. Error codes are not really the way to go, I prefer more of
an Objective-C/Smalltalk null-pattern style, where null can be a valid
argument almost anywhere the type system allows and code handles it
properly, normally by returning null.

Exceptions are a source of contention due to long-range dependencies,
but you can apply that to many things, and if you treat exceptions as
part of the API, then changing an exception can be considered an API
change and therefore something to be done with care.

James Miller
Feb 19 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 08:34:30PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 7:58 PM, H. S. Teoh wrote:
On Sun, Feb 19, 2012 at 05:38:23PM -0600, Andrei Alexandrescu wrote:
[...]
No dependence on context. The bit simply tells you "operation has
failed, but due to a transitory matter". That is information local
to the thrower.
[...] But *which* transitory matter? A temporary outage on the network? A timeout due to excessive CPU load? A full disk (which is transitory because some other process might remove a large file in the interim)?
Doesn't matter.
Without knowing the context, this information is of little use.
It is. User code may decide to retry the high-level operation (of which the low-level failure has no knowledge).
[...] But on what basis will it make this decision? All it knows is that something went wrong somewhere deep in the call stack, and it's presented with a binary choice: retry or abort. It doesn't know what that problem was. So how would it know if retrying would help? Saying that it "might" help doesn't seem useful to me. It's only useful if you present this choice to the *user* along with the error message encapsulated in the exception, and let the user make the decision on the basis of the error message. I think we're all agreed that parsing the error message is not a viable solution, so basically the catch block is presented with a blind binary choice of which it knows nothing about. Such a choice is meaningless to a computer program. Do you have a concrete scenario in mind where such a decision would actually be useful? Otherwise we'll just end up with boilerplate code copy-n-pasted everywhere of the form: auto retries = SomeArbitraryNumber; do { try { ... } catch(Exception e) { if (e.is_transient && retries-- > 0) continue; throw e; } } while(false); But since this block is completely independent of what's inside the try block, why not just put it where the exception is generated in the first place? T -- If the comments and the code disagree, it's likely that *both* are wrong. -- Christopher
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 9:06 PM, H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 08:34:30PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 7:58 PM, H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 05:38:23PM -0600, Andrei Alexandrescu wrote:
[...]
 No dependence on context. The bit simply tells you "operation has
 failed, but due to a transitory matter". That is information local
 to the thrower.
[...] But *which* transitory matter? A temporary outage on the network? A timeout due to excessive CPU load? A full disk (which is transitory because some other process might remove a large file in the interim)?
Doesn't matter.
 Without knowing the context, this information is of little use.
It is. User code may decide to retry the high-level operation (of which the low-level failure has no knowledge).
[...] But on what basis will it make this decision? All it knows is that something went wrong somewhere deep in the call stack, and it's presented with a binary choice: retry or abort.
No. The information is: "There's been failure, but due to a transitory cause." In a high-level transaction, it doesn't matter which particular step failed. If whatever failure was transitory, the transaction can be attempted again.
 It doesn't know what
 that problem was.
Doesn't have to.
 So how would it know if retrying would help? Saying
 that it "might" help doesn't seem useful to me.
Retrying helps because the error happened because of a temporary cause. That info is known at the raise place, and nicely passed up to the high-level command.
 It's only useful if you present this choice to the *user* along with the
 error message encapsulated in the exception, and let the user make the
 decision on the basis of the error message.
That's up to the application. A server application, for example, can't ask the user. We log and we have configurable number of retries.
 I think we're all agreed that parsing the error message is not a viable
 solution, so basically the catch block is presented with a blind binary
 choice of which it knows nothing about.  Such a choice is meaningless to
 a computer program.

 Do you have a concrete scenario in mind where such a decision would
 actually be useful? Otherwise we'll just end up with boilerplate code
 copy-n-pasted everywhere of the form:

 	auto retries = SomeArbitraryNumber;
 	do {
 		try {
 			...
 		} catch(Exception e) {
 			if (e.is_transient&&  retries-->  0)
 				continue;
 			throw e;
 		}
 	} while(false);

 But since this block is completely independent of what's inside the try
 block, why not just put it where the exception is generated in the first
 place?
I explained this. The raise locus does not have access to the high-level context. Andrei
Feb 19 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 09:17:23PM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 9:06 PM, H. S. Teoh wrote:
[...]
Do you have a concrete scenario in mind where such a decision would
actually be useful? Otherwise we'll just end up with boilerplate code
copy-n-pasted everywhere of the form:

	auto retries = SomeArbitraryNumber;
	do {
		try {
			...
		} catch(Exception e) {
			if (e.is_transient&&  retries-->  0)
				continue;
			throw e;
		}
	} while(false);

But since this block is completely independent of what's inside the
try block, why not just put it where the exception is generated in
the first place?
I explained this. The raise locus does not have access to the high-level context.
[...] This is why I'm liking the Lisp model more and more. The lower level code knows best what recovery strategies are available. But it can't make that decision at that level. So you need the higher level code to make this decision. But if you simply unwind the stack all the way back up to the higher level code, then you've lost the opportunity of reusing the execution context of the lower level code to perform the recovery. You have to start from scratch. You also have no other recovery strategies available, since the original execution context is gone. The only options left are retry or abort. By having the high-level code register a delegate which can make this sorts of decisions, when the low-level code *does* encounter a problem it will be able to perform the chosen recovery procedure immediately, rather than throwing away the entire execution context and perhaps having to reestablish all of it again later. This doesn't mean the low-level network socket that encounters the problem will be able to run the recovery right there; you do have to unwind the stack to some point where intelligent recovery is possible. But the point is that this is often lower down in the call stack than the code which is qualified to make decisions about which recovery strategy should be used. That's why you want to encapsulate the code that makes this decision in a delegate which is run at the lower-level code where intelligent recovery takes place. T -- Life is too short to run proprietary software. -- Bdale Garbee
Feb 19 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 03:57:50PM +1300, James Miller wrote:
 I agree that the "Lispian" model works well, though I had issues
 trying to get my head around it when I encountered it.
 
 I don't know how you'd make a simpler version for D (D lacking Lisps
 ridiculous macros) but maybe something that essentially "returns" a
 list of recovery codes (which would unfortunately have to be
 documented) that can be called depending on the context of the error.
[...] The concept doesn't require Lisp macros to work. D already has the necessary machinery to implement it. All that's needed is some syntactic sugar to make it very easy to use. Personally I'd like language-level support for it, but I suspect judicious use of templates may alleviate even that.
 But error-handling is hard, programmers are naturally lazy, and
 checking errors is not something exiting. Exceptions are always going
 to be a source of contention amongst people. I know people (mostly C
 programmers) that hate them, and other people swear by them. I agree
 that incorrect parameters should not be Exceptions, and are
 contract-level issues, check your parameters before passing them if
 there are conditions on them!
D already supports contracts. The current convention throws AssertError on contract failure, but you could just as easily terminate the program right there.
 its a difficult topic, hence the ridiculously long thread we have
 going here. Error codes are not really the way to go, I prefer more of
 an Objective-C/Smalltalk null-pattern style, where null can be a valid
 argument almost anywhere the type system allows and code handles it
 properly, normally by returning null.
[...] Error codes are pass. The only reason we still have them is because of OS's inheriting the legacy APIs and conventions from 20 years ago. T -- I am Ohm of Borg. Resistance is voltage over current.
Feb 19 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 16:36:16 H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 05:13:16PM -0600, 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.
[...] That's because you're missing the hierarchy, which may look more like this: Error +--Exception +--GetOptException (bad name, I prefer CommandLineException) | +--FlagArgumentMissingException | +--InvalidFlagArgumentException | +--UnknownFlagException +--FileException | +--FileNotFoundException | +--NotFileException | +--NotDirException | +--AccessDeniedException +--IOException (OK, I added this branch, just to show the idea) +--ReadErrorException +--WriteErrorException So if all you care about is to handle command-line option parse errors, and don't care which error it is, you could just catch GetOptException. If all you care about is file access exceptions, you can just catch FileException without having to worry which specific exception it is. Alternatively, if you don't care which exception it is at all, then just catch Exception. That's the whole point of a (well-designed) exception hierarchy. It lets you be as generic or as specific as you want. Note also, that an elaborated exception hierarchy lets you attach additional specific information that might be useful in error recovery. For example, FileException may have a field containing the filename that caused the error, so that if the program is processing a list of filenames, it knows which one went wrong. You would not want such information in Exception, because it only applies to FileException's. Similarly, GetOptException (or CommandLineException) may have additional fields to indicate which option is malformed. Such information, obviously, shouldn't be in other kinds of exceptions but those that inherit GetOptException.
Exactly! And in order to programmatically handle exceptions rather than simply print out messages, that hierarchy and the extra information that the derived exceptions give is necessary. - Jonathan M Davis
Feb 18 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 10:21:17PM -0200, Jose Armando Garcia wrote:
 On Sat, Feb 18, 2012 at 9:13 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> 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.
Yeah. This gets even worse by the fact that some times you have to import vendor specific modules with their own set of exceptions. For example the JDBC exception model was just a nightmare. Depending on which database driver you used you could have a completely different set of exceptions you had to catch. The Spring Framework solved this problem by just throwing just one exception: SQLException (if my memory serves me right). The developer then had to switch based on the driver type and the error code. I don't think this is perfect but I think it works much better in practice.
That simply means Phobos has to be designed such that vendor-specific modules throw exceptions that inherit from SQLException. This is merely the symptom of a poorly-designed exception hierarchy, not of the fact that there is a hierarchy.
 The other nice thing about this model is that it is easier to know
 which error you want to handle. Database vendor document their errors
 using error code not language specific exception. If the Oracle manual
 says that the database gives error XXXX for such and such condition
 how in the world do you know to which JDBC exception does that match
 or D exception, etc.?
Simple. By doing this: class OracleException : SQLException { OracleErrorCode_t code; this(...) {...} } Then if your code just wants to handle generic SQL errors, you catch SQLException. If your code is ready to deal with Oracle error codes, then catch OracleDriverException and process the code. Just because some other exception *may* derive from OracleException doesn't mean you have to specifically catch that. Just catch the base class and read the error code yourself. That's what a class hierarchy is for!! I can't believe something this simple has to be explained so elaborately. I thought all of us here knew how to use OO?? T -- "You know, maybe we don't *need* enemies." "Yeah, best friends are about all I can take." -- Calvin & Hobbes
Feb 18 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote in message 
news:mailman.556.1329612309.20196.digitalmars-d puremagic.com...
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
My thoughts exactly. Andrei is a very intelligent, very capable programmer, and I do have a lot of respect for him, but it seems that every month or so, we have to collectively teach him something else about basic programming to ensure D doesn't veer off the rails into crazyville.
Feb 18 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 10:14 PM, Nick Sabalausky wrote:
 "H. S. Teoh"<hsteoh quickfur.ath.cx>  wrote in message
 news:mailman.556.1329612309.20196.digitalmars-d puremagic.com...
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
My thoughts exactly. Andrei is a very intelligent, very capable programmer, and I do have a lot of respect for him, but it seems that every month or so, we have to collectively teach him something else about basic programming to ensure D doesn't veer off the rails into crazyville.
I knew the day of reckoning would come :o). Andrei
Feb 18 2012
prev sibling next sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Sat, Feb 18, 2012 at 10:46 PM, H. S. Teoh <hsteoh quickfur.ath.cx> wrote=
:
 On Sat, Feb 18, 2012 at 10:21:17PM -0200, Jose Armando Garcia wrote:
 On Sat, Feb 18, 2012 at 9:13 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> 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.
Yeah. This gets even worse by the fact that some times you have to import vendor specific modules with their own set of exceptions. For example the JDBC exception model was just a nightmare. Depending on which database driver you used you could have a completely different set of exceptions you had to catch. The Spring Framework solved this problem by just throwing just one exception: SQLException (if my memory serves me right). The developer then had to switch based on the driver type and the error code. I don't think this is perfect but I think it works much better in practice.
That simply means Phobos has to be designed such that vendor-specific modules throw exceptions that inherit from SQLException. This is merely the symptom of a poorly-designed exception hierarchy, not of the fact that there is a hierarchy.
 The other nice thing about this model is that it is easier to know
 which error you want to handle. Database vendor document their errors
 using error code not language specific exception. If the Oracle manual
 says that the database gives error XXXX for such and such condition
 how in the world do you know to which JDBC exception does that match
 or D exception, etc.?
Simple. By doing this: =A0 =A0 =A0 =A0class OracleException : SQLException { =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0OracleErrorCode_t code; =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0this(...) {...} =A0 =A0 =A0 =A0}
You basically agree that we need error codes inside the exception to handle database errors correctly. What about POSIX error codes? Basically in some cases you are solving the problem using inheritance in other cases you are solving the problem by switching on a variable.
 Then if your code just wants to handle generic SQL errors, you catch
 SQLException. If your code is ready to deal with Oracle error codes,
 then catch OracleDriverException and process the code.

 Just because some other exception *may* derive from OracleException
 doesn't mean you have to specifically catch that. Just catch the base
 class and read the error code yourself. That's what a class hierarchy is
 for!!

 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
Just worry about what you know and don't know...
 T

 --
 "You know, maybe we don't *need* enemies." "Yeah, best friends are about
 all I can take." -- Calvin & Hobbes
Feb 18 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.)
Feb 18 2012
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Walter:

 (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.)
To improve this discussion a small benchmark is useful to see how much bloat this actually causes. Bye, bearophile
Feb 18 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 2/18/2012 8:08 PM, bearophile wrote:
 To improve this discussion a small benchmark is useful to see how much bloat
this actually causes.
It'll increase with reflection and perfect garbage collection.
Feb 18 2012
parent reply Sean Cavanaugh <WorksOnMyMachine gmail.com> writes:
On 2/18/2012 11:07 PM, Walter Bright wrote:
 On 2/18/2012 8:08 PM, bearophile wrote:
 To improve this discussion a small benchmark is useful to see how much
 bloat this actually causes.
It'll increase with reflection and perfect garbage collection.
Are these coming? :)
Feb 18 2012
parent "Robert Jacques" <sandford jhu.edu> writes:
On Sat, 18 Feb 2012 23:08:21 -0600, Sean Cavanaugh <WorksOnMyMachine gmail.com>
wrote:
 On 2/18/2012 11:07 PM, Walter Bright wrote:
 On 2/18/2012 8:08 PM, bearophile wrote:
 To improve this discussion a small benchmark is useful to see how much
 bloat this actually causes.
It'll increase with reflection and perfect garbage collection.
Are these coming? :)
Yes. Heap-perfect GC has had patches in Bugzilla, though they've suffered bit-rot. I'd really like a fully precise collector, but that is more work for Walter. Reflection is part of the variant refactoring I've been working on. I've kept the design very limited so far, but it does result in a lot of codegen and slow compile times in certain situations (mostly due to type tuple bugs and Algebraic).
Feb 18 2012
prev sibling next sibling parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Walter Bright" <newshound2 digitalmars.com> wrote in message 
news:jhpqdj$30t6$1 digitalmars.com...
 (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.)
As a counterpoint: With a standard exception heirachy in some cases executable size will be decreased, because it will reduce the number of libraries inventing their own similar heirachy.
Feb 18 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 04:31, Walter Bright a écrit :
 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.)
Why is the executable size such a big issue ? Additionnaly, nto providing a convenient Exception hierarchy is going inveitably to lead lib devs to create theire own. And the bloat will be even bigger (due to redundancy) and the result less convenient because the result will be inconsistent for the user of theses libs.
Feb 19 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 13:20:19 deadalnix wrote:
 Why is the executable size such a big issue ?
Some people complain bitterly about any additional size to the executable at all, whether they use all of the symbols in it or not, and if they don't use all of the symbols, then they're that much more annoyed by the additional size. It crops of semi-frequently around here that someone complains about how big hello world is. Most of the problem goes away if phobos because a shared library like it's going to eventually, but someone is always going to be complaining about binary size - especially with a systems language. - Jonathan M Davis
Feb 19 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 13:32, Jonathan M Davis a écrit :
 On Sunday, February 19, 2012 13:20:19 deadalnix wrote:
 Why is the executable size such a big issue ?
Some people complain bitterly about any additional size to the executable at all, whether they use all of the symbols in it or not, and if they don't use all of the symbols, then they're that much more annoyed by the additional size. It crops of semi-frequently around here that someone complains about how big hello world is. Most of the problem goes away if phobos because a shared library like it's going to eventually, but someone is always going to be complaining about binary size - especially with a systems language. - Jonathan M Davis
I know that people complain. But most of them are unable to explain why it is a problem. Plus, hello world size is a very stupid exemple accroding to me. It is just like comparaing sorting algorithms with an array of 5 elements. The code bloat that Exception would add is in O(1). So it will get irrevealant in every non trivial program toy like hello world. Note that Hello World in Java is about 50Mb. It is because the stdlib is big. And it has much more played in favor of java than against. Plus, we consider adding perfect GC (or at least on heap), which would be O(n) on the code bloat. This may be not noticeable on an hello world program, but this will sure generate more code bloat on any real application.
Feb 19 2012
prev sibling parent Piotr Szturmaj <bncrbme jadamspam.pl> writes:
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.
Feb 21 2012
prev sibling next sibling parent reply Robert Clipsham <robert octarineparrot.com> writes:
On 18/02/2012 23:13, 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
Perhaps something like: class PhobosException : Exception { this(string _module, uint flags) { ... } } try { doSomething(); } catch(PhobosException e) { // An exception was thrown by phobos if (e.moduleOf == "file") { // An exception was thrown by std.file if (e.flags & FileException.NotFound) { // File wasn't found } } else if (e.moduleOf == "stdio") { // An exception was thrown by std.stdio } // We only want to handle file and stdio exceptions, rethrow throw e; } Some pros/cons: * Catch-all for all phobos exceptions * No binary bloat from having lots of exception classes * Can still handle specific exceptions * e.flags isn't type-safe * It's not particularly pretty * Can only have up to 32 different "exceptions" in a module (64 if you make it a ulong) * It looks stupid if you come from an OOP language like java and you're used to having 5000 catch blocks * No need to decide on any exception hierarchy, just throw a PhobosException -- Robert http://octarineparrot.com/
Feb 19 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"Robert Clipsham" <robert octarineparrot.com> wrote in message 
news:jhref7$bo$1 digitalmars.com...
 On 18/02/2012 23:13, 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
Perhaps something like: class PhobosException : Exception { this(string _module, uint flags) { ... } } try { doSomething(); } catch(PhobosException e) { // An exception was thrown by phobos if (e.moduleOf == "file") { // An exception was thrown by std.file if (e.flags & FileException.NotFound) { // File wasn't found } } else if (e.moduleOf == "stdio") { // An exception was thrown by std.stdio } // We only want to handle file and stdio exceptions, rethrow throw e; } Some pros/cons: * Catch-all for all phobos exceptions * No binary bloat from having lots of exception classes * Can still handle specific exceptions * e.flags isn't type-safe * It's not particularly pretty * Can only have up to 32 different "exceptions" in a module (64 if you make it a ulong) * It looks stupid if you come from an OOP language like java and you're used to having 5000 catch blocks * No need to decide on any exception hierarchy, just throw a PhobosException
No, exceptions need to be based around semantics, not modules. The former is reusable and is well-proven to be very useful. The latter isn't particularly useful in general. And if anyone does have use for it, we could just add "moduleOf" to Exception, no need for it to be phobos-specific.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 19 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"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.
Feb 19 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Feb 19 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 4:00 PM, 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.
They don't; the intent here is, again, to cater to the common case.
 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).
Sure. That would mean std.file could define // inside std.file class FileNotFound : public ModuleException!.stringof { ... } class DiskFull : public ModuleException!.stringof { ... } Or could define one: // inside std.file class FileException : public ModuleException!.stringof { Code code; ... }
 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.
I think that direction is worth exploring.
 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.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something. Andrei
Feb 19 2012
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Sun, 19 Feb 2012 23:04:59 -0000, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 2/19/12 4:00 PM, Nick Sabalausky wrote:
 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.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something.
I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 5:55 AM, Regan Heath wrote:
 On Sun, 19 Feb 2012 23:04:59 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/19/12 4:00 PM, Nick Sabalausky wrote:
 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.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something.
I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p
Where did it seem I was playing devil's advocate? Thanks. Andrei
Feb 21 2012
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Tue, 21 Feb 2012 14:19:17 -0000, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 2/21/12 5:55 AM, Regan Heath wrote:
 On Sun, 19 Feb 2012 23:04:59 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/19/12 4:00 PM, Nick Sabalausky wrote:
 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.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something.
I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p
Where did it seem I was playing devil's advocate? Thanks.
"Devil's Advocate" is perhaps not the right term, as you don't seem to ever argue the opposite to what you believe. But, it occasionally seems to me that you imply ignorance on your part, in order to draw more information from other posters on exactly what they think or are proposing. So, some get frustrated as they feel they have to explain "everything" to you (and not just you, there have been times where - for whatever reason - it seems that anything less than a description of every single minute detail results in a miss understanding - no doubt partly due to the medium in which we are communicating). Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 22 2012
parent reply James Miller <james aatch.net> writes:
On 23 February 2012 05:09, Regan Heath <regan netmail.co.nz> wrote:
 On Tue, 21 Feb 2012 14:19:17 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/21/12 5:55 AM, Regan Heath wrote:
 On Sun, 19 Feb 2012 23:04:59 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/19/12 4:00 PM, Nick Sabalausky wrote:
 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 insanel=
y
 pedantic detail.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something.
I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to b=
e
 said for that, but it's probably less annoying to some if you're clear
 about that from the beginning. :p
Where did it seem I was playing devil's advocate? Thanks.
"Devil's Advocate" is perhaps not the right term, as you don't seem to ev=
er
 argue the opposite to what you believe. =C2=A0But, it occasionally seems =
to me
 that you imply ignorance on your part, in order to draw more information
 from other posters on exactly what they think or are proposing. =C2=A0So,=
some
 get frustrated as they feel they have to explain "everything" to you (and
 not just you, there have been times where - for whatever reason - it seem=
s
 that anything less than a description of every single minute detail resul=
ts
 in a miss understanding - no doubt partly due to the medium in which we a=
re
 communicating).


 Regan

 --
 Using Opera's revolutionary email client: http://www.opera.com/mail/
I think that is technically called being facetious. -- James Miller
Feb 23 2012
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Thu, 23 Feb 2012 15:13:17 -0000, James Miller <james aatch.net> wrote:
 On 23 February 2012 05:09, Regan Heath <regan netmail.co.nz> wrote:
 On Tue, 21 Feb 2012 14:19:17 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/21/12 5:55 AM, Regan Heath wrote:
 On Sun, 19 Feb 2012 23:04:59 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/19/12 4:00 PM, Nick Sabalausky wrote:
 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.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something.
I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p
Where did it seem I was playing devil's advocate? Thanks.
"Devil's Advocate" is perhaps not the right term, as you don't seem to ever argue the opposite to what you believe. But, it occasionally seems to me that you imply ignorance on your part, in order to draw more information from other posters on exactly what they think or are proposing. So, some get frustrated as they feel they have to explain "everything" to you (and not just you, there have been times where - for whatever reason - it seems that anything less than a description of every single minute detail results in a miss understanding - no doubt partly due to the medium in which we are communicating). Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
I think that is technically called being facetious.
Doesn't seem quite right to me: http://dictionary.reference.com/browse/facetious R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 24 2012
parent Don Clugston <dac nospam.com> writes:
On 24/02/12 13:47, Regan Heath wrote:
 On Thu, 23 Feb 2012 15:13:17 -0000, James Miller <james aatch.net> wrote:
 On 23 February 2012 05:09, Regan Heath <regan netmail.co.nz> wrote:
 On Tue, 21 Feb 2012 14:19:17 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/21/12 5:55 AM, Regan Heath wrote:
 On Sun, 19 Feb 2012 23:04:59 -0000, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/19/12 4:00 PM, Nick Sabalausky wrote:
 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.
It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something.
I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p
Where did it seem I was playing devil's advocate? Thanks.
"Devil's Advocate" is perhaps not the right term, as you don't seem to ever argue the opposite to what you believe. But, it occasionally seems to me that you imply ignorance on your part, in order to draw more information from other posters on exactly what they think or are proposing. So, some get frustrated as they feel they have to explain "everything" to you (and not just you, there have been times where - for whatever reason - it seems that anything less than a description of every single minute detail results in a miss understanding - no doubt partly due to the medium in which we are communicating). Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
I think that is technically called being facetious.
Doesn't seem quite right to me: http://dictionary.reference.com/browse/facetious R
Socratic irony?
Mar 05 2012
prev sibling next sibling parent reply Patrick Down <patrick.down using.that.google.mail.thing.com> writes:
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.
Feb 19 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Feb 19 2012
prev sibling parent reply Jim Hewes <jimhewes gmail.com> writes:
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
Feb 19 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 02:53:04PM -0800, Jim Hewes wrote:
[...]
 Two, the exception hierarchy is orthogonal to the library.
Yes, yes, and yes! You have identified one of the causes of the current problems with the exception hierarchy. [...]
 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.
For a well-defined library like Phobos, we really shouldn't be defining new exceptions in each module. Rather, (almost) all exceptions should be collected in a common place, organized as a clean hierarchy without being unnecessarily bound to any particular module. Modifications to the exception hierarchy need to be properly evaluated before being committed to the codebase. Of course, there are some exceptions that are specific to a module; those obviously belong in their respective module. But generally speaking, exceptions should fit into a clean classification hierarchy independently of which module first introduced them. Just because exception X was first introduced by module M doesn't mean that it's specific to module M; it may encapsulate a larger class of problems that M just happens to touch. By separating the exception hierarchy from the module hierarchy, we can clean up a lot of the mess that resulted from conflating the two. T -- Chance favours the prepared mind. -- Louis Pasteur
Feb 19 2012
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-18 23:26, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 20:20:23 deadalnix wrote:
 I think your oppinion here is shaped by C++. For what I experienced, in
 C++, exception are only usefull for very important problem you cannot
 possibly solve, and at the best log the error and exit.

 An exemple is std::bad_alloc .

 However, in D, I think this is more the role of an Errors. Exception are
 something « softer ». It will alert you on problems your program
 encounter, but that are recoverable.

 You cannot recover from any exception at any place (sometime, you just
 cannot at all).

 Let's get an exemple : your program ask a file to the user and do some
 operations with this file. If the file doesn't exists, you can prompt
 for another file to the user with a meaningful message and start again.
 However, the first version of your program can just ignore that case and
 fail with a less specific handler in firsts versions.

 You cannot achieve something like that if you don't have a useful type
 to rely on. Here something like FileNotFoundException is what you want.

 The type of the exception must depend on the problem you are facing, not
 on the module that trhow it. I see a lot of people doing the «
 MyProgramException » or « MyLibException » but that doesn't make sense.
 In this case, you are just making things harder.

 Back on the original subject, GetOptException is not what you want. As
 getopt is supposed to handle command line parameters, you'll use
 exception like : MissingParameterException, WrongFormatException,
 UnknowParameterException or anything that is meaningful.

 Those Exceptions would inherit from something like CommandLineException.
 This is useful because it describe what you are facing. Because you cant
 to know what is the problem, not which piece of code face the problem.

 If this politic is choosen, then It would make sense to have several
 modules of phobos throwing exceptions of the same type, or inheriting
 from the same base class.

 Exception type is a convenient way to filter what you catch and what you
 don't know how to handle at this point.
In general, I agree with all of this. I very much think that having typed exceptions makes a lot of sense. In general, I think that Java got exceptions right (ignoring the issue with checked exceptions). Having typed exceptions works really well. In the case of getopt, what we want is a GetOptException which is for anything which goes wrong with getopt so that someone can catch that exception type if they want to just handle the case wheree getopt throws but don't want to swallow other exception types by calling exception and pretty much just want to print an error and exit the program or continue without any comand-line arguments if that's what they prefer to do.
Isn't that what CommandLineException would be? Or would "getopt" throw other exceptions that would not derive from CommandLineException? So we end up with this: GetOptException / \ / \ / \ CommandLineException SomeKindOfOtherException ?
 Then you have other exception types derived from GetOptException which deal
 with specific types of issues - such as FlagArgumentMissingException,
 InvalidFlagArgumentException, UnknownFlagException. Each of those exception
 types could then give information which specifically pertains to those errors -
 such as the flag that had a problem, the type that the flag is supposed to
 receive, the type that the flag actually received, the invalid value that the
 flag was given, etc. Such exceptions can then allow you to properly handle and
 report problems with command-line arguments. Right now, all you know is that
 something went wrong, and pretty much the best that you can do is print out
 that something went wrong. You can do any decent error handling at all. You
 need specific exception types which give you the appropriate information in
 order to do that.

 Another example is FileException. It would be benificial to have exceptions
 like FileNotFoundException, NotFileException, NotDirException,
 AccessDeniedException, etc which are derived from FileException. Then programs
 could handle the specific instance of what went wrong nice and cleanly rather
 than having to look at error codes. At least FileException provides an error
 code, but that makes for much uglier handling code assuming that you even have
 any clue what the error codes mean. And since the error codes can vary from OS
 to OS, you end up with system-specific error handling code if you try and use
 errno. Whereas if std.file just translated the error code to the correct
 exception type, you're then very cleanly informed as to what the problem was
 and can catch the exception based on which situtations you can recover from
 and which you can't as well as having different catch blocks to handle
different
 problems differently.

 Simply having an exception type per module is somewhat better than just having
 Exception, because it gives you a better idea of what went wrong (e.g. you got
 a UTFException rather than a FileException), but it's still way too general in
 a lot of cases, and I can see why some would think that it creates needless
 boilerplate code. Also, in some cases, having exception types derive from more
 general exceptions than what the module focuses on can be useful. For
 instance, Java has IOException as the base for all IOExceptions. FileException
 could be derived from that, and then std.stream and std.stdio could could have
 their exception types derive from that as well. Then you could specifically
 handle all exceptions related to I/O together.

 I'm completely sold on typed exceptions, but I think that we could do better
 with them than we're currently doing.

 - Jonathan M Davis
Otherwise I completely agree. -- /Jacob Carlborg
Feb 19 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 12:43:12 Jacob Carlborg wrote:
 On 2012-02-18 23:26, Jonathan M Davis wrote:
 In the case of getopt, what we want is a GetOptException which is for
 anything which goes wrong with getopt so that someone can catch that
 exception type if they want to just handle the case wheree getopt throws
 but don't want to swallow other exception types by calling exception and
 pretty much just want to print an error and exit the program or continue
 without any comand-line arguments if that's what they prefer to do.
Isn't that what CommandLineException would be? Or would "getopt" throw other exceptions that would not derive from CommandLineException? So we end up with this: GetOptException / \ / \ / \ CommandLineException SomeKindOfOtherException
IF you want to call it CommandLineException rather than GetOptException, that's fine. I don't much care. The name isn't the critical part here. GetOptException would be more in line with how we've been naming things, but CommandLineException would be more reusable by code outside of std.getopt, so it's probably better. The critical thing here though is that getopt throw an exception (or exceptions) which actually give you the information that you need to handle the problem, which isn't the case right now. - Jonathan M Davis
Feb 19 2012
prev sibling next sibling parent reply "Nathan M. Swan" <nathanmswan gmail.com> writes:
On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei 
Alexandrescu wrote:
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

 Let's come up with a good doctrine for exception defining and 
 handling in Phobos. From experience I humbly submit that 
 catching by type is most of the time useless.


 Andrei
Here's a compromise I would suggest: we have the different exception types for different exceptional behaviors, but still they all descend from a common exception type that has a field with the module name; this way, the client can choose which way they want to go. It would be nice if there was a mixin template that creates an exception class that acts like this; making similar exception classes is annoying.
Feb 18 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 08:18:53PM +0100, Nathan M. Swan wrote:
 On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei Alexandrescu
 wrote:
There's a discussion that started in a pull request:

https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

Let's come up with a good doctrine for exception defining and
handling in Phobos. From experience I humbly submit that catching
by type is most of the time useless.
[...]
 Here's a compromise I would suggest: we have the different exception
 types for different exceptional behaviors, but still they all
 descend from a common exception type that has a field with the
 module name; this way, the client can choose which way they want to
 go.
I think deadalnix's approach is better. Exceptions should not be categorized by module. What if the module depends on submodules? Then catching only that module's exceptions will miss exceptions thrown from submodules. No, exceptions need to be based on *semantics* rather than modules, like CommandLineException, not GetOptException.
 It would be nice if there was a mixin template that creates an
 exception class that acts like this; making similar exception
 classes is annoying.
+1. This I agree with. T -- Never step over a puddle, always step around it. Chances are that whatever made it is still dripping.
Feb 18 2012
parent "Regan Heath" <regan netmail.co.nz> writes:
On Sat, 18 Feb 2012 19:58:14 -0000, H. S. Teoh <hsteoh quickfur.ath.cx>  
wrote:

 On Sat, Feb 18, 2012 at 08:18:53PM +0100, Nathan M. Swan wrote:
 On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei Alexandrescu
 wrote:
There's a discussion that started in a pull request:

https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

Let's come up with a good doctrine for exception defining and
handling in Phobos. From experience I humbly submit that catching
by type is most of the time useless.
[...]
 Here's a compromise I would suggest: we have the different exception
 types for different exceptional behaviors, but still they all
 descend from a common exception type that has a field with the
 module name; this way, the client can choose which way they want to
 go.
I think deadalnix's approach is better. Exceptions should not be categorized by module. What if the module depends on submodules? Then catching only that module's exceptions will miss exceptions thrown from submodules. No, exceptions need to be based on *semantics* rather than modules, like CommandLineException, not GetOptException.
The way I currently prefer to use exceptions in C++ is to have 1 exception type per module. That module will catch and wrap *all* exceptions thrown by modules it calls. i.e. [somemodule.cpp] ... try { ... } catch(OtherModuleException &ex) { throw SomeModuleException("lalala", ex); } In this way I know, when calling a method in SomeModule, the only exception I need to catch is SomeModuleException. So, code which uses multiple modules might look like.. try { SomeModule a = new.. OtherModule b = new .. ... a->method(); ... b->method(); } catch(SomeModuleException ex) { ... } catch(OtherModuleException ex) { ... } where typically one of those modules is something like a tcpip socket module, or a database module and the other may be something much more specific/different (which internally could use sockets etc). In the case of more complex modules, where I want to allow different error cases to be caught separately I might define multiple exceptions, named on error semantics, but all prefixed with the module i.e. SomeModule<semantic_error>Exception - but again, the module methods/functions will always catch and wrap all exceptions from all modules it itself calls so you have a small finite list of exceptions you need to worry about. Basically, I've taken exception handling and flattened it somewhat to make it easier to keep track of what exceptions can be thrown when/where and what I need to worry about handling. It is still possible to dig into an exception and get the inner/cause exception, and theoretically I could alter my behaviour based on that, but I've never needed to yet. All my exceptions come from a common base, except for certain "errors" which are only caught at top level locations i.e. process main, thread main, or the top of a conceptual "task" or task engine. These are derived from a separate base type - much like we have Error and Exception in D - in fact, I probably got the idea from this NG :p
 It would be nice if there was a mixin template that creates an
 exception class that acts like this; making similar exception
 classes is annoying.
+1 I use a macro in C++ for this. Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 20 2012
prev sibling next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 2/18/12, Nathan M. Swan <nathanmswan gmail.com> wrote:
 It would be nice if there was a mixin template that creates an
 exception class that acts like this; making similar exception
 classes is annoying.
It would be even nicer if we didn't need a mixin for classes that simply forward the ctor call to the base class ctor: class FileException : Exception { } // no-go, have to write a ctor that forwards to super AIUI this has something to do with ctors not being virtual. I think someone mentioned this could be improved one day.
Feb 18 2012
parent reply Ben Davis <entheh cantab.net> writes:
On 18/02/2012 21:07, Andrej Mitrovic wrote:
 On 2/18/12, Nathan M. Swan<nathanmswan gmail.com>  wrote:
 It would be nice if there was a mixin template that creates an
 exception class that acts like this; making similar exception
 classes is annoying.
It would be even nicer if we didn't need a mixin for classes that simply forward the ctor call to the base class ctor: class FileException : Exception { } // no-go, have to write a ctor that forwards to super AIUI this has something to do with ctors not being virtual. I think someone mentioned this could be improved one day.
I guess this is a bit off topic, but what you probably want is syntactic sugar that says "declare constructors matching all super constructors and calling through to them" and can be used in ANY class hierarchy (not necessarily exceptions). For example: class Subtype : Supertype { super all; } If you want to expose just specific constructors, then there could also be a shorthand for "declare a constructor matching a specific super constructor and calling through to it" - so you don't have to repeat all the arguments. For example: class Subtype : Supertype { super(); super(string,int); } That would then make it an entirely separate issue and completely not Exception-specific.
Feb 18 2012
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 01:10:10AM +0000, Ben Davis wrote:
[...]
 I guess this is a bit off topic, but what you probably want is
 syntactic sugar that says "declare constructors matching all super
 constructors and calling through to them" and can be used in ANY class
 hierarchy (not necessarily exceptions). For example:
 
 class Subtype : Supertype {
     super all;
 }
 
 If you want to expose just specific constructors, then there could
 also be a shorthand for "declare a constructor matching a specific
 super constructor and calling through to it" - so you don't have to
 repeat all the arguments. For example:
 
 class Subtype : Supertype {
     super();
     super(string,int);
 }
 
 That would then make it an entirely separate issue and completely
 not Exception-specific.
+1. This is definitely something not specific to Exception. Quite often, you want to create a derived class overriding just one or two members of the base class, but end up having to copy-n-paste most of the many ctors in the base class along with their elaborate arguments just so you can pass through the arguments to them. Having a way of simply saying "the ctors in this class default to the base class ctors" will solve this problem in a very nice and logical way. (I.e., you're sortof "inheriting the base class ctors", although not exactly, of course.) T -- Today's society is one of specialization: as you grow, you learn more and more about less and less. Eventually, you know everything about nothing.
Feb 18 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 17:20:41 H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 01:10:10AM +0000, Ben Davis wrote:
 [...]
 
 I guess this is a bit off topic, but what you probably want is
 syntactic sugar that says "declare constructors matching all super
 constructors and calling through to them" and can be used in ANY class
 hierarchy (not necessarily exceptions). For example:
 
 class Subtype : Supertype {
 
     super all;
 
 }
 
 If you want to expose just specific constructors, then there could
 also be a shorthand for "declare a constructor matching a specific
 super constructor and calling through to it" - so you don't have to
 repeat all the arguments. For example:
 
 class Subtype : Supertype {
 
     super();
     super(string,int);
 
 }
 
 That would then make it an entirely separate issue and completely
 not Exception-specific.
+1. This is definitely something not specific to Exception. Quite often, you want to create a derived class overriding just one or two members of the base class, but end up having to copy-n-paste most of the many ctors in the base class along with their elaborate arguments just so you can pass through the arguments to them. Having a way of simply saying "the ctors in this class default to the base class ctors" will solve this problem in a very nice and logical way. (I.e., you're sortof "inheriting the base class ctors", although not exactly, of course.)
Feel free to open a pull request. It would certainly be a useful feature for reducing boilerplate code in some class hierarchies (though in many, the constructors in the derived class must be different, in which case it wouldn't work). One potential issue though is that not all base classes necessarily share the same constructors. Which ones would be grabbed? The ones from the immediate base class? All of them? Only the ones common to all? - Jonathan M Davis
Feb 18 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/19/2012 02:23 AM, Jonathan M Davis wrote:
 One potential issue though is that not all base classes necessarily share the
 same constructors. Which ones would be grabbed? The ones from the immediate
 base class? All of them? Only the ones common to all?
There is only one base class. Inheriting constructors from anywhere further up the hierarchy cannot work, because then the base class cannot be constructed.
Feb 18 2012
parent Ben Davis <entheh cantab.net> writes:
On 19/02/2012 02:19, Timon Gehr wrote:
 On 02/19/2012 02:23 AM, Jonathan M Davis wrote:
 One potential issue though is that not all base classes necessarily
 share the
 same constructors. Which ones would be grabbed? The ones from the
 immediate
 base class? All of them? Only the ones common to all?
There is only one base class. Inheriting constructors from anywhere further up the hierarchy cannot work, because then the base class cannot be constructed.
To phrase it another way, you could say that the direct base class has already decided which of ITS base class's constructors to wrap and expose. The direct base class's decision is final, so the class extending it further can only base its constructors on that.
Feb 19 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 02:23, Jonathan M Davis a écrit :
 On Saturday, February 18, 2012 17:20:41 H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 01:10:10AM +0000, Ben Davis wrote:
 [...]

 I guess this is a bit off topic, but what you probably want is
 syntactic sugar that says "declare constructors matching all super
 constructors and calling through to them" and can be used in ANY class
 hierarchy (not necessarily exceptions). For example:

 class Subtype : Supertype {

      super all;

 }

 If you want to expose just specific constructors, then there could
 also be a shorthand for "declare a constructor matching a specific
 super constructor and calling through to it" - so you don't have to
 repeat all the arguments. For example:

 class Subtype : Supertype {

      super();
      super(string,int);

 }

 That would then make it an entirely separate issue and completely
 not Exception-specific.
+1. This is definitely something not specific to Exception. Quite often, you want to create a derived class overriding just one or two members of the base class, but end up having to copy-n-paste most of the many ctors in the base class along with their elaborate arguments just so you can pass through the arguments to them. Having a way of simply saying "the ctors in this class default to the base class ctors" will solve this problem in a very nice and logical way. (I.e., you're sortof "inheriting the base class ctors", although not exactly, of course.)
Feel free to open a pull request. It would certainly be a useful feature for reducing boilerplate code in some class hierarchies (though in many, the constructors in the derived class must be different, in which case it wouldn't work). One potential issue though is that not all base classes necessarily share the same constructors. Which ones would be grabbed? The ones from the immediate base class? All of them? Only the ones common to all? - Jonathan M Davis
Well that should be achievable with compile time refexion and CTFE/mixin or mixin template. Maybe this should be inserted in phobos.
Feb 19 2012
prev sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 2/19/12, Ben Davis <entheh cantab.net> wrote:
 That would then make it an entirely separate issue and completely not
 Exception-specific.
Yes I meant in the general case not just Exception. Exception is just a regular class.
Feb 18 2012
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 20:18:53 Nathan M. Swan wrote:
 Here's a compromise I would suggest: we have the different
 exception types for different exceptional behaviors, but still
 they all descend from a common exception type that has a field
 with the module name; this way, the client can choose which way
 they want to go.
If someone just wants to handle generic exceptions, then they can catch Exception. That's no reason to not actually have a proper exception hierarchy that programmers can use if they want to. Module-specific works on some level precisely because modules generally encompass specific behaviors (e.g. file handling or string handling), but a really well-designed exception hierarchy would not be specifically tied to modules the way that we are now. Rather, we'd have exceptions specific to behaviors, and different modules would reuse exceptions from other modules where appropriate based on what they're doing.
 It would be nice if there was a mixin template that creates an
 exception class that acts like this; making similar exception
 classes is annoying.
I've considered creating one (it woud be quite easy), but the problem is that you don't end up with any ddoc documentation for them. - Jonathan M Davis
Feb 18 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 12:52:05PM -0600, Andrei Alexandrescu wrote:
 There's a discussion that started in a pull request:
 
 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca
 
 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is
 most of the time useless.
[...] It's only useless because of a poorly-designed exception hierarchy. Java, for example, has useful things like FileNotFoundException, DiskFullException, etc., that you can catch to handle specific problems in a specific way. They are also part of a well-designed hierarchy. For example, both of the above exceptions are subsumed under IOException, so if you wanted to handle a general I/O exception and don't care which one it is, you could just catch IOException. Now, granted, there are limitations to such a design, for example if you want to catch a category of exceptions that don't map precisely to a node in the inheritance hierarchy. It may be possible to deal with such situations by making Exception an interface instead, although that may introduce other issues. Either that, or allow a list of exceptions in a catch() block, but that would require some further thought, because blindly allowing multiple arguments to catch introduces an initialization problem: try { ... } catch(FileNotFoundException e1, DiskFullException e2) { // which of e1, e2 is actually thrown? } The problem with this approach is the proliferation of of exception classes, many of which differ only in fine ways that most applications wouldn't even care about. The basic problem here is that we are mapping what amounts to error codes to classes. C++ does allow throwing arbitrary objects (IIRC), so in a sense you *could* throw an error code instead of a class object. But that's not necessarily a good thing, because then you end up with different modules throwing different, mutually incompatible enumerated error codes, which brings us back to square two (not square one because at least we don't have to manually propagate error codes in every level of the call stack), where we have a bunch of incompatible error types and we don't necessarily know what to do with them. Having a common root to all exceptions is a good thing. T -- Try to keep an open mind, but not so open your brain falls out. -- theboz
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 1:41 PM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 12:52:05PM -0600, Andrei Alexandrescu wrote:
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is
 most of the time useless.
[...] It's only useless because of a poorly-designed exception hierarchy. Java, for example, has useful things like FileNotFoundException, DiskFullException, etc., that you can catch to handle specific problems in a specific way. They are also part of a well-designed hierarchy. For example, both of the above exceptions are subsumed under IOException, so if you wanted to handle a general I/O exception and don't care which one it is, you could just catch IOException.
It's great that you bring expertise from the Java world. I should note that the above does little in the way of putting an argument to the table. It appeals to subjective qualifications ("poorly designed", "well designed") so one's only recourse to getting convinced is just believing what you say without any evidence. It's equally unimpressive that FileNotFoundException and DiskFullException inherit IOException; seeing that one might say "yeah, sure" but there should be some compelling evidence that the distinction makes a difference.
 Now, granted, there are limitations to such a design, for example if you
 want to catch a category of exceptions that don't map precisely to a
 node in the inheritance hierarchy. It may be possible to deal with such
 situations by making Exception an interface instead, although that may
 introduce other issues.
From this and other posts I'd say we need to design the base exception classes better, for example by defining an overridable property isTransient that tells caller code whether retrying might help.
 Either that, or allow a list of exceptions in a catch() block, but that
 would require some further thought, because blindly allowing multiple
 arguments to catch introduces an initialization problem:

 	try { ... }
 	catch(FileNotFoundException e1, DiskFullException e2) {
 		// which of e1, e2 is actually thrown?
 	}
It seems exceptions still are not entirely understood, and I agree that adding some random mechanism doesn't do good.
 The problem with this approach is the proliferation of of exception
 classes, many of which differ only in fine ways that most applications
 wouldn't even care about. The basic problem here is that we are mapping
 what amounts to error codes to classes.
Yes. Types, to be more general. The question is why are various error deserving of their own types.
 C++ does allow throwing
 arbitrary objects (IIRC), so in a sense you *could* throw an error code
 instead of a class object. But that's not necessarily a good thing,
 because then you end up with different modules throwing different,
 mutually incompatible enumerated error codes, which brings us back to
 square two (not square one because at least we don't have to manually
 propagate error codes in every level of the call stack), where we have a
 bunch of incompatible error types and we don't necessarily know what to
 do with them. Having a common root to all exceptions is a good thing.
I didn't contend the opposite. Andrei
Feb 18 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 05:47:58PM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 1:41 PM, H. S. Teoh wrote:
[...]
It's only useless because of a poorly-designed exception hierarchy.
Java, for example, has useful things like FileNotFoundException,
DiskFullException, etc., that you can catch to handle specific
problems in a specific way. They are also part of a well-designed
hierarchy. For example, both of the above exceptions are subsumed
under IOException, so if you wanted to handle a general I/O exception
and don't care which one it is, you could just catch IOException.
It's great that you bring expertise from the Java world.
You flatter me, but I should make it clear that I'm no Java expert. I haven't used it in a significant way for years. But I do know enough about it to know, at least in principle, the merits of its exception class hierarchy.
 I should note that the above does little in the way of putting an
 argument to the table. It appeals to subjective qualifications
 ("poorly designed", "well designed") so one's only recourse to getting
 convinced is just believing what you say without any evidence. It's
 equally unimpressive that FileNotFoundException and DiskFullException
 inherit IOException; seeing that one might say "yeah, sure" but there
 should be some compelling evidence that the distinction makes a
 difference.
I wanted to talk about the principles of using an exception hierarchy, not get lost in the details of it, that's why I didn't go into the details of how exactly the hierarchy should be designed. As for this particular case, I was only trying to illustrate the point with a concrete example; I wasn't suggesting that we must implement those exact three exception classes. Nevertheless, I would say the decision to make FileNotFoundException and DiskFullException inherit from IOException can be based on: 1) The fact that they are based on a common set of OS primitives, i.e., the filesystem, thereby providing a logical set of related exceptions; 2) It makes sense to put errno in IOException, whereas it doesn't make sense to put errno in Exception, since not all Exception's have any relation to errno. 3) From a more abstract POV, it makes sense to group file-related exceptions under a common root, command-line parsing exceptions under another root, numerical exceptions under yet another root, etc.. And how do we decide which exception belongs where? By checking whether an application might want to catch all exceptions in a group as a whole, e.g., a numerical app wants to handle any numerical errors, but don't care about I/O errors (which should just propagate). So it wouldn't make sense to put, say, ArithmeticOverflowException under IOException.
Now, granted, there are limitations to such a design, for example if
you want to catch a category of exceptions that don't map precisely
to a node in the inheritance hierarchy. It may be possible to deal
with such situations by making Exception an interface instead,
although that may introduce other issues.
From this and other posts I'd say we need to design the base exception classes better, for example by defining an overridable property isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation. The bottomline is, you can't expect a couple of properties in the base class (most generic level) will be helpful in all cases. In some cases, you *want* to know exactly what the exception was programmatically, so that the program knows how to react properly. [...]
The problem with this approach is the proliferation of of exception
classes, many of which differ only in fine ways that most
applications wouldn't even care about. The basic problem here is that
we are mapping what amounts to error codes to classes.
Yes. Types, to be more general. The question is why are various error deserving of their own types.
I have to say, I'm not wedded to the idea of Exception class hierarchies, but currently it's the best choice on the table. Basically you *need* some kind of polymorphic object to represent an exception, because errors come in all shapes and forms, and it's simply not good enough to have what amounts to a binary system (fatal uncatchable exception and non-fatal exception that you may catch and attempt to continue if you dare). Whether it's a class, a template, or whatever, doesn't really matter in the end, but you do want to have: 1) Genericity: if I don't care what error it is, just that an error happened, I should be able to catch exceptions in general and deal with them. 2) Specificity: if I have a very precise error (or a very precise set of errors) that I can handle, I want to be able to catch those, and only those, errors, and deal with them. The ones I can't handle, I want to just propagate. 3) Programmatic information: the fact that I want to handle a certain set of errors means that I know how to deal with them, provided I have enough information about them. Which means the exceptions I catch need to come with domain-specific information (such as errno for OS-related exceptions, or Oracle error numbers for the Oracle API library, etc.). Having toString() as the only way to extract information out of the exception is not good enough for recovering from errors programmatically. (And obviously sticking errno, Oracle errors, etc., into a giant union in Exception isn't the right solution either.) Currently, an elaborated exception class hierarchy, for all its warts and the objections against it, best fits the bill. If you have a better way of doing this, I'm all ears. But the current "fatal"/"nonfatal" dichotomy is simply too coarse to be of use in large applications. T -- Food and laptops don't mix.
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 8:00 PM, H. S. Teoh wrote:
  From this and other posts I'd say we need to design the base exception
 classes better, for example by defining an overridable property
 isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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. Andrei
Feb 18 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 12:43:58AM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
 From this and other posts I'd say we need to design the base exception
classes better, for example by defining an overridable property
isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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? Why doesn't the function concerned simply retry on its own? Network stack code does that. It would be nightmarish to program network applications if you always have to implement retry on your own (much less use *exceptions* to handle them)! T -- Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 12:54 AM, H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 12:43:58AM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
  From this and other posts I'd say we need to design the base exception
 classes better, for example by defining an overridable property
 isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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
Feb 19 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 02:02:39AM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 12:54 AM, H. S. Teoh wrote:
On Sun, Feb 19, 2012 at 12:43:58AM -0600, Andrei Alexandrescu wrote:
[...]
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.
[...] 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? 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. 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? T -- Obviously, some things aren't very obvious.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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). Andrei
Feb 19 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Feb 19 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 09:02, Andrei Alexandrescu a crit :
 On 2/19/12 12:54 AM, H. S. Teoh wrote:
 On Sun, Feb 19, 2012 at 12:43:58AM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
 From this and other posts I'd say we need to design the base exception
 classes better, for example by defining an overridable property
 isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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 ?
Feb 19 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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. Andrei
Feb 19 2012
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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??
Feb 19 2012
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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 =C3=A9crit :
I'm thinking an error is transient if retrying the operation wi=
th 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 poin=
ts.
What do you expect from that discussion if yourself you do not put=
any
arguement on the table ?
=20 The answer is meaningful. The purpose of exceptions is allowing for=
 centralized error handling, and a capabilities-based system makes t=
hat
 simple (e.g. you get to make decisions about recoverability in one
 place, regardless of which part of the exception hierarchy the
 exception originated.
=20 [...] =20 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 fil=
e
 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?
=20
 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 b=
e
 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 co= des in=20 a function, you have to check them on practically every call to another= =20 function. This makes a mess of your code. If you use exceptions, on the= other=20 hand, you don't have any of those checks in your code. Rather, you wrap= the=20 code in a try-catch block, and put your error-handling code in one plac= e=20 within the function. _That_ is centralizing it. In some cases, that means a try-catch block at a higher level without a= ny in=20 sub functions, because it makes the most sense for a function higher up= to=20 handle the exceptions there (e.g. if you're going to want to pop up a d= ialag=20 to ask the user to give a different file, you want that higher up, not = in=20 std.file), and in some cases, you want the try-catch block to be much c= loser to=20 where the throw occurred (sometimes even in the same function). Now, maybe Andrei meant centralizing the error handling even more than = that,=20 but it makes so little sense to have _one_ place in your program handli= ng all=20 of the exceptions that I have a very hard time believing that that's wh= at he=20 really meant. - Jonathan M Davis
Feb 19 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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. Andrei
Feb 19 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 00:43:58 Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
  From this and other posts I'd say we need to design the base exception
 
 classes better, for example by defining an overridable property
 isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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.
A core problem with the idea is that whether or not it makes sense to try again depends on what the caller is doing. In general, I think that it's best to give the caller as much useful information is possible so that _it_ can decide the best way to handle the exception. - Jonathan M Davis
Feb 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 1:12 AM, Jonathan M Davis wrote:
 On Sunday, February 19, 2012 00:43:58 Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
    From this and other posts I'd say we need to design the base exception

 classes better, for example by defining an overridable property
 isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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.
A core problem with the idea is that whether or not it makes sense to try again depends on what the caller is doing. In general, I think that it's best to give the caller as much useful information is possible so that _it_ can decide the best way to handle the exception.
That sounds like "I violently agree". Andrei
Feb 19 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 02:06:38 Andrei Alexandrescu wrote:
 On 2/19/12 1:12 AM, Jonathan M Davis wrote:
 On Sunday, February 19, 2012 00:43:58 Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
    From this and other posts I'd say we need to design the base
    exception
 
 classes better, for example by defining an overridable property
 isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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.
A core problem with the idea is that whether or not it makes sense to try again depends on what the caller is doing. In general, I think that it's best to give the caller as much useful information is possible so that _it_ can decide the best way to handle the exception.
That sounds like "I violently agree".
Then I'm confused. "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. - Jonathan M Davis
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 19 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Feb 19 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 19 2012
prev sibling parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
How about adding a string[string] or a variant[string] to the 
Exception class, so one can know details about the subclassed 
exception without downcasting? How ugly would that be?

For instance:

     ...
     catch (Exception ex) {
       if ("transient" in ex.details) {
             repeatOneMoreTime();
       }
       if ("i18n_code" in ex.details) {
             log(translate(ex.details["i18n_code"]));
       }
     }
     ...

Details can be standard by convention or otherwise custom.
(I can see that this can lead to messy proliferation of details, 
but at least solves most of the issues).

--jm (BIG FAN OF D. GUYS I LOVE ALL YOUR GOOD WORK)




On Sunday, 19 February 2012 at 08:06:38 UTC, Andrei Alexandrescu 
wrote:
 On 2/19/12 1:12 AM, Jonathan M Davis wrote:
 On Sunday, February 19, 2012 00:43:58 Andrei Alexandrescu 
 wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
   From this and other posts I'd say we need to design the 
 base exception

 classes better, for example by defining an overridable 
 property
 isTransient that tells caller code whether retrying might 
 help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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.
A core problem with the idea is that whether or not it makes sense to try again depends on what the caller is doing. In general, I think that it's best to give the caller as much useful information is possible so that _it_ can decide the best way to handle the exception.
That sounds like "I violently agree". Andrei
Feb 19 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 6:27 AM, Juan Manuel Cabo wrote:
 How about adding a string[string] or a variant[string] to the Exception
 class, so one can know details about the subclassed exception without
 downcasting?
That is a must for custom formatting and i18n. Andrei
Feb 19 2012
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-19 13:27, Juan Manuel Cabo wrote:
 How about adding a string[string] or a variant[string] to the Exception
 class, so one can know details about the subclassed exception without
 downcasting? How ugly would that be?

 For instance:

 ...
 catch (Exception ex) {
 if ("transient" in ex.details) {
 repeatOneMoreTime();
 }
 if ("i18n_code" in ex.details) {
 log(translate(ex.details["i18n_code"]));
 }
 }
 ...

 Details can be standard by convention or otherwise custom.
 (I can see that this can lead to messy proliferation of details, but at
 least solves most of the issues).
How would you know which keys are available in "ex.details", documentation? -- /Jacob Carlborg
Feb 19 2012
next sibling parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
Well, since keys would be string, you can define them at the base 
class.
So, FileException can define a few detail names that you know are 
used in its derived classes. And so, Exception can define detail 
names that are standard for all classes.

So it would look like:

class FileException {
     [..]
     static immutable details_filename = "filename";
     static immutable details_ispipe = "ispipe";
     [..]
}

class Exception {
     [..]
     static immutable details_transient = "transient";
     static immutable details_i18n_name = "i18n_name";
     [..]
}


So, you *know* that a portion of the tree supports certain 
details in the associative array, when you type the dot after the 
exception class name in an IDE with autocomplete, (or ctrl-N in 
Vim with the definition open ;-) ).
And you can document with ddoc those static variables. So the 
example would be now:

For instance:

  ...
  catch (Exception ex) {
      if (Exception.details_i18n_name in ex.details) {
          log(translate(ex.details[Exception.details_i18n_name]));
      }
      if (FileException.details_filename in ex.details) {
          log("This file is trouble: "
              ~ ex.details[FileException.details_filename]);
      }
      if (Exception.details_transient in ex.details) {
          repeatOneMoreTime();
      }
  }
  ...




On Sunday, 19 February 2012 at 14:54:29 UTC, Jacob Carlborg wrote:
 On 2012-02-19 13:27, Juan Manuel Cabo wrote:
 How about adding a string[string] or a variant[string] to the 
 Exception
 class, so one can know details about the subclassed exception 
 without
 downcasting? How ugly would that be?

 For instance:

 ...
 catch (Exception ex) {
 if ("transient" in ex.details) {
 repeatOneMoreTime();
 }
 if ("i18n_code" in ex.details) {
 log(translate(ex.details["i18n_code"]));
 }
 }
 ...

 Details can be standard by convention or otherwise custom.
 (I can see that this can lead to messy proliferation of 
 details, but at
 least solves most of the issues).
How would you know which keys are available in "ex.details", documentation?
Feb 19 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 8:54 AM, Jacob Carlborg wrote:
 On 2012-02-19 13:27, Juan Manuel Cabo wrote:
 How about adding a string[string] or a variant[string] to the Exception
 class, so one can know details about the subclassed exception without
 downcasting? How ugly would that be?

 For instance:

 ...
 catch (Exception ex) {
 if ("transient" in ex.details) {
 repeatOneMoreTime();
 }
 if ("i18n_code" in ex.details) {
 log(translate(ex.details["i18n_code"]));
 }
 }
 ...

 Details can be standard by convention or otherwise custom.
 (I can see that this can lead to messy proliferation of details, but at
 least solves most of the issues).
How would you know which keys are available in "ex.details", documentation?
Programmatically, too. A string templating engine would take the table and format it for the appropriate language. Andrei
Feb 19 2012
prev sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
Because the default behavior of the function regarding retries can be
undesirable.
What If you're willing to call the function for the first one which succeeds?
Implementing retry inside the function, which tries is a violation of
single-responsibility principle.

On Sun, Feb 19, 2012 at 10:54 AM, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:
 On Sun, Feb 19, 2012 at 12:43:58AM -0600, Andrei Alexandrescu wrote:
 On 2/18/12 8:00 PM, H. S. Teoh wrote:
 From this and other posts I'd say we need to design the base exception
classes better, for example by defining an overridable property
isTransient that tells caller code whether retrying might help.
Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename. On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
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? Why doesn't the function concerned simply retry on its own? Network stack code does that. It would be nightmarish to program network applications if you always have to implement retry on your own (much less use *exceptions* to handle them)! T -- Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry
-- Bye, Gor Gyolchanyan.
Feb 20 2012
prev sibling parent "Nick Sabalausky" <a a.a> writes:
Andrei Alexandrescu wrote:
catching by type is most of the time useless.
Andrei Alexandrescu wrote...
 I should note that the above does little in the way of putting an argument 
 to the table. It appeals to subjective qualifications ("poorly designed", 
 "well designed")
/facepalm Just once, could you be as incredibly pedantic with your own arguments as you are with everyone else's?
Feb 18 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 16:46:43 H. S. Teoh wrote:
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
I think that the problem stems from people frequently using exceptions incorrectly, and many of the C++ programmers probably haven't _ever_ seen them used correctly, since I don't think that it's very common for C++ programs to define exception hierarchies - especially not advanced ones like Java has. And when you see a lot of bad exception code, that tends to turn you off to them, and it definitely doesn't show you how to use them correctly. - Jonathan M Davis
Feb 18 2012
next sibling parent reply Ben Davis <entheh cantab.net> writes:
On 19/02/2012 00:48, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 16:46:43 H. S. Teoh wrote:
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
I think that the problem stems from people frequently using exceptions incorrectly, and many of the C++ programmers probably haven't _ever_ seen them used correctly, since I don't think that it's very common for C++ programs to define exception hierarchies - especially not advanced ones like Java has. And when you see a lot of bad exception code, that tends to turn you off to them, and it definitely doesn't show you how to use them correctly. - Jonathan M Davis
I can assure you they get misused in Java too. Most people write this: try { lots and lots and lots of stuff here } catch (Exception e) {} All they wanted to do was get round the compile error because they called Thread.sleep() which throws InterruptedException which you're required to catch, and they know they're never going to call Thread.interrupt(). What they end up doing is catching NullPointerException, ArrayIndexOutOfBoundsException and all the other things that really matter, and nasty bugs can go undiagnosed. Eclipse's auto-fix impoves things a little - it can generate this for you: try { ... } catch (InterruptedException e) { //For example //TODO: auto-generated catch block e.printStackTrace(); } But no one does their TODOs, and no one takes care to keep the console output short enough to be useful. You CAN write "throw new RuntimeException(e)", and you won't be required to catch that one. And then exceptions are incredibly useful, and I've even found rare cases where creating my own checked exception makes an API much safer. (I was processing code, and I had something like an 'uninline' operation that occasionally couldn't work, so the caller had to always have a backup plan in case the exception was thrown.) So it could be a good system, but no one has any idea what they're doing with it. If I had to improve the Java one, I suppose I would: - distinguish between 'bug' exceptions (e.g. null) and 'you're more likely to want to catch this' exceptions (e.g. IO). Maybe the bug ones should have been Errors, since people *usually* don't catch those (and are certainly never required to). As it stands, Errors are reserved for corrupt class files and other VM panic scenarios. - make sure the 'lazy' approach is a good one: the less you type, the fewer exceptions you catch, and the less likely you are to silence those exceptions at runtime. Probably the main problem with the exception hierarchy is that the short names tend to be the more general ones that are more dangerous to catch indiscriminately.
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 01:43:27 Ben Davis wrote:
 I can assure you they get misused in Java too. Most people write this:
Oh yes. It's not like the fact that their exception hierarchy is good means that people don't misuse it. Far too many people try and ignore errors in any language. And a solid exception hierarchy does nothing to stop people from being stupid and simply catching Exception to ignore. But if you have a good exception hierarchy, then you _can_ properly handle errors if you want to. My point was that because C++ doesn't have a standard exception hierarchy (even worse, they let you throw literally anything), it's much rarer to see one in C++ programs, let alone a good one. So, it's something that many C++ programmers have not been exposed to. Java programmers do not have that excuse. So, it's arguably that much worse when they misuse exceptions.
 - distinguish between 'bug' exceptions (e.g. null) and 'you're more
 likely to want to catch this' exceptions (e.g. IO). Maybe the bug ones
 should have been Errors, since people *usually* don't catch those (and
 are certainly never required to). As it stands, Errors are reserved for
 corrupt class files and other VM panic scenarios.
Well, in D, you'd use exceptions derived from Error (though null pointers still result in a good ol' segfault). Java's situation is marred by the whole thing with checked exceptions. D's basic is better in that regard, I think. It's the lack of a proper exception hierarchy where we're worse.
 - make sure the 'lazy' approach is a good one: the less you type, the
 fewer exceptions you catch, and the less likely you are to silence those
 exceptions at runtime. Probably the main problem with the exception
 hierarchy is that the short names tend to be the more general ones that
 are more dangerous to catch indiscriminately.
You mean, make the standard exception names really long and ugly, and the more specific ones short? e.g. Exception becomes something like StandardExceptionThatYouReallyShouldBeCatchingCatchTheDerivedType whereas specific exceptions are more like SpecifcEx (though that's a bit extreme)? Well, while that's a good sentiment, the very nature of more specific exceptions means that their namesj are likely to be more specific and therefore longer (especially when you have descriptive names). So, I'm not sure that that's ultimately all that reasonable, even if the basic idea is a good one. - Jonathan M Davis
Feb 18 2012
parent Ben Davis <entheh cantab.net> writes:
On 19/02/2012 01:54, Jonathan M Davis wrote:
 On Sunday, February 19, 2012 01:43:27 Ben Davis wrote:
 - distinguish between 'bug' exceptions (e.g. null) and 'you're more
 likely to want to catch this' exceptions (e.g. IO). Maybe the bug ones
 should have been Errors, since people *usually* don't catch those (and
 are certainly never required to). As it stands, Errors are reserved for
 corrupt class files and other VM panic scenarios.
Well, in D, you'd use exceptions derived from Error (though null pointers still result in a good ol' segfault). Java's situation is marred by the whole thing with checked exceptions. D's basic is better in that regard, I think. It's the lack of a proper exception hierarchy where we're worse.
Indeed, I'd noticed I was getting RangeErrors, not RangeExceptions :)
 - make sure the 'lazy' approach is a good one: the less you type, the
 fewer exceptions you catch, and the less likely you are to silence those
 exceptions at runtime. Probably the main problem with the exception
 hierarchy is that the short names tend to be the more general ones that
 are more dangerous to catch indiscriminately.
You mean, make the standard exception names really long and ugly, and the more specific ones short? e.g. Exception becomes something like StandardExceptionThatYouReallyShouldBeCatchingCatchTheDerivedType whereas specific exceptions are more like SpecifcEx (though that's a bit extreme)? Well, while that's a good sentiment, the very nature of more specific exceptions means that their namesj are likely to be more specific and therefore longer (especially when you have descriptive names). So, I'm not sure that that's ultimately all that reasonable, even if the basic idea is a good one.
LOL - yeah, there's a reason I didn't propose any actual names. It's not an easy problem to solve. But I felt the psychology angle might not have been covered and was worth mentioning all the same. You could name the class "AllExceptions" though. But as the insidious ones like off-by-ones are Errors (or segfaults), it's definitely a much smaller problem in D than in Java.
Feb 19 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 02:43, Ben Davis a écrit :
 On 19/02/2012 00:48, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 16:46:43 H. S. Teoh wrote:
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
I think that the problem stems from people frequently using exceptions incorrectly, and many of the C++ programmers probably haven't _ever_ seen them used correctly, since I don't think that it's very common for C++ programs to define exception hierarchies - especially not advanced ones like Java has. And when you see a lot of bad exception code, that tends to turn you off to them, and it definitely doesn't show you how to use them correctly. - Jonathan M Davis
I can assure you they get misused in Java too. Most people write this: try { lots and lots and lots of stuff here } catch (Exception e) {}
You raise 2 prblems of java exception system. First, RuntimeException is a subclass of Exception. Second, you have to catch all Exceptions except RuntimeException and Error. We can consider what is good in Java without keeping the bad part.
Feb 19 2012
prev sibling parent reply "so" <so so.so> writes:
On Sunday, 19 February 2012 at 00:50:07 UTC, Jonathan M Davis 
wrote:
 On Saturday, February 18, 2012 16:46:43 H. S. Teoh wrote:
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
I think that the problem stems from people frequently using exceptions incorrectly, and many of the C++ programmers probably haven't _ever_ seen them used correctly, since I don't think that it's very common for C++ programs to define exception hierarchies - especially not advanced ones like Java has. And when you see a lot of bad exception code, that tends to turn you off to them, and it definitely doesn't show you how to use them correctly. - Jonathan M Davis
Problem is, "no one" using exception handling correctly including language experts. There is no consensus on where they are useful or not. Neither articles nor codes help you. Go read every single one of them and come back and code something. I invested a lot of time on it, yet i am now using it when i need an aggressive assert. It is a great idea but incomplete.
Feb 19 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 9:50 AM, so wrote:
 On Sunday, 19 February 2012 at 00:50:07 UTC, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 16:46:43 H. S. Teoh wrote:
 I can't believe something this simple has to be explained so
 elaborately. I thought all of us here knew how to use OO??
I think that the problem stems from people frequently using exceptions incorrectly, and many of the C++ programmers probably haven't _ever_ seen them used correctly, since I don't think that it's very common for C++ programs to define exception hierarchies - especially not advanced ones like Java has. And when you see a lot of bad exception code, that tends to turn you off to them, and it definitely doesn't show you how to use them correctly. - Jonathan M Davis
Problem is, "no one" using exception handling correctly including language experts. There is no consensus on where they are useful or not. Neither articles nor codes help you.
Quite so. That's why slapping with the book won't help - there's no book! :o) Andrei
Feb 19 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 22:54:37 Jose Armando Garcia wrote:
 You basically agree that we need error codes inside the exception to
 handle database errors correctly. What about POSIX error codes?
 Basically in some cases you are solving the problem using inheritance
 in other cases you are solving the problem by switching on a variable.
There is a world of difference between having a specific exception type have an error code from an external source (e.g. a database or POSIX) and making _all_ exceptions function via error codes. And personally, I'd argue that in many cases, the correct thing to do would be to have a base class exception type which has the error code in it (e.g. FileException) and then have derived exceptions where the one you use depends on what the error code actually was. Then additional information pertaining to that specific error code could be put in the derived exception, and you can catch the exceptions by type rather than having to resort to switching on the error type in the exception. That way, you can actually take advantage of the exception hierarchy for controlling how you handle the exception, and you have additional information pertaining to what went wrong. And if you want access to the original error code, you have it. You can even just catch the base exception type and use a switch if you want to. But you don't have to. - Jonathan M Davis
Feb 18 2012
prev sibling next sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Sat, Feb 18, 2012 at 11:19 PM, Jonathan M Davis <jmdavisProg gmx.com> wrote:
 On Saturday, February 18, 2012 22:54:37 Jose Armando Garcia wrote:
 You basically agree that we need error codes inside the exception to
 handle database errors correctly. What about POSIX error codes?
 Basically in some cases you are solving the problem using inheritance
 in other cases you are solving the problem by switching on a variable.
There is a world of difference between having a specific exception type have an error code from an external source (e.g. a database or POSIX) and making _all_ exceptions function via error codes. And personally, I'd argue that in many cases, the correct thing to do would be to have a base class exception type which has the error code in it (e.g. FileException) and then have derived exceptions where the one you use depends on what the error code actually was. Then additional information pertaining to that specific error code could be put in the derived exception, and you can catch the exceptions by type rather than having to resort to switching on the error type in the exception. That way, you can actually take advantage of the exception hierarchy for controlling how you handle the exception, and you have additional information pertaining to what went wrong. And if you want access to the original error code, you have it. You can even just catch the base exception type and use a switch if you want to. But you don't have to.
I think that is a good compromise. The one thing we need to solve is that for example Oracle's error code may collide with POSIX error code. This is why I suggested using namespace but there may be other solutions. Thanks, -Jose
 - Jonathan M Davis
Feb 18 2012
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhos0l$102t$1 digitalmars.com...
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca
What the hell...? It says "5 notes on commit", but only 2 are shown (both by alexrp). (And yes, that's with a modern browser with JS on.)
 From experience I humbly submit that catching by type is most of the time 
 useless.
Another one for the file of "Crazy shit Andrei says" ;) From experience, I (and clearly many others here) find a sparse, flat exception hierarchy to be problematic and limiting. But even with a rich detailed exception hierarchy, those (ie, Andrei) who want to limit themselves to catching course-grained exceptions can do so, thanks to the nature of subtyping. So why are we even discussing this?
Feb 18 2012
parent reply "Zach" <mips intel.arm> writes:
On Sunday, 19 February 2012 at 01:29:40 UTC, Nick Sabalausky 
wrote:
 Another one for the file of "Crazy shit Andrei says" ;)

 From experience, I (and clearly many others here) find a 
 sparse, flat exception hierarchy to be problematic and 
 limiting. But even with a rich detailed exception hierarchy, 
 those (ie, Andrei) who want to limit themselves to catching 
 course-grained exceptions can do so, thanks to the nature of 
 subtyping. So why are we even discussing this?
How about we revisit ancient design decisions which once held true... but no longer is the case due to our "god-language" being more expressive? In my experience an "exception hierarchy" is never good enough, it suffers from the same problems as most frameworks also do... they simplify/destroy too much info of from the original error. ex why don't we throw a closure? Of course we could go crazy with mixins and CTFE,CTTI,RTTI aswell... imho the goal should not be to do as good as java, the goal is progress! Java should copy our design if anything... we could have a very rich exception structure... without the need for a hierarchy. try(name) // try extended to support full closure syntax { DIR* dir = opendir(toStringz(name)); if(dir==0 && errno==ENOTDIR) throw; // throws the entire try block as a closure }
Feb 18 2012
next sibling parent reply Sean Cavanaugh <WorksOnMyMachine gmail.com> writes:
On 2/18/2012 7:56 PM, Zach wrote:
 On Sunday, 19 February 2012 at 01:29:40 UTC, Nick Sabalausky wrote:
 Another one for the file of "Crazy shit Andrei says" ;)

 From experience, I (and clearly many others here) find a sparse, flat
 exception hierarchy to be problematic and limiting. But even with a
 rich detailed exception hierarchy, those (ie, Andrei) who want to
 limit themselves to catching course-grained exceptions can do so,
 thanks to the nature of subtyping. So why are we even discussing this?
How about we revisit ancient design decisions which once held true... but no longer is the case due to our "god-language" being more expressive? In my experience an "exception hierarchy" is never good enough, it suffers from the same problems as most frameworks also do... they simplify/destroy too much info of from the original error. ex why don't we throw a closure? Of course we could go crazy with mixins and CTFE,CTTI,RTTI aswell... imho the goal should not be to do as good as java, the goal is progress! Java should copy our design if anything... we could have a very rich exception structure... without the need for a hierarchy. try(name) // try extended to support full closure syntax { DIR* dir = opendir(toStringz(name)); if(dir==0 && errno==ENOTDIR) throw; // throws the entire try block as a closure }
My C++ experience revolves around some rather large codebases that do not have exceptions enabled at all. The more I look into seeing what it would take to start using them, all I see are some pretty huge downsides: The exact nature of the error for a lot of exceptions for like File I/O are very platform specific. Throwing these objects effectively bubbles up highly platform specific data up to the caller, which infects the caller with the need to deal with platform specific data (posix codes vs GetLastError, vs various return values from the functions that failed etc). This is not a good thing for keeping a codebase isolated from platform differences. There has been seeing a huge shift in needing to make everything I work on threaded on a fine-grained level (game simulation, graphics rendering, various physics based systems and simulations, networking etc). The way exceptions work, unwind the stack until someone 'cares', is not a good model in this environment. Ideally everything is on its own thread or exists as a job in a job queue, or exists as some kind of node in a flow based system. In these environments there is no caller on the _stack_ to unwind to. You have to package up another async message with the failure and handle it somewhere else. In many ways this is superior, as it solves the problem exceptions were created to solve : route errors to the proper code that 'cares'. In the Von Neumann model this has been made difficult by the stack itself. Thinking of exceptions as they are currently implemented in Java, C++, D, etc is automatically artificially constraining how they need to work. Exceptions as they are now exist as a way to jump up the stack some arbitrary amount (until a handler is found). The real problem that needs solving is 'route errors the most appropriate place available' with the constraint of 'keep the program in a working state'. I would prefer a system that works equally well in both kinds of environments, as we are already mixing the styles, and switching code from one to the other requires a large amount of refactoring due to the differences in error handling.
Feb 18 2012
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Sean Cavanaugh" <WorksOnMyMachine gmail.com> wrote in message 
news:jhpr9t$29i$1 digitalmars.com...
 My C++ experience revolves around some rather large codebases that do not 
 have exceptions enabled at all.  The more I look into seeing what it would 
 take to start using them, all I see are some pretty huge downsides:

 The exact nature of the error for a lot of exceptions for like File I/O 
 are very platform specific.  Throwing these objects effectively bubbles up 
 highly platform specific data up to the caller, which infects the caller 
 with the need to deal with platform specific data (posix codes vs 
 GetLastError, vs various return values from the functions that failed 
 etc).  This is not a good thing for keeping a codebase isolated from 
 platform differences.
Sounds like you're just dealing with some notably bad designed exceptions. I've never come across such a problem, and I've used exeptions extensively.
Feb 18 2012
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Sean Cavanaug:

 In the Von Neumann model this has been made difficult by the stack 
 itself.  Thinking of exceptions as they are currently implemented in 
 Java, C++, D, etc is automatically artificially constraining how they 
 need to work.
It's interesting to take a look at how "exceptions" are designed in Lisp: http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html Bye, bearophile
Feb 18 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 11:09:23PM -0500, bearophile wrote:
 Sean Cavanaug:
 
 In the Von Neumann model this has been made difficult by the stack
 itself.  Thinking of exceptions as they are currently implemented in
 Java, C++, D, etc is automatically artificially constraining how
 they need to work.
It's interesting to take a look at how "exceptions" are designed in Lisp: http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
[...] I'm surprised nobody responded to this. I read through the article a bit, and it does present some interesting concepts that we may be able to make use of in D. Here's a brief (possibly incomplete) summary: One problem with the try-throw-catch paradigm is that whenever an exception is raised, the stack unwinds up some number of levels in the call stack. By the time it gets to the catch{} block, the context in which the problem happened is already long-gone, and there is no other recourse but to abort the operation, or try it again from scratch. There is no way to recover from the problem by, say, trying to fix it *in the context in which it happened* and then continuing with the operation. Say P calls Q, Q calls R, and R calls S. S finds a problem that prevents it from doing what R expects it to do, so it throws an exception. R doesn't know what to do, so it propagates the exception to Q. Q doesn't know what to do either, so it propagates the exception to P. By the time P gets to know about the problem, the execution context of S is long gone; the operation that Q was trying to perform has already been aborted. There's no way to recover except to repeat a potentially very expensive operation. The way Lisp handles this is by something called "conditions". I won't get into the definitions and stuff (just read the article), but the idea is this: - When D encounters a problem, it signals a "condition". - Along with the condition, it may register 0 or more "restarts", basically predefined methods of recovering from the condition. - The runtime then tries to recover from the condition by: - Checking to see if there's a handler registered for this condition. If there is, invoke the most recently registered one *in the context of the function that triggered the condition*. - If there's no handler, unwind the stack and propagate the condition to the caller. - There are two kinds of handlers: - The equivalent of a "catch": matches some subset of conditions that propagated to that point in the code. Some stack unwinding may already have taken place, so these are equivalent to catch block in D. - Pre-bound handlers: these are registered with the runtime condition handler before the condition is triggered (possibly very high up the call stack). They are invoked *in the context of the code that triggered the condition*. Their primary use is to decide which of the restarts associated with the condition should be used to recover from it. The pre-bound handlers are very interesting. They allow in-place recovery by having high-level callers to decide what to do, *without unwinding the stack*. Here's an example: LoadConfig() is a function that loads an application's configuration files, parses them, and sets up some runtime objects based on configuration file settings. LoadConfig calls a bunch of functions to accomplish what it does, among which is ParseConfig(). ParseConfig() in turn calls ParseConfigItem() for each configuration item in the config file, to set up the runtime objects associated with that item. ParseConfigItem() calls DecodeUTF() to convert the configuration file's text representation from, say, UTF-8 to dchar. So the call stack looks like this: LoadConfig ParseConfig ParseConfigItem DecodeUTF Now suppose the config file has some UTF encoding errors. This causes DecodeUTF to throw a DecodingError. ParseConfigItem can't go on, since that configuration item is mangled. So it propagates DecodingError to ParseConfig. Now, ParseConfig could simply abort, but using the idea of prebound handlers, it can actually offer two ways of recovering: (1) SkipConfigItem, to simply skip the mangled config item and process the rest of the config file as usual, or (2) ReparseConfigItem, to allow custom code to manually fix a bad config item and reprocess it. The problem is, ParseConfig doesn't know which action to take. It's too low-level to make that sort of decision. You need higher-level code, that knows what the application needs to do, to decide that. But ParseConfig can't just propagate the exception to said high-level code, because if it does, parsing of the entire config file is aborted and will have to be restarted from scratch. The solution is to have the higher-level code register a delegate with the exception system. Something like this: // NOTE: not real D code void main() { registerHandler(auto delegate(ParseError e) { if (can_repair_item(e.item)) { return e.ReparseConfigItem( repairConfigItem(e.item)); } else { return e.SkipConfigItem(); } }); ParseConfig(configfile); } Now when ParseConfig encounters a problem, it signals a ParseError object with two options for recovery: ReparseConfigItem and SkipConfigItem. It doesn't try to fix the problem on its own, but it lets the delegate from main() make that decision. The runtime exception system then sees if there's a matching handler, and calls the handler with the ParseError to determine which course of action to take. If no handler is found, or the handler decides to abort, then ParseError is propagated to the caller with stack unwinding. So ParseConfig might look something like this: // NOTE: not real D code auto ParseConfig(...) { foreach (item; config_items) { try { // Note: not real proposed syntax, this is just // to show the semantics of the mechanism: restart: auto objs = ParseConfigItem(item); SetupConfigObjects(objs); } catch(ParseConfigItemError) { // Note: not real proposed syntax, this is just // to show the semantics of the mechanism: ConfigError e; e.ReparseConfigItem = void delegate(ConfigItem fixedItem) { goto restart; }; e.SkipConfigItem = void delegate() { continue; } // This will unwind stack if no handler is // found, or handler decides to propagate // exception. handleError(e); } } } OK, so it looks real ugly right now. But if this mechanism is built into the language, we could have much better syntax, something like this: auto ParseConfig(...) { foreach (item; config_items) { try { auto objs = ParseConfigItem(item); SetupConfigObjects(objs); } recoverBy ReparseConfigItem(fixedItem) { item = fixedItem; restart; // restarts try{} block } recoverBy SkipConfigItem() { setDefaultConfigObjs(); continue; // continues foreach loop } } } This is just a rough sketch syntax, just to show the idea. It can of course be improved upon. T -- Nobody is perfect. I am Nobody. -- pepoluan, GKC forum
Feb 19 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-20 02:03, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:09:23PM -0500, bearophile wrote:
 Sean Cavanaug:

 In the Von Neumann model this has been made difficult by the stack
 itself.  Thinking of exceptions as they are currently implemented in
 Java, C++, D, etc is automatically artificially constraining how
 they need to work.
It's interesting to take a look at how "exceptions" are designed in Lisp: http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
[...] I'm surprised nobody responded to this. I read through the article a bit, and it does present some interesting concepts that we may be able to make use of in D. Here's a brief (possibly incomplete) summary: One problem with the try-throw-catch paradigm is that whenever an exception is raised, the stack unwinds up some number of levels in the call stack. By the time it gets to the catch{} block, the context in which the problem happened is already long-gone, and there is no other recourse but to abort the operation, or try it again from scratch. There is no way to recover from the problem by, say, trying to fix it *in the context in which it happened* and then continuing with the operation. Say P calls Q, Q calls R, and R calls S. S finds a problem that prevents it from doing what R expects it to do, so it throws an exception. R doesn't know what to do, so it propagates the exception to Q. Q doesn't know what to do either, so it propagates the exception to P. By the time P gets to know about the problem, the execution context of S is long gone; the operation that Q was trying to perform has already been aborted. There's no way to recover except to repeat a potentially very expensive operation. The way Lisp handles this is by something called "conditions". I won't get into the definitions and stuff (just read the article), but the idea is this: - When D encounters a problem, it signals a "condition". - Along with the condition, it may register 0 or more "restarts", basically predefined methods of recovering from the condition. - The runtime then tries to recover from the condition by: - Checking to see if there's a handler registered for this condition. If there is, invoke the most recently registered one *in the context of the function that triggered the condition*. - If there's no handler, unwind the stack and propagate the condition to the caller. - There are two kinds of handlers: - The equivalent of a "catch": matches some subset of conditions that propagated to that point in the code. Some stack unwinding may already have taken place, so these are equivalent to catch block in D. - Pre-bound handlers: these are registered with the runtime condition handler before the condition is triggered (possibly very high up the call stack). They are invoked *in the context of the code that triggered the condition*. Their primary use is to decide which of the restarts associated with the condition should be used to recover from it. The pre-bound handlers are very interesting. They allow in-place recovery by having high-level callers to decide what to do, *without unwinding the stack*. Here's an example: LoadConfig() is a function that loads an application's configuration files, parses them, and sets up some runtime objects based on configuration file settings. LoadConfig calls a bunch of functions to accomplish what it does, among which is ParseConfig(). ParseConfig() in turn calls ParseConfigItem() for each configuration item in the config file, to set up the runtime objects associated with that item. ParseConfigItem() calls DecodeUTF() to convert the configuration file's text representation from, say, UTF-8 to dchar. So the call stack looks like this: LoadConfig ParseConfig ParseConfigItem DecodeUTF Now suppose the config file has some UTF encoding errors. This causes DecodeUTF to throw a DecodingError. ParseConfigItem can't go on, since that configuration item is mangled. So it propagates DecodingError to ParseConfig. Now, ParseConfig could simply abort, but using the idea of prebound handlers, it can actually offer two ways of recovering: (1) SkipConfigItem, to simply skip the mangled config item and process the rest of the config file as usual, or (2) ReparseConfigItem, to allow custom code to manually fix a bad config item and reprocess it. The problem is, ParseConfig doesn't know which action to take. It's too low-level to make that sort of decision. You need higher-level code, that knows what the application needs to do, to decide that. But ParseConfig can't just propagate the exception to said high-level code, because if it does, parsing of the entire config file is aborted and will have to be restarted from scratch. The solution is to have the higher-level code register a delegate with the exception system. Something like this: // NOTE: not real D code void main() { registerHandler(auto delegate(ParseError e) { if (can_repair_item(e.item)) { return e.ReparseConfigItem( repairConfigItem(e.item)); } else { return e.SkipConfigItem(); } }); ParseConfig(configfile); } Now when ParseConfig encounters a problem, it signals a ParseError object with two options for recovery: ReparseConfigItem and SkipConfigItem. It doesn't try to fix the problem on its own, but it lets the delegate from main() make that decision. The runtime exception system then sees if there's a matching handler, and calls the handler with the ParseError to determine which course of action to take. If no handler is found, or the handler decides to abort, then ParseError is propagated to the caller with stack unwinding. So ParseConfig might look something like this: // NOTE: not real D code auto ParseConfig(...) { foreach (item; config_items) { try { // Note: not real proposed syntax, this is just // to show the semantics of the mechanism: restart: auto objs = ParseConfigItem(item); SetupConfigObjects(objs); } catch(ParseConfigItemError) { // Note: not real proposed syntax, this is just // to show the semantics of the mechanism: ConfigError e; e.ReparseConfigItem = void delegate(ConfigItem fixedItem) { goto restart; }; e.SkipConfigItem = void delegate() { continue; } // This will unwind stack if no handler is // found, or handler decides to propagate // exception. handleError(e); } } } OK, so it looks real ugly right now. But if this mechanism is built into the language, we could have much better syntax, something like this: auto ParseConfig(...) { foreach (item; config_items) { try { auto objs = ParseConfigItem(item); SetupConfigObjects(objs); } recoverBy ReparseConfigItem(fixedItem) { item = fixedItem; restart; // restarts try{} block } recoverBy SkipConfigItem() { setDefaultConfigObjs(); continue; // continues foreach loop } } } This is just a rough sketch syntax, just to show the idea. It can of course be improved upon. T
I was actually thinking something similar, the part about registering exception handlers, i.e. using "registerHandler". -- /Jacob Carlborg
Feb 20 2012
prev sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Zach" <mips intel.arm> wrote in message 
news:tpxbylbgpvarpzzlpuuf forum.dlang.org...
 On Sunday, 19 February 2012 at 01:29:40 UTC, Nick Sabalausky wrote:
 Another one for the file of "Crazy shit Andrei says" ;)

 From experience, I (and clearly many others here) find a sparse, flat 
 exception hierarchy to be problematic and limiting. But even with a rich 
 detailed exception hierarchy, those (ie, Andrei) who want to limit 
 themselves to catching course-grained exceptions can do so, thanks to the 
 nature of subtyping. So why are we even discussing this?
How about we revisit ancient design decisions which once held true... but no longer is the case due to our "god-language" being more expressive?
I'm fine with that when there's an alternative suggested that makes sense, or when there's an *actual* problem identified with the current way.
 In my experience an "exception hierarchy" is never good enough, it suffers 
 from the same problems as most frameworks also do... they simplify/destroy 
 too much info of from the original error.
The only problem I've ever had with them is that there's no templated catch blocks, so you can't handle two different exceptions with the same code without violating DRY or worse: catching the common base type and rethrowing when it's not what you wanted. Toss in templated catch blocks, and I've have no problem at all.
 ex why don't we throw a closure?
Haxe lets you throw literally anything. It's a useless pain in the ass that's along the same lines as VB being "flexible" enough to "allow" you to have arrays indexed from whatever random number you want. Adds no benefit, and just forces people to make sure to handle extra scenarios that aren't useful in the first place.
 Of course we could go crazy with mixins and CTFE,CTTI,RTTI aswell... imho 
 the goal should not be to do as good as java, the goal is progress! Java 
 should copy our design if anything... we could have a very rich exception 
 structure... without the need for a hierarchy.

 try(name) // try extended to support full closure syntax
 {
   DIR* dir = opendir(toStringz(name));

   if(dir==0 && errno==ENOTDIR)
     throw; // throws the entire try block as a closure
 }
What would the use of that be?
Feb 18 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:jhprac$2aj$1 digitalmars.com...
 The only problem I've ever had with them is that there's no templated 
 catch blocks, so you can't handle two different exceptions with the same 
 code without violating DRY or worse: catching the common base type and 
 rethrowing when it's not what you wanted. Toss in templated catch blocks, 
 and I've have no problem at all.
Do you mean something like this? try { something(); } catch (e : ThisException, ThatException, OtherException) { static assert(is(typeof(e) == CommonType!(ThisException, ThatException, OtherException)); } catch (Exception e) { // Every other type derived from Exception } Or do you think the full power to be able to template catch blocks as if they were functions would be useful for something?
Feb 18 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 16:59:49 Daniel Murphy wrote:
 "Nick Sabalausky" <a a.a> wrote in message
 news:jhprac$2aj$1 digitalmars.com...
 
 The only problem I've ever had with them is that there's no templated
 catch blocks, so you can't handle two different exceptions with the same
 code without violating DRY or worse: catching the common base type and
 rethrowing when it's not what you wanted. Toss in templated catch blocks,
 and I've have no problem at all.
Do you mean something like this? try { something(); } catch (e : ThisException, ThatException, OtherException) { static assert(is(typeof(e) == CommonType!(ThisException, ThatException, OtherException)); } catch (Exception e) { // Every other type derived from Exception } Or do you think the full power to be able to template catch blocks as if they were functions would be useful for something?
I think that being able to have a catch block which took multiple exception types would be plenty. There are times when it would be very valuable to be able to use the same catch block for multiple exceptions without having to catch their base type (which would then potentially catch other exceptions which you didn't want to catch). So, something like that second catch block that you have there would be very valuable. - Jonathan M Davis
Feb 18 2012
next sibling parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Jonathan M Davis" <jmdavisProg gmx.com> wrote in message 
news:mailman.582.1329634661.20196.digitalmars-d puremagic.com...
 On Sunday, February 19, 2012 16:59:49 Daniel Murphy wrote:
 "Nick Sabalausky" <a a.a> wrote in message
 news:jhprac$2aj$1 digitalmars.com...

 The only problem I've ever had with them is that there's no templated
 catch blocks, so you can't handle two different exceptions with the 
 same
 code without violating DRY or worse: catching the common base type and
 rethrowing when it's not what you wanted. Toss in templated catch 
 blocks,
 and I've have no problem at all.
Do you mean something like this? try { something(); } catch (e : ThisException, ThatException, OtherException) { static assert(is(typeof(e) == CommonType!(ThisException, ThatException, OtherException)); } catch (Exception e) { // Every other type derived from Exception } Or do you think the full power to be able to template catch blocks as if they were functions would be useful for something?
I think that being able to have a catch block which took multiple exception types would be plenty. There are times when it would be very valuable to be able to use the same catch block for multiple exceptions without having to catch their base type (which would then potentially catch other exceptions which you didn't want to catch). So, something like that second catch block that you have there would be very valuable. - Jonathan M Davis
I assume you mean the _first_ catch block? By the looks of it java 7 has this with an awful syntax. Maybe we should introduce implicit 'catch fallthrough': void fun() { try { something; } catch (ExceptionA) {} // falls through implicitly catch (ExpcetionB) { // handle both A and B break; } catch (ExceptionC) { // handle exception C break; } }
Feb 18 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 18:44:30 Daniel Murphy wrote:
 I assume you mean the _first_ catch block?
Yes. I should have been more specific.
 By the looks of it java 7 has this with an awful syntax.
 
 Maybe we should introduce implicit 'catch fallthrough':
 
 void fun()
 {
     try { something; }
     catch (ExceptionA) {} // falls through implicitly
     catch (ExpcetionB)
     { // handle both A and B
         break;
     }
     catch (ExceptionC)
     {
         // handle exception C
         break;
     }
 }
That wouldn't work, because it would make it so that ignore an exception in the middle if you wanted to (much as that's generally bad practice). It would also have the problem that you couldn't access the exception itself, and while you obviously wouldn't be operating on the exception's exact type regardless, you might want to iteract with the functions on a common based class. So, while at first glance, it seems like a good idea, I think that it has too many issues as-is to work. It might be possible to adjust the idea to make it workable though. Right now, it's possible to do it via mixins or calling a function inside the catch, but doing something similar to this would certainly be nice, assuming that we could sort out the kinks. - Jonathan M Davis
Feb 18 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Jonathan M Davis" <jmdavisProg gmx.com> wrote in message 
news:mailman.585.1329637995.20196.digitalmars-d puremagic.com...
 On Sunday, February 19, 2012 18:44:30 Daniel Murphy wrote:
 I assume you mean the _first_ catch block?
Yes. I should have been more specific.
 By the looks of it java 7 has this with an awful syntax.

 Maybe we should introduce implicit 'catch fallthrough':

 void fun()
 {
     try { something; }
     catch (ExceptionA) {} // falls through implicitly
     catch (ExpcetionB)
     { // handle both A and B
         break;
     }
     catch (ExceptionC)
     {
         // handle exception C
         break;
     }
 }
That wouldn't work, because it would make it so that ignore an exception in the middle if you wanted to (much as that's generally bad practice). It would also have the problem that you couldn't access the exception itself, and while you obviously wouldn't be operating on the exception's exact type regardless, you might want to iteract with the functions on a common based class. So, while at first glance, it seems like a good idea, I think that it has too many issues as-is to work. It might be possible to adjust the idea to make it workable though. Right now, it's possible to do it via mixins or calling a function inside the catch, but doing something similar to this would certainly be nice, assuming that we could sort out the kinks. - Jonathan M Davis
I wasn't really serious about implicit fallthrough. 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
Feb 19 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Feb 19 2012
next sibling parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
Hello D community! This is my first post!! I hope I can bring 
clarity to all this. If not, I apologize.

Some time ago I researched the best way to classify exceptions 
and build a hierarchy. I came up with the following rules:


1) At the top level, there would be RecoverableExceptions and 
FatalExceptions (or you can call them something like 
CatcheableException and FatalExceptions, or Exception and Error).

2) Fatal exceptions shouldn't be catched. They imply that the 
program lost basic guarantees to go on (memory corruption, 
missing essential file, etc.). You could catch them if you wanted 
to, but it makes no sense other than at your top-level method 
(main(), etc.).

3) A RecoverableException can be converted to a FatalException by 
rethrowing it, once a catch decides so. You shouldn't do the 
reverse: a FatalException never should be converted to a 
RecoverableException.

4) It makes no sense to subclass FatalExceptions since there 
won't be a catch that groups them in a base type (since they are 
not catcheable).

5) Only create a new RecoverableException class type if it makes 
sense to write a catch for it alone. Otherwise, use an 
preexisting type.

6) Only group RecoverableExceptions in a category if it makes 
sense to write a catch for that category. Please don't group them 
because its fancy or "cleaner", that is a bad reason.


Who decides when an Exception is Unrecoverable? Library code 
almost never decides it, since an exception is only unrecoverable 
if the whole_program_invariant got broken, and libraries are only 
a part of a program. So they will tend to throw 
RecoverableExceptions, which can be reconverted to Unrecoverable 
by the program.

In some cases, it is clear that an exception is Unrecoverable. 
When you call a function without satisfying its arguments 
precondition (ie: null argument) the only way to fix that is by 
editing the program. You shouldn't have called it like that in 
the first place, why would you? So you let the 
UnrecoverableException bubble up to your main function, and log 
its stacktrace to fix it.

Unrecoverable means the program got 'tainted', basic guarantees 
got broken (possible memory corruption, etc.). Most exceptions 
will be Recoverable.

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 wrote these rules some time ago. Please excuse me if they come 
off a bit pedantic!!!!!!!!!!!! Its all only a clarifying 
convention.


According to all this:

* FileNotFoundException is useful. It tells you what happened. It 
is a RecoverableException (under my convention) because until it 
reaches the program, the library doesn't know if the program can 
recover from that (it could be a system missing file, or just a 
document the user asked for).

* DiskFailureException is only useful if someone can write a 
catch for it. If so, then it is a RecoverableException. Only the 
program can decide if it broke basic guarantees.

* Most argument exceptions are Unrecoverable. A function throwing 
shouldn't have been called like that in the first place. The only 
fix is to go back to editing the program. (precondition broken).


Another thing: you cannot decide whether an exception is 
Unrecoverable based only on whether the thing that got broken is 
the postcondition of a function. It is the 
whole_program_invariant that decides that. For instance:  
findStuff(someStuff)  might not know if someStuff is important 
enough for the stability of the program if not found. The 
postcondition is broken if it doesn't return the Stuff. That 
might be recoverable.

And PLEASE: don't make classifications by the point of view of 
the cause of the problem. DO make classifications by the point of 
view of the fixing/recovery of the problem; the catch block is 
who you are talking to. FileNotFoundBecauseFilesystemUnmounted is 
worthless.

So, to sum up: (1) it makes no sense to subclass fatal 
exceptions, and (2) never subclass a RecoverableException if you 
are not helping a catch block with that (but please do if it aids 
recovery).

..so verbose and pedantic for my first post... yikes.. i beg 
forgiveness!!!




On Sunday, 19 February 2012 at 09:27:48 UTC, 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
Feb 19 2012
next sibling parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
I uploaded my little 2003 article on exception categorization to 
github,
where I detailed my whole point_of_view_of_the_catch and 
whole_program_invariant phylosophy for organizing clasess, and 
compared
C++, .NET and Java hierarchies. Its not a professional article, 
and I never wrote a translation to english:

https://github.com/jmcabo/ExceptionArticle/blob/master/articuloExcepciones_v2.pdf

--jm


On Sunday, 19 February 2012 at 10:30:45 UTC, Juan Manuel Cabo 
wrote:
 Hello D community! This is my first post!! I hope I can bring 
 clarity to all this. If not, I apologize.

 Some time ago I researched the best way to classify exceptions 
 and build a hierarchy. I came up with the following rules:


 1) At the top level, there would be RecoverableExceptions and 
 FatalExceptions (or you can call them something like 
 CatcheableException and FatalExceptions, or Exception and 
 Error).

 2) Fatal exceptions shouldn't be catched. They imply that the 
 program lost basic guarantees to go on (memory corruption, 
 missing essential file, etc.). You could catch them if you 
 wanted to, but it makes no sense other than at your top-level 
 method (main(), etc.).

 3) A RecoverableException can be converted to a FatalException 
 by rethrowing it, once a catch decides so. You shouldn't do the 
 reverse: a FatalException never should be converted to a 
 RecoverableException.

 4) It makes no sense to subclass FatalExceptions since there 
 won't be a catch that groups them in a base type (since they 
 are not catcheable).

 5) Only create a new RecoverableException class type if it 
 makes sense to write a catch for it alone. Otherwise, use an 
 preexisting type.

 6) Only group RecoverableExceptions in a category if it makes 
 sense to write a catch for that category. Please don't group 
 them because its fancy or "cleaner", that is a bad reason.


 Who decides when an Exception is Unrecoverable? Library code 
 almost never decides it, since an exception is only 
 unrecoverable if the whole_program_invariant got broken, and 
 libraries are only a part of a program. So they will tend to 
 throw RecoverableExceptions, which can be reconverted to 
 Unrecoverable by the program.

 In some cases, it is clear that an exception is Unrecoverable. 
 When you call a function without satisfying its arguments 
 precondition (ie: null argument) the only way to fix that is by 
 editing the program. You shouldn't have called it like that in 
 the first place, why would you? So you let the 
 UnrecoverableException bubble up to your main function, and log 
 its stacktrace to fix it.

 Unrecoverable means the program got 'tainted', basic guarantees 
 got broken (possible memory corruption, etc.). Most exceptions 
 will be Recoverable.

 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 wrote these rules some time ago. Please excuse me if they 
 come off a bit pedantic!!!!!!!!!!!! Its all only a clarifying 
 convention.


 According to all this:

 * FileNotFoundException is useful. It tells you what happened. 
 It is a RecoverableException (under my convention) because 
 until it reaches the program, the library doesn't know if the 
 program can recover from that (it could be a system missing 
 file, or just a document the user asked for).

 * DiskFailureException is only useful if someone can write a 
 catch for it. If so, then it is a RecoverableException. Only 
 the program can decide if it broke basic guarantees.

 * Most argument exceptions are Unrecoverable. A function 
 throwing shouldn't have been called like that in the first 
 place. The only fix is to go back to editing the program. 
 (precondition broken).


 Another thing: you cannot decide whether an exception is 
 Unrecoverable based only on whether the thing that got broken 
 is the postcondition of a function. It is the 
 whole_program_invariant that decides that. For instance:  
 findStuff(someStuff)  might not know if someStuff is important 
 enough for the stability of the program if not found. The 
 postcondition is broken if it doesn't return the Stuff. That 
 might be recoverable.

 And PLEASE: don't make classifications by the point of view of 
 the cause of the problem. DO make classifications by the point 
 of view of the fixing/recovery of the problem; the catch block 
 is who you are talking to. 
 FileNotFoundBecauseFilesystemUnmounted is worthless.

 So, to sum up: (1) it makes no sense to subclass fatal 
 exceptions, and (2) never subclass a RecoverableException if 
 you are not helping a catch block with that (but please do if 
 it aids recovery).

 ..so verbose and pedantic for my first post... yikes.. i beg 
 forgiveness!!!




 On Sunday, 19 February 2012 at 09:27:48 UTC, 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
Feb 19 2012
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 4:30 AM, Juan Manuel Cabo wrote:
 Hello D community! This is my first post!! I hope I can bring clarity to
 all this. If not, I apologize.
[snip] Thanks for an insightful post. If you found the time, it would be great if you could translate your paper on exceptions from Spanish. Andrei
Feb 19 2012
parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
Thanks for the interest!!, Not sure when, but I'll write a 
translation.
--jm

On Sunday, 19 February 2012 at 15:57:25 UTC, Andrei Alexandrescu 
wrote:
 On 2/19/12 4:30 AM, Juan Manuel Cabo wrote:
 Hello D community! This is my first post!! I hope I can bring 
 clarity to
 all this. If not, I apologize.
[snip] Thanks for an insightful post. If you found the time, it would be great if you could translate your paper on exceptions from Spanish. Andrei
Feb 19 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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. Andrei
Feb 19 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"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.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 19 2012
next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"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.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 4:20 PM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 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.
I mentioned the issues here and there, but it's worth collecting them in one place. 1. Type multiplicity. Too many handwritten types are harmful. They exacerbate boilerplate and favor code duplication. 2. Using types and inheritance excessively to represent simple categorical information. 3. Forcing cross-cutting properties into a tree structure. The way I see exceptions is a semantic graph, and making it into a tree forces things like repeated catch clauses for distinct types coming from distinct parts of the hierarchy. 4. Front-loaded design. Very finely-grained hierarchies are built on the off chance someone may need something AND would want to use a type for that. 5. Unclear on the protocol. The "well-designed" phrase has come about often in this thread, but the specifics are rather vague. 6. Bottom-heavy base interface. Class hierarchies should have significant functionality at upper levels, which allows generic code to do significant reusable work using the base class. Instead, exceptions add functionality toward the bottom of the hierarchy, which encourages non-reusable code that deals with specifics. There might be a couple more, but I think they can be considered derivative. Andrei
Feb 19 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 17:35:31 Andrei Alexandrescu wrote:
 On 2/19/12 4:20 PM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 
 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.
I mentioned the issues here and there, but it's worth collecting them in one place. 1. Type multiplicity. Too many handwritten types are harmful. They exacerbate boilerplate and favor code duplication.
Assuming that the exception provides additional information via member variables rather than just being a new exception type which is essentially identical to Exception save for its type, then you have to write them by hand anyway. It's only the ones where their only point of existence is their type (which therefore indicates the type of problem that occurred but isn't able for whatever reason to give any further useful information) which result in boilerplate problems. And since in general we really should be adding member variables with additional information, the boilerplate should be minimized. And if we _do_ want such classes, we can use a mixin to generate them (the downside being that they don't end up in the ddoc - though that could be fixed, and it probably should be). We shouldn't go to town and create tons and tons of exception types, but we should also have them where they're useful.
 2. Using types and inheritance excessively to represent simple
 categorical information.
try-catch blocks are _designed_ to take advantage of types and inheritance. So, moving away from using derived types for exceptions makes it harder to write exception handling code. It also works very well with inheritance to have someone catch a more general exception and ignore the existance of the more specific ones if they don't care about the specifics of what went wrong. So, the hierarchy ends up being very useful. And how do intend to _add_ additional information to exceptions if not through derived types which can then hold additional member variables? I don't see how you could have the relevant information without going out of your way to avoid inheritance by using type codes and something equivalent to void* for the data. It would just be cleaner - and better suited to how catch blocks work - to use an exception hierarchy. It seems to me that the very design of exceptions in the language is geared towards having a class hierarchy of exceptions. And I contend that doing that works quite well. Certainly, in my experience, it does. It's when programmers try to avoid handling exceptions that things go awry (e.g. by catching Exception and then ignoring it), and that can happen regardless of your design. You can't prevent programmers from being stupid.
 3. Forcing cross-cutting properties into a tree structure. The way I see
 exceptions is a semantic graph, and making it into a tree forces things
 like repeated catch clauses for distinct types coming from distinct
 parts of the hierarchy.
Except that in most cases, you want to do handle exceptions differently if they come from different parts of the hierarchy. And making it so that you could do something like catch(Ex1, Ex2 : Ex e) would cover the other cases quite well.
 4. Front-loaded design. Very finely-grained hierarchies are built on the
 off chance someone may need something AND would want to use a type for that.
There's definitely some truth to that. However, it's very easy to add more exception types later without disrupting code. For instance, if you added IOException, and made all of the exsisting exception types which would make sense with that as their base class then derive from it, then new code could choose to call either IOException or the more specific exception types. And existing could would _already_ call the more specific exception types, so nothing would be disrupted. And if you added more specific exceptions (e.g. creating FileNotFoundException and making it derive from FileException), then new code could choose to catch the more specific exception instead of FileException, but existing code would continue to catch FileException without a problem. So, we can organize our exception hierarchy on a fairly rough level to begin with and add exceptions to it (both common and derived) as appropriate. And while we'd still end up with some up front design, because we'd have to create at least some of the hierarchy to begin with, the fact that we can add to the hierarchy later without breaking code minimizes the need to add stuff that we think that we _might_ need. It's removing exception types and moving them from one part of the hierarchy to another which is disruptive. And while avoiding those _does_ mean doing a good job with how you initially put exceptions into a hierarchy, I don't think that it's that hard to avoid once you've got a basic hierarchy, especially if we start with a minimal number of exception types and add more later as appropriate rather than creating a large, complex hierarchy to begin with. The main breakage would be in moving from the module-specific exceptions to a hierarchy, and much of that wouldn't break anything, because as we don't have much of a hierarchy right now, it would only be an issue with the ones that we decided to remove.
 5. Unclear on the protocol. The "well-designed" phrase has come about
 often in this thread, but the specifics are rather vague.
As with any design, we'd have to look at concrete examples and examine their That should give us at least a good idea of what _they_ think is a well- designed hierarchy, and we can take the pieces that best apply to us and our situation. We don't need to start with anything as large as they have, but it's something to work from.
 6. Bottom-heavy base interface. Class hierarchies should have
 significant functionality at upper levels, which allows generic code to
 do significant reusable work using the base class. Instead, exceptions
 add functionality toward the bottom of the hierarchy, which encourages
 non-reusable code that deals with specifics.
True. But I don't see a way around that. The more specific the problem, the more information that you have. The more general the problem, the less information that you have. So, you naturally end up with more data in the derived classes. And for the most part, it's the data that you want, not polymorphic functions. Polymorphism is of limited usefulness with exceptions. It's the hierarchy which is useful. In fact, you could probably have exceptions be non-polymorphic if we had a construct in the language which had inheritance but not polymorphism. But we don't. So, while I think that you make a valid point, I still think that using classes with an inheritance hierarchy is the best fit. - Jonathan M Davis
Feb 19 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Feb 19 2012
prev sibling next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 21:57, Andrei Alexandrescu a crit :
 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.
Modules are organized by what the do, not how they fail. This hierarchy make no sense to me. Worse, it will not be reusable in user code.
Feb 19 2012
prev sibling parent reply "foobar" <foo bar.com> writes:
On Sunday, 19 February 2012 at 20:57:09 UTC, 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.
 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
I just died a little reading this. Are you suggesting that in order to handle IO exceptions I need to: try { ...whatever... } catch (PackageException!"std.io") {...} } catch (PackageException!"tango.io") {...} } catch (PackageException!"otherLib.io") {...} ... What the hell is wrong with just using an IOException? AS Nick wrote, it seems you have a complete lack of understanding of how exceptions work which is unsurprising coming from a c++ expert. Also, this entire discussion you started about "improving" exceptions looks to me like a combination of NIH syndrome sparkled with heavy doses of premature optimization.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 12:44 AM, foobar wrote:
 I just died a little reading this. Are you suggesting that in order
 to handle IO exceptions I need to: try { ...whatever... } catch
 (PackageException!"std.io") {...} } catch
 (PackageException!"tango.io") {...} } catch
 (PackageException!"otherLib.io") {...} ...

 What the hell is wrong with just using an IOException?
There's nothing wrong, but there's a possible misunderstanding. If tango.io and otherLib.io cooperate with std, then they'd originate exceptions in std.io (as opposed to their own). Do note that the issue is exactly the same if libraries use IOException - they all must agree on using the same nomenclature, whether it's called PackageException!"std.io" or IOException. ModuleException and PackageException have one important thing going for them: they automate away a good amount of boilerplate, which makes them interesting for me to look at, and worth sharing as long as we're brainstorming. The associated issues as clear as the advantages. Probably ModuleException is too specific to be interesting, but PackageException seems useful.
 AS Nick wrote, it seems you have a complete lack of understanding of
  how exceptions work which is unsurprising coming from a c++ expert.
Always eager to learn (so please come with all you've got), but quite honest I hope in a way you're exaggerating, seeing as a lot of the stuff I authored for D (the scope statement, exception chaining, a full chapter in TDPL) would be completely broken.
 Also, this entire discussion you started about "improving" exceptions
 looks to me like a combination of NIH syndrome sparkled with heavy
 doses of premature optimization.
What is being optimized here? Thanks, Andrei
Feb 19 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 01:10:39 Andrei Alexandrescu wrote:
 On 2/20/12 12:44 AM, foobar wrote:
 I just died a little reading this. Are you suggesting that in order
 to handle IO exceptions I need to: try { ...whatever... } catch
 (PackageException!"std.io") {...} } catch
 (PackageException!"tango.io") {...} } catch
 (PackageException!"otherLib.io") {...} ...
 
 What the hell is wrong with just using an IOException?
There's nothing wrong, but there's a possible misunderstanding. If tango.io and otherLib.io cooperate with std, then they'd originate exceptions in std.io (as opposed to their own). Do note that the issue is exactly the same if libraries use IOException - they all must agree on using the same nomenclature, whether it's called PackageException!"std.io" or IOException. ModuleException and PackageException have one important thing going for them: they automate away a good amount of boilerplate, which makes them interesting for me to look at, and worth sharing as long as we're brainstorming. The associated issues as clear as the advantages. Probably ModuleException is too specific to be interesting, but PackageException seems useful.
It saves no boilerplate at the catch point, because you have to know what you're catching to do anything useful with it. It could be that you choose to catch a common exception rather than a specific one (e.g. IOException instead of FileException), but regardless of whether you use templates or mixins or whatever to generate the exception type's source code, you still need to write the handling code by hand. Handling code is _not_ the sort of thing that can be automated. And as for saving boilerplate in defining exceptions, well some of that could be done via mixins, but since useful exceptions often have additional member variables, you're going to have to write many of them by hand anyway. _Some_ exceptions _could_ be done via mixins and the like when it's their type that matters and that's it, but I question that that's enough boilerplate to be a big deal - especially since it's not likely we should be adding exception types all the time. I think that you're overestimating the amount of boilerplate code involved, and there are easy ways to reduce it without making exceptions be tied to modules or packages. And as has been said before, ultimately the module that an exception comes from doesn't mean much. It's what went wrong that matters. And ideally, the standard library would have exception types that user code would throw or derive its own exception types from, in which case the exception types get even more divorced from the modules that they're declared in. So, ultimately, tying exceptions to modules or packages is _not_ a good idea in the general case. Sometimes it makes sense - particularly if you're talking about making a base exception type which a project as a whole uses (e.g. a PostgresException for a postgres library) - but in general, it's a bad approach, and it's one that we should be moving away from. I say that we organize Phobos' exceptions into a proper hierarchy and then let other D projects derive from them or not as they see appropriate. But a standard set of exceptions should help standardize exceptions in D in general, even if not all libraries and projects choose to derive their exception types from the standard library's exception types. We don't need to do anything elaborate with how exceptions are defined. We just need to organize them into a hierarchy. And it's not like we even have all that many exceptions to organize at the moment. This thread was started due to a legitimate problem - the fact that our exception types are not always well chosen and definitely aren't always well organized. But that doesn't mean that we need to do anything new or fancy to fix the problem either. - Jonathan M Davis
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:38 AM, Jonathan M Davis wrote:
 On Monday, February 20, 2012 01:10:39 Andrei Alexandrescu wrote:
 ModuleException and PackageException have one important thing going for
 them: they automate away a good amount of boilerplate, which makes them
 interesting for me to look at, and worth sharing as long as we're
 brainstorming. The associated issues as clear as the advantages.
 Probably ModuleException is too specific to be interesting, but
 PackageException seems useful.
It saves no boilerplate at the catch point, because you have to know what you're catching to do anything useful with it. It could be that you choose to catch a common exception rather than a specific one (e.g. IOException instead of FileException), but regardless of whether you use templates or mixins or whatever to generate the exception type's source code, you still need to write the handling code by hand. Handling code is _not_ the sort of thing that can be automated.
Absolutely. For catching we're looking for allowing centralization, not for automation.
 And as for saving boilerplate in defining exceptions, well some of that could
 be done via mixins, but since useful exceptions often have additional member
 variables, you're going to have to write many of them by hand anyway.
Again, I think this thread clarified we need the "Variant[string] info;" member however we define the hierarchy. Also, I think we can do better than defining the boilerplate constructor (see e.g. https://github.com/D-Programming-Language/phobos/pull/439). It's just a function. Consider: // this goes in the stdlib void raise(ConcreteException)(string message, Throwable t = null, string f = __FILE__, size_t l = __LINE__) { auto r = new ConcreteException; r.message = message; r.file = f; r.line = l; r.next = t; throw r; } class AcmeException : Exception {} Now whenever you want to raise AcmeException, you say raise!AcmeException("message"). Also, raise may accept additional data that fills the Variant[string]. That makes exception definitions one-liners.
 And as has been said before, ultimately the module that an exception comes
 from doesn't mean much. It's what went wrong that matters. And ideally, the
 standard library would have exception types that user code would throw or
 derive its own exception types from, in which case the exception types get
 even more divorced from the modules that they're declared in. So, ultimately,
 tying exceptions to modules or packages is _not_ a good idea in the general
 case. Sometimes it makes sense - particularly if you're talking about making a
 base exception type which a project as a whole uses (e.g. a PostgresException
 for a postgres library) - but in general, it's a bad approach, and it's one
 that we should be moving away from.
I tend to agree, particularly because it's easier for people to remember express names instead of origins encoded by module. Ironically, many of Phobos' exceptions today follow the exception-to-module correspondence. Still I think it's worth keeping in mind the possibility of having the PackageException as a base trail for various exceptions specific to a package. Andrei
Feb 20 2012
next sibling parent reply dennis luehring <dl.soluz gmx.net> writes:
 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
to use an mighty hyper map capable of holding all informative "values" will just follow in the same amount of non-using code, and the using code will be filled up with info["blub"], info["blab"], evil castings, sensless const key-string an still no proper way to show/use the information in an generic and typesafe way sorry but feels like throwing anway the signature conecpt and replace it with an runtime thing ... maybe we should also get rid of function signatures with "Variant[string] parameter" :}
Feb 20 2012
next sibling parent "Nick Sabalausky" <a a.a> writes:
"dennis luehring" <dl.soluz gmx.net> wrote in message 
news:jhtrh1$113l$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
to use an mighty hyper map capable of holding all informative "values" will just follow in the same amount of non-using code, and the using code will be filled up with info["blub"], info["blab"], evil castings, sensless const key-string an still no proper way to show/use the information in an generic and typesafe way sorry but feels like throwing anway the signature conecpt and replace it with an runtime thing ... maybe we should also get rid of function signatures with "Variant[string] parameter" :}
Exactly. It smacks of abandoning normal code in favor of JS and other such ultra-dynamic toy/scripting langauges where *everything* is a mutable AA just for the fuck of it.
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 10:15 AM, dennis luehring wrote:
 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
to use an mighty hyper map capable of holding all informative "values" will just follow in the same amount of non-using code, and the using code will be filled up with info["blub"], info["blab"], evil castings, sensless const key-string an still no proper way to show/use the information in an generic and typesafe way sorry but feels like throwing anway the signature conecpt and replace it with an runtime thing ... maybe we should also get rid of function signatures with "Variant[string] parameter" :}
I understand this seems loose, but again, when you want to do custom formatting or i18n this is the way to go. The job of rendering the exception as a string must be outsourced (heh) outside the exception, and Variant[string] is a simple mechanism to do so. Andrei
Feb 20 2012
next sibling parent dennis luehring <dl.soluz gmx.net> writes:
Am 20.02.2012 17:36, schrieb Andrei Alexandrescu:
 On 2/20/12 10:15 AM, dennis luehring wrote:
  Again, I think this thread clarified we need the "Variant[string] info;"
  member however we define the hierarchy.
to use an mighty hyper map capable of holding all informative "values" will just follow in the same amount of non-using code, and the using code will be filled up with info["blub"], info["blab"], evil castings, sensless const key-string an still no proper way to show/use the information in an generic and typesafe way sorry but feels like throwing anway the signature conecpt and replace it with an runtime thing ... maybe we should also get rid of function signatures with "Variant[string] parameter" :}
I understand this seems loose, but again, when you want to do custom formatting or i18n this is the way to go. The job of rendering the exception as a string must be outsourced (heh) outside the exception, and Variant[string] is a simple mechanism to do so.
it does not seem - it is special_exception --> special_exception_rendere is the way or else you need to define that clean,compile-time-safe and all the other stuff isn't needed anymore
Feb 20 2012
prev sibling parent reply dennis luehring <dl.soluz gmx.net> writes:
Am 20.02.2012 17:36, schrieb Andrei Alexandrescu:
 I understand this seems loose, but again, when you want to do custom
 formatting or i18n this is the way to go. The job of rendering the
 exception as a string must be outsourced (heh) outside the exception,
 and Variant[string] is a simple mechanism to do so.

 Andrei
it is loose and Variant[string] is a simple mechanism for data transport, but evil in form of compile-time checks, the complete signature of your information is then (per se) only runtime-checked, how to prevent difference in the info[] filling and info[] usage over time? i just mistyped my key, removed a key, used a different, etc... multiply that by the amount of needed Exceptions and their info-key-variants ... hell on earth in the end you will have a bunch of modules wich const-string-keys for the exception creation and rendering, and then i don't see any real difference to a specialize exception
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 12:11 PM, dennis luehring wrote:
 Am 20.02.2012 17:36, schrieb Andrei Alexandrescu:
 I understand this seems loose, but again, when you want to do custom
 formatting or i18n this is the way to go. The job of rendering the
 exception as a string must be outsourced (heh) outside the exception,
 and Variant[string] is a simple mechanism to do so.

 Andrei
it is loose and Variant[string] is a simple mechanism for data transport, but evil in form of compile-time checks, the complete signature of your information is then (per se) only runtime-checked, how to prevent difference in the info[] filling and info[] usage over time? i just mistyped my key, removed a key, used a different, etc... multiply that by the amount of needed Exceptions and their info-key-variants ... hell on earth
This is not hell on earth, it is the classic type dilution when you need to abide to binary interfaces.
 in the end you will have a bunch of modules wich const-string-keys for
 the exception creation and rendering, and then i don't see any real
 difference to a specialize exception
No, you end up with centralized formatting facilities and smaller code. Andrei
Feb 20 2012
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] info;" 
 member however we define the hierarchy.
I disagree. I don't see a need for that.
 Also, I think we can do better than defining the boilerplate constructor 
 (see e.g. https://github.com/D-Programming-Language/phobos/pull/439). It's 
 just a function. Consider:

 // this goes in the stdlib
 void raise(ConcreteException)(string message, Throwable t = null, string f 
 = __FILE__, size_t l = __LINE__)
 {
   auto r = new ConcreteException;
   r.message = message;
   r.file = f;
   r.line = l;
   r.next = t;
   throw r;
 }

 class AcmeException : Exception {}

 Now whenever you want to raise AcmeException, you say 
 raise!AcmeException("message"). Also, raise may accept additional data 
 that fills the Variant[string]. That makes exception definitions 
 one-liners.
So instead of solving a much more general problem with a clean definition-side-only solution, you'd rather tell people "Our langauge has 'throw' like any other modern langauge. Exept that many exceptions need to be used with this 'raise' thing instead of 'throw'." Why do you keep optimizing for API creators at the expense of API users?
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Feb 20 2012
next sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 16:37:28 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in 
 message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the 
 "Variant[string] info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception. Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in her browser, she does not know or care what 404 means. instead she gets a formated page suggesting her to check her spelling and probably a suggestion to try google.com instead. the exception notifies the developer of the error, the developer does extra processing (e.g. to suggest similar valid websites) and the user get a friendly notification. clearly it doesn't make sense to put all this into the exception.
Feb 20 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:05 AM, foobar wrote:
 On Monday, 20 February 2012 at 16:37:28 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org> wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string]
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception.
At the end of the day, a human-readable error message must be properly formatted given some exception that signaled an error. So I disagree that exceptions are meant for the developer. They are mechanism, a means to an end.
 Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in her
 browser, she does not know or care what 404 means. instead she gets a
 formated page suggesting her to check her spelling and probably a
 suggestion to try google.com instead.
Sure, and the question is how the message gets created.
 the exception notifies the developer of the error, the developer does
 extra processing (e.g. to suggest similar valid websites) and the user
 get a friendly notification. clearly it doesn't make sense to put all
 this into the exception.
That extra processing must format the message given the information passed by the exception. _Definitely_ it doesn't make sense to put the formatting processing in the exception. That's why shipping the information outside the exception in a generic format is necessary, hence the Variant[string]. Andrei
Feb 20 2012
next sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 17:11:09 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 11:05 AM, foobar wrote:
 On Monday, 20 February 2012 at 16:37:28 UTC, Andrei 
 Alexandrescu wrote:
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org> wrote 
 in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the 
 "Variant[string]
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception.
At the end of the day, a human-readable error message must be properly formatted given some exception that signaled an error. So I disagree that exceptions are meant for the developer. They are mechanism, a means to an end.
 Trivial example: My mom enters a misspelled URL (e.g. 
 goggle.com) in her
 browser, she does not know or care what 404 means. instead she 
 gets a
 formated page suggesting her to check her spelling and 
 probably a
 suggestion to try google.com instead.
Sure, and the question is how the message gets created.
 the exception notifies the developer of the error, the 
 developer does
 extra processing (e.g. to suggest similar valid websites) and 
 the user
 get a friendly notification. clearly it doesn't make sense to 
 put all
 this into the exception.
That extra processing must format the message given the information passed by the exception. _Definitely_ it doesn't make sense to put the formatting processing in the exception. That's why shipping the information outside the exception in a generic format is necessary, hence the Variant[string]. Andrei
This extra processing is orthogonal to the exception. the same exception can be logged to a file, processed (per above example) and generate graphical notification to the user, etc. The exception contains the information pertaining only to what went wrong. the rest is not part of this discussion. The exact same exception in the example would also be thrown on a mistyped URL in an application that tries to scrape some info from a website for further processing. The error is still the same - the url is incorrect but different use cases handle it differently. In the former example I might call to a i18n lib (someone already mentioned gettext) while in the latter I'll call a logging library with the the mistyped url (for statistics' sake). in the first I use the url to find a closest match, in the second I want to log said requested url. Both handled by *other* mechanisms. in both cases the exception needs a url field and in both cases I have no need for the Variant[string] map.
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:44 AM, foobar wrote:
 This extra processing is orthogonal to the exception. the same exception
 can be logged to a file, processed (per above example) and generate
 graphical notification to the user, etc. The exception contains the
 information pertaining only to what went wrong. the rest is not part of
 this discussion.
Exactly. I don't see how a disagreement follows from here. So isn't it reasonable to design the exception such that it can offer information pertaining to what went wrong, in a uniform manner?
 The exact same exception in the example would also be thrown on a
 mistyped URL in an application that tries to scrape some info from a
 website for further processing. The error is still the same - the url is
 incorrect but different use cases handle it differently. In the former
 example I might call to a i18n lib (someone already mentioned gettext)
 while in the latter I'll call a logging library with the the mistyped
 url (for statistics' sake).
 in the first I use the url to find a closest match, in the second I want
 to log said requested url. Both handled by *other* mechanisms.
 in both cases the exception needs a url field and in both cases I have
 no need for the Variant[string] map.
The Variant[string] map saves a lot of duplication whenever you want to format a human-readable string (which is a common activity with exceptions). It transforms this (I'm too lazy to write code anew by hand, so I'll paste Jonathan's): try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } into this: try getopt(args, ...) catch(Exception e) { stderr.writeln(stringTemplate(typeid(e).toString(), e.info)); return -1; } The stringTemplate function loads the formatting template from a table indexed on typeid(e).toString() and formats it with the info. It's simple factorization. Andrei
Feb 20 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
 On 2/20/12 11:44 AM, foobar wrote:
 This extra processing is orthogonal to the exception. the same exception
 can be logged to a file, processed (per above example) and generate
 graphical notification to the user, etc. The exception contains the
 information pertaining only to what went wrong. the rest is not part of
 this discussion.
Exactly. I don't see how a disagreement follows from here. So isn't it reasonable to design the exception such that it can offer information pertaining to what went wrong, in a uniform manner?
In most cases, that's not even vaguely possible. An exception for a socket would probably include an IP and port in its additonal information. An exception for a file operation would include the file name that it tried to operate on. An exception for malformed unicode would include information on the string and the bad character. An exception from parsing XML would give you information about which part of the XML had the problem and what the problem was. All of these situations are completely different. Using a Variant is the equivalent of using a void* and casting to the actual information based no the type of the exception or on a type code in the exception. How is that better than simply having an actual exception hierarchy with each of the derived exceptions having fields specific to their circumstances? I contend that it's far worse. It's throwing away type information simply in an effort to genericize. Genericity can be very useful, but it can also be over-applied. I don't see how you could possibly make that uniform. It's very non-uniform by its very nature. The handling _needs_ to be non-uniform.
 The exact same exception in the example would also be thrown on a
 mistyped URL in an application that tries to scrape some info from a
 website for further processing. The error is still the same - the url is
 incorrect but different use cases handle it differently. In the former
 example I might call to a i18n lib (someone already mentioned gettext)
 while in the latter I'll call a logging library with the the mistyped
 url (for statistics' sake).
 in the first I use the url to find a closest match, in the second I want
 to log said requested url. Both handled by *other* mechanisms.
 in both cases the exception needs a url field and in both cases I have
 no need for the Variant[string] map.
The Variant[string] map saves a lot of duplication whenever you want to format a human-readable string (which is a common activity with exceptions). It transforms this (I'm too lazy to write code anew by hand, so I'll paste Jonathan's):
[snip]
 
 The stringTemplate function loads the formatting template from a table
 indexed on typeid(e).toString() and formats it with the info. It's
 simple factorization.
That only works in this particular case, because it happens that the code is just figuring out how to translate the exception into an error message without trying to recover. If you want to do anything fancier (like suggest the flag that the user probably meant), you need those extra fields in the exceptions. Also, much of the time, the application is going to want to print its own error messages, not what library that its calling used. And frequently, the application can give much better messages, because it knows what it's doing, not just the operation that it tried and failed. Exceptions are for _developers_, not users, and in general, exception messages should not be printed to users. And moving any messages that exceptions have out of the exception itself is _bad_ for debugging. The exception should contain its own information, not have it externalized for internationalization purposes. If its in the exception itself, you can see it in the debugger. That doesn't work anywhere near as well when you externalize its stuff. I gave the getopt example, because we were talking about getopt, and it was easy to write. It shows one way that the fields in the exception could be useful. But it's an abnormality in that it's whole purpose is to allow you to figure out how to best give an error message to the user. Many, many other exceptions need to be processed by the program without necessarily informing the user of anything. So, getopt was probably a bad example. It just was an easy one to give. - Jonathan M Davis
Feb 20 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 12:23 PM, Jonathan M Davis wrote:
 On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
 On 2/20/12 11:44 AM, foobar wrote:
 This extra processing is orthogonal to the exception. the same exception
 can be logged to a file, processed (per above example) and generate
 graphical notification to the user, etc. The exception contains the
 information pertaining only to what went wrong. the rest is not part of
 this discussion.
Exactly. I don't see how a disagreement follows from here. So isn't it reasonable to design the exception such that it can offer information pertaining to what went wrong, in a uniform manner?
In most cases, that's not even vaguely possible.
Please hear me out.
 An exception for a socket
 would probably include an IP and port in its additonal information. An
 exception for a file operation would include the file name that it tried to
 operate on. An exception for malformed unicode would include information on
 the string and the bad character. An exception from parsing XML would give you
 information about which part of the XML had the problem and what the problem
 was. All of these situations are completely different. Using a Variant is the
 equivalent of using a void* and casting to the actual information based no the
 type of the exception or on a type code in the exception. How is that better
 than simply having an actual exception hierarchy with each of the derived
 exceptions having fields specific to their circumstances?
It is better because you get to factor out all formatting in one place. You combine a string template that has references to symbolic names, with a context giving the symbolic names. Otherwise the code doing so would need to duplicate that code all over the place - exactly as you did in your example.
 I contend that it's
 far worse. It's throwing away type information simply in an effort to
 genericize. Genericity can be very useful, but it can also be over-applied.
No, it's not throwing away anything. If you want to handle the actual exception, sure, you get typed access to the state. All I want is push one interface method up.
 I don't see how you could possibly make that uniform. It's very non-uniform by
 its very nature. The handling _needs_ to be non-uniform.
No, it doesn't, and if you just hear me out for a second you'll see how. It's very simple. Formatting is all about pairing symbolic names with data. A format engine would load a string template that uses symbolic name such as "file", "line", "IP", "port", and a context which contains bindings of these names to values. That way formatting does not need to know what an IP means etc.
 The stringTemplate function loads the formatting template from a table
 indexed on typeid(e).toString() and formats it with the info. It's
 simple factorization.
That only works in this particular case, because it happens that the code is just figuring out how to translate the exception into an error message without trying to recover. If you want to do anything fancier (like suggest the flag that the user probably meant), you need those extra fields in the exceptions.
Sure. Again, this is not advocating replacement of exception hierarchies with tables!
 Also, much of the time, the application is going to want to print its own
 error messages, not what library that its calling used.
Sure. Then the application has its own string templates. That was the purpose of the whole thing. Exceptions give bindings, applications give formatting templates.
 And frequently, the
 application can give much better messages, because it knows what it's doing,
 not just the operation that it tried and failed. Exceptions are for
 _developers_, not users, and in general, exception messages should not be
 printed to users.
Yup. Yup.
 And moving any messages that exceptions have out of the exception itself is
 _bad_ for debugging.
Sure. Print typeid(e) if you so want, and handle explicit exception specifically as needed.
 The exception should contain its own information, not
 have it externalized for internationalization purposes.
And that information should be accessible in a uniform manner.
 If its in the
 exception itself, you can see it in the debugger. That doesn't work anywhere
 near as well when you externalize its stuff.

 I gave the getopt example, because we were talking about getopt, and it was
 easy to write.
Code like that is present in very many catch blocks.
 It shows one way that the fields in the exception could be
 useful.
To me it shows how code can be duplicated without necessity.
 But it's an abnormality in that it's whole purpose is to allow you to
 figure out how to best give an error message to the user. Many, many other
 exceptions need to be processed by the program without necessarily informing
 the user of anything. So, getopt was probably a bad example. It just was an
 easy one to give.
You may want to give another one. Andrei
Feb 20 2012
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 12:45:57 Andrei Alexandrescu wrote:
 On 2/20/12 12:23 PM, Jonathan M Davis wrote:
 I don't see how you could possibly make that uniform. It's very
 non-uniform by its very nature. The handling _needs_ to be non-uniform.
No, it doesn't, and if you just hear me out for a second you'll see how. It's very simple. Formatting is all about pairing symbolic names with data. A format engine would load a string template that uses symbolic name such as "file", "line", "IP", "port", and a context which contains bindings of these names to values. That way formatting does not need to know what an IP means etc.
 The stringTemplate function loads the formatting template from a table
 indexed on typeid(e).toString() and formats it with the info. It's
 simple factorization.
That only works in this particular case, because it happens that the code is just figuring out how to translate the exception into an error message without trying to recover. If you want to do anything fancier (like suggest the flag that the user probably meant), you need those extra fields in the exceptions.
Sure. Again, this is not advocating replacement of exception hierarchies with tables!
Okay. If all you're looking to do is provide a templating mechanism for exception messages, then fine. If it saves on code, great. I question that it's worth the complexity, but maybe it would be a big improvement. I suspect that we'd have to implement it to really find that out. But I think that the exceptions themselves should be organized into a class hierarchy such that you can catch them based on type in a hierarchical manner and access their member variables like you'd access any object's member variables. Using a variant for that just complicates the code which actually wants to handle the exceptions rather than simply print out an error message. - Jonathan M Davis
Feb 20 2012
prev sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
 ... 
 
 Andrei
 
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too. --jm
Feb 20 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
 ... 
 
 Andrei
 
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.
Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly redundant exception classes: class UTFError : Exception {...} class LexUTFError : LexUTFError { int line, col; ... } class ConfigFileParseError : LexUTFError { string cfgfile_name; } auto decodeUTF(...) { ... throw new UTFError; } auto configLexer(...) { try { ... decodeUTF(...); } catch(UTFError e) { throw new LexUTFError(...); } } auto configParser(...) { try { ... configLexer(...); } catch(LexUTFError e) { throw new ConfigFileParseError(...); } }
 The idea of Variant[string] remedies that case without creating a new
 exception class just for the added fields. If that case is solved,
 then the tipical need for creating new exception types that don't
 really aid selecting them for catching and recovery is solved too.
[...] However, I still hesitate about using Variant[string]. How would you address the following problem: // Module A class MyException : Exception { this() { info["mydetail"] = ...; } } // Module B auto func() { try { ... } catch(MyException e) { if (e.info["mydetail"] == ...) { ... } } } If module A's maintainer renames "mydetail" to "detail", then module B will still compile with no problem, but now e.info["mydetail"] doesn't exist and will cause a runtime error at worst. At best, the catch block won't be able to recover from the error as it did before, because now it can't find the info it was looking for. If "mydetail" had been a field stored in MyException, then module B would get a compile-time error, and the problem can be fixed immediately, instead of going unnoticed until it blows up at the customer's production server. T -- Some ideas are so stupid that only intellectuals could believe them. -- George Orwell
Feb 20 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
Yeah.. that is a problem! :-) Thanks for liking the idea, now we can
talk about the fine details!!

One way is to not let the user direct access to the associative array,
but wrap the e.info["MyDetail"] call in a nothrow function, such as
e.info("MyDetail"), and an e.hasInfo("MyDetail"), and of course:
e.addInfo("MyDetail", value) and e.allInfoNames() or something.

The nothrow function would return an empty value if not found (I fear
that it might not be of the same Variant subtype as the Variant value
was intended when present).

--jm


On 02/20/2012 05:53 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
 ... 

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.
Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly redundant exception classes: class UTFError : Exception {...} class LexUTFError : LexUTFError { int line, col; ... } class ConfigFileParseError : LexUTFError { string cfgfile_name; } auto decodeUTF(...) { ... throw new UTFError; } auto configLexer(...) { try { ... decodeUTF(...); } catch(UTFError e) { throw new LexUTFError(...); } } auto configParser(...) { try { ... configLexer(...); } catch(LexUTFError e) { throw new ConfigFileParseError(...); } }
 The idea of Variant[string] remedies that case without creating a new
 exception class just for the added fields. If that case is solved,
 then the tipical need for creating new exception types that don't
 really aid selecting them for catching and recovery is solved too.
[...] However, I still hesitate about using Variant[string]. How would you address the following problem: // Module A class MyException : Exception { this() { info["mydetail"] = ...; } } // Module B auto func() { try { ... } catch(MyException e) { if (e.info["mydetail"] == ...) { ... } } } If module A's maintainer renames "mydetail" to "detail", then module B will still compile with no problem, but now e.info["mydetail"] doesn't exist and will cause a runtime error at worst. At best, the catch block won't be able to recover from the error as it did before, because now it can't find the info it was looking for. If "mydetail" had been a field stored in MyException, then module B would get a compile-time error, and the problem can be fixed immediately, instead of going unnoticed until it blows up at the customer's production server. T
Feb 20 2012
next sibling parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
I forgot to add, that you could define standard details names
as string constants, and even document them in the string constant
definition.

--jm


On 02/20/2012 06:11 PM, Juan Manuel Cabo wrote:
 Yeah.. that is a problem! :-) Thanks for liking the idea, now we can
 talk about the fine details!!
 
 One way is to not let the user direct access to the associative array,
 but wrap the e.info["MyDetail"] call in a nothrow function, such as
 e.info("MyDetail"), and an e.hasInfo("MyDetail"), and of course:
 e.addInfo("MyDetail", value) and e.allInfoNames() or something.
 
 The nothrow function would return an empty value if not found (I fear
 that it might not be of the same Variant subtype as the Variant value
 was intended when present).
 
 --jm
 
 
 On 02/20/2012 05:53 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
 ... 

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.
Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly redundant exception classes: class UTFError : Exception {...} class LexUTFError : LexUTFError { int line, col; ... } class ConfigFileParseError : LexUTFError { string cfgfile_name; } auto decodeUTF(...) { ... throw new UTFError; } auto configLexer(...) { try { ... decodeUTF(...); } catch(UTFError e) { throw new LexUTFError(...); } } auto configParser(...) { try { ... configLexer(...); } catch(LexUTFError e) { throw new ConfigFileParseError(...); } }
 The idea of Variant[string] remedies that case without creating a new
 exception class just for the added fields. If that case is solved,
 then the tipical need for creating new exception types that don't
 really aid selecting them for catching and recovery is solved too.
[...] However, I still hesitate about using Variant[string]. How would you address the following problem: // Module A class MyException : Exception { this() { info["mydetail"] = ...; } } // Module B auto func() { try { ... } catch(MyException e) { if (e.info["mydetail"] == ...) { ... } } } If module A's maintainer renames "mydetail" to "detail", then module B will still compile with no problem, but now e.info["mydetail"] doesn't exist and will cause a runtime error at worst. At best, the catch block won't be able to recover from the error as it did before, because now it can't find the info it was looking for. If "mydetail" had been a field stored in MyException, then module B would get a compile-time error, and the problem can be fixed immediately, instead of going unnoticed until it blows up at the customer's production server. T
Feb 20 2012
prev sibling parent dennis luehring <dl.soluz gmx.net> writes:
Am 20.02.2012 22:11, schrieb Juan Manuel Cabo:
 Yeah.. that is a problem! :-) Thanks for liking the idea, now we can
 talk about the fine details!!

 One way is to not let the user direct access to the associative array,
 but wrap the e.info["MyDetail"] call in a nothrow function, such as
 e.info("MyDetail"), and an e.hasInfo("MyDetail"), and of course:
 e.addInfo("MyDetail", value) and e.allInfoNames() or something.
if the key is missing i can't recover/control the error - now all my data is gone - a silly wraper which catches the key-does-not-exists-lets-just-return-null will not help here
Feb 21 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 2:53 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
 ...

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.
Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly redundant exception classes:
[snip] Yah, it does look we need to better investigate the Variant[string] info() primitive. Andrei
Feb 20 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 03:12:08PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 2:53 PM, H. S. Teoh wrote:
On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
...
Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
...

Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.
Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly redundant exception classes:
[snip] Yah, it does look we need to better investigate the Variant[string] info() primitive.
[...] I still don't like the idea of using Variant[string], though. (1) It doesn't allow compile-time type checking. This is a big minus, in my book. (2) It's overly flexible. Anyone along the call stack can insert (hopefully NOT delete!!) additional data into the Exception object, as the stack is unwound. By the time it gets to the final catch() block, you cannot guarantee a particular field you depend on will be defined. Say if your call graph looks something like this: main() +--func1() +--func2() | +--helperFunc() | +--func3() | +--helperFunc() +--func4() +--helperFunc() Suppose helperFunc() throws HelperException, which func1's catch block specifically wants to handle. Suppose func2() adds an attribute called "lineNumber" to its catch block, which then rethrows the exception, and func3() adds an attribute called "colNumber". Now how should you write func1()'s catch block? You will get all HelperException's thrown, but you've no idea from which part of the call graph it originates. If it comes from func3(), then you have both "lineNumber" and "colNumber". If it comes before you reach func3(), then only "lineNumber" is defined. If it comes from func4(), then neither is present. So your catch block degenerates into a morass of if-then-else conditions. And then what do you do if you're depending on a particular field to be set, but it's not? Rethrow the exception? Then you have the stack trace reset problem. Whereas if HelperException always has the the same fields, the catch block is very straightforward: just catch HelperException, and you are guaranteed you have all the info you need. Then if func3() wants to add more info, create a new exception derived from HelperException, and add the field there. Then in func1(), add a new catch block that catches the new exception, and makes use of the new field. This does introduce a lot of little exception classes, which you could argue is class bloat, but I don't see how the Variant[string] method is necessarily superior. It comes with its own set of (IMHO quite nasty) problems. T -- One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie. -- The Silicon Valley Tarot
Feb 20 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 I still don't like the idea of using Variant[string], though.
 
 (1) It doesn't allow compile-time type checking. This is a big minus, in
 my book.
When you need compile-time type checking, define a variable in your class. Just make sure that you are creating that new exception class for a good reason. When a user needs to add a variable to the exception, he can add it without putting your exception class chained in a new type of exception, that will hide your class from being selected by upstream catch blocks in the call tree.
 
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound. 
As is currently the case. Did you know that anyone can overwrite any field of the exception and rethrow it? Such as msg field and so on?
 By the time it gets to the final catch() block,
 you cannot guarantee a particular field you depend on will be defined.
If you want to guarantee it, then use a plain old variable for that piece of data. I just would like a way to add data to an exception without creating a new type. If I create a new exception type for the wrong reasons, I'm polluting the exception hierarchy. If I pollute the exception hierarchy, then catching by exception type becomes convolluted. It becomes difficult for an exception to fall in the right catch. And I think that is a worst problem than not being sure if a piece of data is in the extra info. Data is data. Types are types. Exceptions should be typed the best way possible that will allow me to select them and fall in the right catch block. And that is the *what* of the error, not the data of the error.
 Say if your call graph looks something like this:
 
 	main()
 	  +--func1()
 	      +--func2()
 	      |   +--helperFunc()
 	      |   +--func3()
 	      |       +--helperFunc()
 	      +--func4()
 	          +--helperFunc()
 
 Suppose helperFunc() throws HelperException, which func1's catch block
 specifically wants to handle. Suppose func2() adds an attribute called
 "lineNumber" to its catch block, which then rethrows the exception, and
 func3() adds an attribute called "colNumber".
 
 Now how should you write func1()'s catch block? You will get all
 HelperException's thrown, but you've no idea from which part of the call
 graph it originates. If it comes from func3(), then you have both
 "lineNumber" and "colNumber". If it comes before you reach func3(), then
 only "lineNumber" is defined. If it comes from func4(), then neither is
 present.
 
 So your catch block degenerates into a morass of if-then-else
 conditions. And then what do you do if you're depending on a particular
 field to be set, but it's not? Rethrow the exception? Then you have the
 stack trace reset problem.
 
 Whereas if HelperException always has the the same fields, the catch
 block is very straightforward: just catch HelperException, and you are
 guaranteed you have all the info you need.
 
 Then if func3() wants to add more info, create a new exception derived
 from HelperException, and add the field there. Then in func1(), add a
 new catch block that catches the new exception, and makes use of the new
 field.
 
 This does introduce a lot of little exception classes, which you could
 argue is class bloat, but I don't see how the Variant[string] method is
 necessarily superior. It comes with its own set of (IMHO quite nasty)
 problems.
 
 
 T
 
HAhaha, it sometimes feel as though people are afraid that the Variant[string] idea is to never use plain old variables and never use exception subclasses. :-) On the contrary, the idea is so that plain old variables and exception subclasses can be created for the right reasons, and to remove cases where they need to be created for the wrong reasons. --jm
Feb 20 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 07:44:30PM -0300, Juan Manuel Cabo wrote:
[...]
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound. 
As is currently the case. Did you know that anyone can overwrite any field of the exception and rethrow it? Such as msg field and so on?
This is an implementation bug. Exceptions should always be const in the catch block. I believe this issue has been filed, and will eventually be fixed.
 By the time it gets to the final catch() block, you cannot guarantee
 a particular field you depend on will be defined.
If you want to guarantee it, then use a plain old variable for that piece of data. I just would like a way to add data to an exception without creating a new type. If I create a new exception type for the wrong reasons, I'm polluting the exception hierarchy.
Point taken. So I think what we should have is *both* data stored in fields in Exception subclasses, and some kind of way to attach auxilliary data to the exception. Say with Variant[string], or whatever way you prefer. But Variant[string] should not be used for *everything*. That only leads to problems. But then, it limits the usefulness of Variant[string], because then you can't just pass it to the i18n formatter, since now some fields are static but they may need to be part of the formatted message. So we haven't really solved anything, we just added a new feature to Exception which I'm not sure how useful it is. Do you have actual use cases that requires adding data to exceptions? Without concrete examples we're just arguing about hypotheticals. T -- This is not a sentence.
Feb 20 2012
next sibling parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 Do you have actual use
 cases that requires adding data to exceptions? Without concrete examples
 we're just arguing about hypotheticals.
I posted a few hypothetical cases during in the thread, but this is one long megathread! I'm not facing an urgent need right now for the Variant[string] capability. I just realized that the majority of twisted exception hierarchies are grown trying to encode transversal traits of the exceptions in their types names. The exception types should encode only one thing: the error *what*. Now, for real use cases for the Variant[string], you just have to look around and they are everywhere. For instance: a C library wrapper, which gets the library errors encoded as some error code and throws them as exceptions. Shouldn't the library throw a FileNotFoundException when that's the error, instead of throwing a LibraryException that has the error code in a field? So the correct thing to do is: after a library call, the wrapper checks the last error code number with a switch statement, and deciding which standard exception type to throw (defaulting to whatever you like if the error code doesn't map to a standard D exception). Then you add the error code to the Variant[string]. That way, exception types can be standard. So, to keep D exception types standard reusable and respected by future code, you must follow the Open-Closed design principle (nicest principle of OO design ever). --jm On 02/20/2012 07:57 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 07:44:30PM -0300, Juan Manuel Cabo wrote:
 [...]
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound. 
As is currently the case. Did you know that anyone can overwrite any field of the exception and rethrow it? Such as msg field and so on?
This is an implementation bug. Exceptions should always be const in the catch block. I believe this issue has been filed, and will eventually be fixed.
 By the time it gets to the final catch() block, you cannot guarantee
 a particular field you depend on will be defined.
If you want to guarantee it, then use a plain old variable for that piece of data. I just would like a way to add data to an exception without creating a new type. If I create a new exception type for the wrong reasons, I'm polluting the exception hierarchy.
Point taken. So I think what we should have is *both* data stored in fields in Exception subclasses, and some kind of way to attach auxilliary data to the exception. Say with Variant[string], or whatever way you prefer. But Variant[string] should not be used for *everything*. That only leads to problems. But then, it limits the usefulness of Variant[string], because then you can't just pass it to the i18n formatter, since now some fields are static but they may need to be part of the formatted message. So we haven't really solved anything, we just added a new feature to Exception which I'm not sure how useful it is. Do you have actual use cases that requires adding data to exceptions? Without concrete examples we're just arguing about hypotheticals. T
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 4:57 PM, H. S. Teoh wrote:
 So I think what we should have is *both* data stored in fields in
 Exception subclasses, and some kind of way to attach auxilliary data to
 the exception. Say with Variant[string], or whatever way you prefer.

 But Variant[string] should not be used for *everything*. That only leads
 to problems. But then, it limits the usefulness of Variant[string],
 because then you can't just pass it to the i18n formatter, since now
 some fields are static but they may need to be part of the formatted
 message.
Great. I'll plant the interface and submit it for destruction.
 So we haven't really solved anything, we just added a new feature to
 Exception which I'm not sure how useful it is. Do you have actual use
 cases that requires adding data to exceptions? Without concrete examples
 we're just arguing about hypotheticals.
I'm seeing plenty of that in our code base at work. They use exception wrapping in conjunction with a fair amount of contortions because the Variant[string] approach has not been designed. Andrei
Feb 20 2012
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 4:44 PM, Juan Manuel Cabo wrote:
 HAhaha, it sometimes feel as though people are afraid that the Variant[string]
 idea is to never use plain old variables and never use exception subclasses.
:-)

 On the contrary, the idea is so that plain old variables and exception
subclasses
 can be created for the right reasons, and to remove cases where they need
 to be created for the wrong reasons.
Yah, I think there's a lot of confusion and consequent apprehension regarding this. Thanks for attempting to clarify things. Andrei
Feb 20 2012
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 17:23:42 Andrei Alexandrescu wrote:
 On 2/20/12 4:44 PM, Juan Manuel Cabo wrote:
 HAhaha, it sometimes feel as though people are afraid that the
 Variant[string] idea is to never use plain old variables and never use
 exception subclasses. :-)
 
 On the contrary, the idea is so that plain old variables and exception
 subclasses can be created for the right reasons, and to remove cases
 where they need to be created for the wrong reasons.
Yah, I think there's a lot of confusion and consequent apprehension regarding this. Thanks for attempting to clarify things.
I think that as long as it makes sense to catch an exception and handle it based on a new type, any information associated with that exception should be member variables in that exception. But if you have extra information which could be useful but where you wouldn't really want to catch a new type, then it makes some sense to use the hashtable. However, I don't recall ever having personally run into a situation where that sort of need arose. - Jonathan M Davis
Feb 20 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 00:23, Andrei Alexandrescu a crit :
 On 2/20/12 4:44 PM, Juan Manuel Cabo wrote:
 HAhaha, it sometimes feel as though people are afraid that the
 Variant[string]
 idea is to never use plain old variables and never use exception
 subclasses. :-)

 On the contrary, the idea is so that plain old variables and exception
 subclasses
 can be created for the right reasons, and to remove cases where they need
 to be created for the wrong reasons.
Yah, I think there's a lot of confusion and consequent apprehension regarding this. Thanks for attempting to clarify things. Andrei
So it doesn't help. Dulb subclasses of Exceptions are done mostly to be able to catch them. To avoid useless subclasses, we need a more precise way to catch Exception than the type only. This Variant[string] doesn't help.
Feb 21 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 09:32:35PM +0100, deadalnix wrote:
[...]
 So it doesn't help. Dulb subclasses of Exceptions are done mostly to
 be able to catch them. To avoid useless subclasses, we need a more
 precise way to catch Exception than the type only.
[...] This is a good point. Has anybody even considered, *why* does catch match by type? Is there a good reason for that? Or is it just inherited from the rah rah days of OOP where "everything is an object", so since exception is part of everything, exception is an object, therefore we can just catch by object type?
From all the heated debate in this thread, it's clear that exceptions
don't map very well to a class hierarchy, at least not without points of contention and what amounts to workarounds and hacks. So I'm going to throw (har har) this very crazy and wild idea out there and let's see if it's viable: What if instead of catching by class, we catch by attribute matching? So instead of writing: try { ... } catch(SomeExceptionType e) { ... } catch(SomeOtherExceptionType e) { ... } catch(YetAnotherSillyException e) { ... } we write: try { ... } catch(e: exists!e.filename && e.failedOp is File.open) { // something } catch(e: e.is_transient && e.num_retries < 5) { // something else } // And why should we even need an exception object in the first // place? catch(time() >= oldtime+5000) { // This thing's been running for way too long, time to // do something drastic } Flamesuit on! ;-) T -- Famous last words: I *think* this will work...
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 2:47 PM, H. S. Teoh wrote:
 On Tue, Feb 21, 2012 at 09:32:35PM +0100, deadalnix wrote:
 [...]
 So it doesn't help. Dulb subclasses of Exceptions are done mostly to
 be able to catch them. To avoid useless subclasses, we need a more
 precise way to catch Exception than the type only.
[...] This is a good point. Has anybody even considered, *why* does catch match by type? Is there a good reason for that? Or is it just inherited from the rah rah days of OOP where "everything is an object", so since exception is part of everything, exception is an object, therefore we can just catch by object type?
I think a reason is that exceptions should be able to transport an arbitrary amount of information. That means heterogeneous types at least. Then, as you discussed in the beginning of the thread, placing types in a hierarchy allows client code to catch several exceptions sharing a common super type, theory that was well understood. A more debatable aspect of exceptions is the first-match rule in catch blocks. All of OOP goes with best match, except here. But then all code is together so the effect is small.
 From all the heated debate in this thread, it's clear that exceptions
don't map very well to a class hierarchy, at least not without points of contention and what amounts to workarounds and hacks. So I'm going to throw (har har) this very crazy and wild idea out there and let's see if it's viable: What if instead of catching by class, we catch by attribute matching? So instead of writing: try { ... } catch(SomeExceptionType e) { ... } catch(SomeOtherExceptionType e) { ... } catch(YetAnotherSillyException e) { ... } we write: try { ... } catch(e: exists!e.filename&& e.failedOp is File.open) { // something } catch(e: e.is_transient&& e.num_retries< 5) { // something else } // And why should we even need an exception object in the first // place? catch(time()>= oldtime+5000) { // This thing's been running for way too long, time to // do something drastic } Flamesuit on! ;-)
The only problem I see here is ascribing e a type. Andrei
Feb 21 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 03:15:19PM -0600, Andrei Alexandrescu wrote:
[...]
 A more debatable aspect of exceptions is the first-match rule in
 catch blocks. All of OOP goes with best match, except here. But then
 all code is together so the effect is small.
Does it make sense to make it best-match? Or is that too risky since everyone expects it to be first-match? [...]
So I'm going to throw (har har) this very crazy and wild idea out
there and let's see if it's viable:

What if instead of catching by class, we catch by attribute matching?
So instead of writing:

	try { ... }
	catch(SomeExceptionType e) { ... }
	catch(SomeOtherExceptionType e) { ... }
	catch(YetAnotherSillyException e) { ... }

we write:

	try { ... }
	catch(e: exists!e.filename&&  e.failedOp is File.open) {
		// something
	}
	catch(e: e.is_transient&&  e.num_retries<  5) {
		// something else
	}
	// And why should we even need an exception object in the first
	// place?
	catch(time()>= oldtime+5000) {
		// This thing's been running for way too long, time to
		// do something drastic
	}

Flamesuit on! ;-)
The only problem I see here is ascribing e a type.
[...] True, for this to work in its full generality would require duck-typing (the catch block can use any property it tests for, regardless of type). Which D doesn't have. So it looks like we're back to catch conditions, that has come up a few times in this thread: try { ... } catch(Exception e: e.is_transient && ... || ...) { // or whatever the latest proposed syntax is, the idea // is the same. } catch(Exception e: !e.is_transient && ...) { // the previous block doesn't catch if conditions fail, // so we can still get Exception here. } This does start to look like it might make sense to switch to best-match instead of first-match for catch clauses. T -- Your inconsistency is the only consistent thing about you! -- KD
Feb 21 2012
parent "Martin Nowak" <dawg dawgfoto.de> writes:
On Tue, 21 Feb 2012 23:29:32 +0100, H. S. Teoh <hsteoh quickfur.ath.cx>  
wrote:

 On Tue, Feb 21, 2012 at 03:15:19PM -0600, Andrei Alexandrescu wrote:
 [...]
 A more debatable aspect of exceptions is the first-match rule in
 catch blocks. All of OOP goes with best match, except here. But then
 all code is together so the effect is small.
Does it make sense to make it best-match? Or is that too risky since everyone expects it to be first-match?
Catch statements that are not in best-match order are a compile time error, g++ does that as a warning too.
Feb 21 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 22:15, Andrei Alexandrescu a crit :
 What if instead of catching by class, we catch by attribute matching?
 So instead of writing:

 try { ... }
 catch(SomeExceptionType e) { ... }
 catch(SomeOtherExceptionType e) { ... }
 catch(YetAnotherSillyException e) { ... }

 we write:

 try { ... }
 catch(e: exists!e.filename&& e.failedOp is File.open) {
 // something
 }
 catch(e: e.is_transient&& e.num_retries< 5) {
 // something else
 }
 // And why should we even need an exception object in the first
 // place?
 catch(time()>= oldtime+5000) {
 // This thing's been running for way too long, time to
 // do something drastic
 }

 Flamesuit on! ;-)
The only problem I see here is ascribing e a type.
That is why I proposed here : http://forum.dlang.org/thread/jhos0l$102t$1 digitalmars.com?page=25#post-jhtus4:2416tp:2 1:40digitalmars.com some alternative syntax. To make it short : try { // Stuff . . . } catch(Exception e) if(e.transient == false) { // Handling . . . } For the longer explanation, see the link.
Feb 21 2012
prev sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Tue, Feb 21, 2012 at 6:32 PM, deadalnix <deadalnix gmail.com> wrote:
 Le 21/02/2012 00:23, Andrei Alexandrescu a =E9crit :

 On 2/20/12 4:44 PM, Juan Manuel Cabo wrote:
 HAhaha, it sometimes feel as though people are afraid that the
 Variant[string]
 idea is to never use plain old variables and never use exception
 subclasses. :-)

 On the contrary, the idea is so that plain old variables and exception
 subclasses
 can be created for the right reasons, and to remove cases where they ne=
ed
 to be created for the wrong reasons.
Yah, I think there's a lot of confusion and consequent apprehension regarding this. Thanks for attempting to clarify things. Andrei
So it doesn't help. Dulb subclasses of Exceptions are done mostly to be a=
ble
 to catch them. To avoid useless subclasses, we need a more precise way to
 catch Exception than the type only.
Agreed. I would love it if catch worked using a structural/pattern matching mechanism. Something like template's if clause or like Scala pattern matching... -Jose
 This Variant[string] doesn't help.
Feb 22 2012
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-20 23:44, Juan Manuel Cabo wrote:
 I still don't like the idea of using Variant[string], though.

 (1) It doesn't allow compile-time type checking. This is a big minus, in
 my book.
When you need compile-time type checking, define a variable in your class. Just make sure that you are creating that new exception class for a good reason. When a user needs to add a variable to the exception, he can add it without putting your exception class chained in a new type of exception, that will hide your class from being selected by upstream catch blocks in the call tree.
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound.
As is currently the case. Did you know that anyone can overwrite any field of the exception and rethrow it? Such as msg field and so on?
No one says the fields need to be public instance variables. You could take the arguments in the constructor and only have getters. -- /Jacob Carlborg
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 6:36 AM, Jacob Carlborg wrote:
 On 2012-02-20 23:44, Juan Manuel Cabo wrote:
 I still don't like the idea of using Variant[string], though.

 (1) It doesn't allow compile-time type checking. This is a big minus, in
 my book.
When you need compile-time type checking, define a variable in your class. Just make sure that you are creating that new exception class for a good reason. When a user needs to add a variable to the exception, he can add it without putting your exception class chained in a new type of exception, that will hide your class from being selected by upstream catch blocks in the call tree.
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound.
As is currently the case. Did you know that anyone can overwrite any field of the exception and rethrow it? Such as msg field and so on?
No one says the fields need to be public instance variables. You could take the arguments in the constructor and only have getters.
I think he meant to say things have been like that for a while and there's no blood in the streets. Andrei
Feb 21 2012
parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 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. And even if one makes those fields private, anyone can take a pointer to the class or void[] or whatever and do a mess. (Java isn't exepmpt, you can do a mess with reflection there). So there is a minimum of trust that we put on APIs and code that we call downstream. The same trust that one puts, to begin with, when one expects that an exception will be thrown when an error happens. Ruby and PHP are based on a lot of trust for instance! Having the advantages of staticlly typed language doesn't mean that one must turn the advantages into disadvantages and start hammering screws because we love hammers. --jm On 02/21/2012 11:20 AM, Andrei Alexandrescu wrote:
 On 2/21/12 6:36 AM, Jacob Carlborg wrote:
 On 2012-02-20 23:44, Juan Manuel Cabo wrote:
 I still don't like the idea of using Variant[string], though.

 (1) It doesn't allow compile-time type checking. This is a big minus, in
 my book.
When you need compile-time type checking, define a variable in your class. Just make sure that you are creating that new exception class for a good reason. When a user needs to add a variable to the exception, he can add it without putting your exception class chained in a new type of exception, that will hide your class from being selected by upstream catch blocks in the call tree.
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound.
As is currently the case. Did you know that anyone can overwrite any field of the exception and rethrow it? Such as msg field and so on?
No one says the fields need to be public instance variables. You could take the arguments in the constructor and only have getters.
I think he meant to say things have been like that for a while and there's no blood in the streets. Andrei
Feb 21 2012
parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
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.
Feb 21 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 4:04 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 03:12:08PM -0600, Andrei Alexandrescu wrote:
 I still don't like the idea of using Variant[string], though.
I don't like it, either. I mean not "like" like. It's an approach suggested by necessity.
 (1) It doesn't allow compile-time type checking. This is a big minus, in
 my book.
In mine, too. Literally. We're on the same boat.
 (2) It's overly flexible. Anyone along the call stack can insert
 (hopefully NOT delete!!) additional data into the Exception object, as
 the stack is unwound.
But that's a plus. It means the approach scales up to any number of control flows, of which there are combinatorially many. Defining one type for each... well you wouldn't "like" that, either.
 By the time it gets to the final catch() block,
 you cannot guarantee a particular field you depend on will be defined.
Indeed. If you depend on anything you'd want to catch the specific type.
 Say if your call graph looks something like this:

 	main()
 	  +--func1()
 	      +--func2()
 	      |   +--helperFunc()
 	      |   +--func3()
 	      |       +--helperFunc()
 	      +--func4()
 	          +--helperFunc()

 Suppose helperFunc() throws HelperException, which func1's catch block
 specifically wants to handle. Suppose func2() adds an attribute called
 "lineNumber" to its catch block, which then rethrows the exception, and
 func3() adds an attribute called "colNumber".

 Now how should you write func1()'s catch block? You will get all
 HelperException's thrown, but you've no idea from which part of the call
 graph it originates.
 If it comes from func3(), then you have both
 "lineNumber" and "colNumber". If it comes before you reach func3(), then
 only "lineNumber" is defined. If it comes from func4(), then neither is
 present.
Exactly. So you suggest adding one type for each possible control flow? Are you sure this scales beyond a toy example?
 So your catch block degenerates into a morass of if-then-else
 conditions.
No, precisely on the contrary. You catch blockS degenerate into a morass of catch (This) { ... } catch (That) { ... } catch (TheOther) { ... }. That is fine if the code in different "..." does very different things, but it's a terrible approach if all do the same thing, such as formatting. That shouldn't make anyone feel better than using a morass of if/else. The code with Variant[string] does not need combinatorial testing if it wants to do a uniform action (such as formatting). It handles formatting uniformly, and if it wants to look for one particular field it inserts a test.
 And then what do you do if you're depending on a particular
 field to be set, but it's not? Rethrow the exception? Then you have the
 stack trace reset problem.
Don't forget that Variant[string] does not preclude distinct exception types. It's not one or the other.
 Whereas if HelperException always has the the same fields, the catch
 block is very straightforward: just catch HelperException, and you are
 guaranteed you have all the info you need.
HelperException can definitely be there. It can only help if there's additional information associated with it.
 Then if func3() wants to add more info, create a new exception derived
 from HelperException, and add the field there. Then in func1(), add a
 new catch block that catches the new exception, and makes use of the new
 field.
They call that non-scalable code bloat.
 This does introduce a lot of little exception classes, which you could
 argue is class bloat, but I don't see how the Variant[string] method is
 necessarily superior. It comes with its own set of (IMHO quite nasty)
 problems.
Variant[string] is not superior because it doesn't compete against anything. It's a simple addition to the primitives available to the base Exception class. Andrei
Feb 20 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 4:04 PM, H. S. Teoh wrote:
[...]
By the time it gets to the final catch() block, you cannot guarantee
a particular field you depend on will be defined.
Indeed. If you depend on anything you'd want to catch the specific type.
Except that if the information you depend on is only set in the Variant[string], then you'd have to check and rethrow. [...]
Say if your call graph looks something like this:

	main()
	  +--func1()
	      +--func2()
	      |   +--helperFunc()
	      |   +--func3()
	      |       +--helperFunc()
	      +--func4()
	          +--helperFunc()

Suppose helperFunc() throws HelperException, which func1's catch
block specifically wants to handle. Suppose func2() adds an attribute
called "lineNumber" to its catch block, which then rethrows the
exception, and func3() adds an attribute called "colNumber".

Now how should you write func1()'s catch block? You will get all
HelperException's thrown, but you've no idea from which part of the
call graph it originates.  If it comes from func3(), then you have
both "lineNumber" and "colNumber". If it comes before you reach
func3(), then only "lineNumber" is defined. If it comes from func4(),
then neither is present.
Exactly. So you suggest adding one type for each possible control flow? Are you sure this scales beyond a toy example?
Not each possible control flow, but each meaningful exception type, i.e., at some intermediate level, you catch ConversionError and throw IntOverflowError, as you suggested. You don't need a separate type for every possible place where ConversionError gets thrown.
So your catch block degenerates into a morass of if-then-else
conditions.
No, precisely on the contrary. You catch blockS degenerate into a morass of catch (This) { ... } catch (That) { ... } catch (TheOther) { ... }. That is fine if the code in different "..." does very different things, but it's a terrible approach if all do the same thing, such as formatting.
Formatting should use class reflection. We already discussed that, and we already agreed that was the superior approach. When you're catching a specific exception, you're catching it with the view that it will contain precisely information X, Y, Z that you need to recover from the problem. If you don't need to catch something, then don't put the catch block there. The problem with using Variant[string] is that everything gets lumped into one Exception object, and there's no way to only catch the Exception that happens to have variables "p", "q", and "r" set in the Variant[string]. You have to catch an exception type that includes all sorts of combinations of data in Variant[string], then manually do tests to single out the exception you want, and rethrow the rest. That's where the ugliness comes from. [...]
 The code with Variant[string] does not need combinatorial testing if
 it wants to do a uniform action (such as formatting). It handles
 formatting uniformly, and if it wants to look for one particular field
 it inserts a test.
Again, we've already agreed class reflection is the proper solution to this one.
And then what do you do if you're depending on a particular field to
be set, but it's not? Rethrow the exception? Then you have the stack
trace reset problem.
Don't forget that Variant[string] does not preclude distinct exception types. It's not one or the other.
[...] Agreed. But it shouldn't be the be-all and end-all of data passed in exceptions. If anything, it should only be rarely used, with most exception classes using static fields to convey relevant information. I can see the usefulness of using Variant[string] as a way of "decorating" exceptions with "extra attributes", but it shouldn't be the primary way of conveying information from the throw site to the catch site. As for iterating over the information in the most derived class, for formatting, etc., class reflection is the way to go. We shouldn't be using Variant[string] for this, because there's another problem associated with it: suppose MyException sometimes gets "extra_info1" and "extra_info2" tacked onto it, on its way up the call stack, and sometimes not. Now what should the catcher do? How do you format this exception? Should the format string include extra_info1 and extra_info2, or not? If it doesn't, what's the use of this extra info? If it does, what happens if these fields are missing? This is what I mean by not being able to depend on whether some data is there. Ultimately, to do anything useful with the info in the object, you need to know what's there. Preferably, the object's type will tell you exactly what's there, then you do a simple map from type to list of available attributes (e.g., map exception type to format string with known, static list of attributes). But if the type doesn't guarantee what data will be present, then your code becomes vastly more complex, you have to deal with potentially all possible combinations of what's there and what isn't. Instead of a single format string for a single exception type, you now have a combinatorial explosion of format strings for every possible combination of missing/present fields in the exception object. Just because the catch block can just blindly hand this Variant[string] over to the formatter doesn't solve the problem. It merely moves the complexity to the formatter, which *still* has to deal with what happens if what the format string expects to be there isn't there. T -- Public parking: euphemism for paid parking. -- Flora
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 6:25 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote:
 Formatting should use class reflection. We already discussed that, and
 we already agreed that was the superior approach.
Jose's argument convinced me otherwise. I retract my agreement.
 When you're catching a specific exception, you're catching it with the
 view that it will contain precisely information X, Y, Z that you need to
 recover from the problem. If you don't need to catch something, then
 don't put the catch block there.
That's extremely rare in my experience, and only present in toy examples that contain a ton of "..." magic.
 The problem with using Variant[string] is that everything gets lumped
 into one Exception object, and there's no way to only catch the
 Exception that happens to have variables "p", "q", and "r" set in the
 Variant[string].
No. You are still free to define as many exception classes as you deem necessary. Please let's not pit the hash against the hierarchy again. This is confusing the role of the two. Consider the hash an interface function you want to occasionally implement.
 You have to catch an exception type that includes all
 sorts of combinations of data in Variant[string], then manually do tests
 to single out the exception you want, and rethrow the rest. That's where
 the ugliness comes from.
Yah, that would suck, but it's not at all what I say.
 [...]
 The code with Variant[string] does not need combinatorial testing if
 it wants to do a uniform action (such as formatting). It handles
 formatting uniformly, and if it wants to look for one particular field
 it inserts a test.
Again, we've already agreed class reflection is the proper solution to this one.
Agreement rescinded. Sorry! Jose's point was just too good, and reminded me of a pain point I so long had with exception, I'd got used to it as a fact of life.
 And then what do you do if you're depending on a particular field to
 be set, but it's not? Rethrow the exception? Then you have the stack
 trace reset problem.
Don't forget that Variant[string] does not preclude distinct exception types. It's not one or the other.
[...] Agreed. But it shouldn't be the be-all and end-all of data passed in exceptions. If anything, it should only be rarely used, with most exception classes using static fields to convey relevant information.
And to perfectly help code duplication everywhere.
 I can see the usefulness of using Variant[string] as a way of
 "decorating" exceptions with "extra attributes", but it shouldn't be the
 primary way of conveying information from the throw site to the catch
 site.

 As for iterating over the information in the most derived class, for
 formatting, etc., class reflection is the way to go.
Agreement rescinded as far as exceptions go. That doesn't make reflection any less necessary btw. It just reflects the dynamism of exception paths.
 We shouldn't be
 using Variant[string] for this, because there's another problem
 associated with it: suppose MyException sometimes gets "extra_info1" and
 "extra_info2" tacked onto it, on its way up the call stack, and
 sometimes not. Now what should the catcher do?
Use a central loop to render the information.
 How do you format this
 exception?
With a string template as has been discussed.
 Should the format string include extra_info1 and extra_info2,
 or not? If it doesn't, what's the use of this extra info? If it does,
 what happens if these fields are missing?
Decision belongs to the string template engine.
 This is what I mean by not being able to depend on whether some data is
 there. Ultimately, to do anything useful with the info in the object,
 you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
 Preferably, the object's type will tell
 you exactly what's there, then you do a simple map from type to list of
 available attributes (e.g., map exception type to format string with
 known, static list of attributes). But if the type doesn't guarantee
 what data will be present, then your code becomes vastly more complex,
 you have to deal with potentially all possible combinations of what's
 there and what isn't.
I think you are misunderstanding the mechanisms involved. There is no combinatorial code, just simple iteration and such. Dealing with distinct exceptions with distinct code IS combinatorial, repetitive, and non-scalable.
 Instead of a single format string for a single
 exception type, you now have a combinatorial explosion of format strings
 for every possible combination of missing/present fields in the
 exception object.
No, I'm afraid there's a sizeable misunderstanding here. Andrei
Feb 20 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 Jose's argument convinced me otherwise. I retract my agreement.
...
 No, I'm afraid there's a sizeable misunderstanding here.


 Andrei
Hahah, yeah, I think there is a sizeable misunderstanding: unless you are referring to another guy with a spanish name in this thread, (which I haven't found). My name is Juan Manuel (people call me: Juanma, JM or Juan, but Jos is a first! I wouldn't mind John which is the 'translation' of my name). Back to the nasty argument. I think that the example that everyone wants is this one. If anyone solves this one without Variant[string] then it's a better solution than Variant[string]. (I repaste it from an above reply I gave): [..] For instance: a C library wrapper, which gets the library errors encoded as some error code and throws them as exceptions. Shouldn't the library throw a FileNotFoundException when that's the error, instead of throwing a LibraryException that has the error code in a field? So the correct thing to do is: after a library call, the wrapper checks the last error code number with a switch statement, and deciding which standard exception type to throw (defaulting to whatever you like if the error code doesn't map to a standard D exception). Then you add the error code to the Variant[string], and any other extra info. That way, exception types can be standard. So, to keep D exception types standard reusable and respected by future code, you must follow the Open-Closed design principle (nicest principle of OO design ever). [..] Adding the Variant[string] is considered applying the great Open-Closed Design Principle: -Open for reuse. -Closed for modification. http://www.objectmentor.com/resources/articles/ocp.pdf --jm On 02/20/2012 09:38 PM, Andrei Alexandrescu wrote:
 On 2/20/12 6:25 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote:
 Formatting should use class reflection. We already discussed that, and
 we already agreed that was the superior approach.
Jose's argument convinced me otherwise. I retract my agreement.
 When you're catching a specific exception, you're catching it with the
 view that it will contain precisely information X, Y, Z that you need to
 recover from the problem. If you don't need to catch something, then
 don't put the catch block there.
That's extremely rare in my experience, and only present in toy examples that contain a ton of "..." magic.
 The problem with using Variant[string] is that everything gets lumped
 into one Exception object, and there's no way to only catch the
 Exception that happens to have variables "p", "q", and "r" set in the
 Variant[string].
No. You are still free to define as many exception classes as you deem necessary. Please let's not pit the hash against the hierarchy again. This is confusing the role of the two. Consider the hash an interface function you want to occasionally implement.
 You have to catch an exception type that includes all
 sorts of combinations of data in Variant[string], then manually do tests
 to single out the exception you want, and rethrow the rest. That's where
 the ugliness comes from.
Yah, that would suck, but it's not at all what I say.
 [...]
 The code with Variant[string] does not need combinatorial testing if
 it wants to do a uniform action (such as formatting). It handles
 formatting uniformly, and if it wants to look for one particular field
 it inserts a test.
Again, we've already agreed class reflection is the proper solution to this one.
Agreement rescinded. Sorry! Jose's point was just too good, and reminded me of a pain point I so long had with exception, I'd got used to it as a fact of life.
 And then what do you do if you're depending on a particular field to
 be set, but it's not? Rethrow the exception? Then you have the stack
 trace reset problem.
Don't forget that Variant[string] does not preclude distinct exception types. It's not one or the other.
[...] Agreed. But it shouldn't be the be-all and end-all of data passed in exceptions. If anything, it should only be rarely used, with most exception classes using static fields to convey relevant information.
And to perfectly help code duplication everywhere.
 I can see the usefulness of using Variant[string] as a way of
 "decorating" exceptions with "extra attributes", but it shouldn't be the
 primary way of conveying information from the throw site to the catch
 site.

 As for iterating over the information in the most derived class, for
 formatting, etc., class reflection is the way to go.
Agreement rescinded as far as exceptions go. That doesn't make reflection any less necessary btw. It just reflects the dynamism of exception paths.
 We shouldn't be
 using Variant[string] for this, because there's another problem
 associated with it: suppose MyException sometimes gets "extra_info1" and
 "extra_info2" tacked onto it, on its way up the call stack, and
 sometimes not. Now what should the catcher do?
Use a central loop to render the information.
 How do you format this
 exception?
With a string template as has been discussed.
 Should the format string include extra_info1 and extra_info2,
 or not? If it doesn't, what's the use of this extra info? If it does,
 what happens if these fields are missing?
Decision belongs to the string template engine.
 This is what I mean by not being able to depend on whether some data is
 there. Ultimately, to do anything useful with the info in the object,
 you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
 Preferably, the object's type will tell
 you exactly what's there, then you do a simple map from type to list of
 available attributes (e.g., map exception type to format string with
 known, static list of attributes). But if the type doesn't guarantee
 what data will be present, then your code becomes vastly more complex,
 you have to deal with potentially all possible combinations of what's
 there and what isn't.
I think you are misunderstanding the mechanisms involved. There is no combinatorial code, just simple iteration and such. Dealing with distinct exceptions with distinct code IS combinatorial, repetitive, and non-scalable.
 Instead of a single format string for a single
 exception type, you now have a combinatorial explosion of format strings
 for every possible combination of missing/present fields in the
 exception object.
No, I'm afraid there's a sizeable misunderstanding here. Andrei
Feb 20 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
oops, sorry!! I just saw a post by someone named Jose. My thousand apollogies!!

On 02/20/2012 10:01 PM, Juan Manuel Cabo wrote:
 Jose's argument convinced me otherwise. I retract my agreement.
...
 No, I'm afraid there's a sizeable misunderstanding here.


 Andrei
Hahah, yeah, I think there is a sizeable misunderstanding: unless you are referring to another guy with a spanish name in this thread, (which I haven't found). My name is Juan Manuel (people call me: Juanma, JM or Juan, but Jos is a first! I wouldn't mind John which is the 'translation' of my name). Back to the nasty argument. I think that the example that everyone wants is this one. If anyone solves this one without Variant[string] then it's a better solution than Variant[string]. (I repaste it from an above reply I gave): [..] For instance: a C library wrapper, which gets the library errors encoded as some error code and throws them as exceptions. Shouldn't the library throw a FileNotFoundException when that's the error, instead of throwing a LibraryException that has the error code in a field? So the correct thing to do is: after a library call, the wrapper checks the last error code number with a switch statement, and deciding which standard exception type to throw (defaulting to whatever you like if the error code doesn't map to a standard D exception). Then you add the error code to the Variant[string], and any other extra info. That way, exception types can be standard. So, to keep D exception types standard reusable and respected by future code, you must follow the Open-Closed design principle (nicest principle of OO design ever). [..] Adding the Variant[string] is considered applying the great Open-Closed Design Principle: -Open for reuse. -Closed for modification. http://www.objectmentor.com/resources/articles/ocp.pdf --jm On 02/20/2012 09:38 PM, Andrei Alexandrescu wrote:
 On 2/20/12 6:25 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote:
 Formatting should use class reflection. We already discussed that, and
 we already agreed that was the superior approach.
Jose's argument convinced me otherwise. I retract my agreement.
 When you're catching a specific exception, you're catching it with the
 view that it will contain precisely information X, Y, Z that you need to
 recover from the problem. If you don't need to catch something, then
 don't put the catch block there.
That's extremely rare in my experience, and only present in toy examples that contain a ton of "..." magic.
 The problem with using Variant[string] is that everything gets lumped
 into one Exception object, and there's no way to only catch the
 Exception that happens to have variables "p", "q", and "r" set in the
 Variant[string].
No. You are still free to define as many exception classes as you deem necessary. Please let's not pit the hash against the hierarchy again. This is confusing the role of the two. Consider the hash an interface function you want to occasionally implement.
 You have to catch an exception type that includes all
 sorts of combinations of data in Variant[string], then manually do tests
 to single out the exception you want, and rethrow the rest. That's where
 the ugliness comes from.
Yah, that would suck, but it's not at all what I say.
 [...]
 The code with Variant[string] does not need combinatorial testing if
 it wants to do a uniform action (such as formatting). It handles
 formatting uniformly, and if it wants to look for one particular field
 it inserts a test.
Again, we've already agreed class reflection is the proper solution to this one.
Agreement rescinded. Sorry! Jose's point was just too good, and reminded me of a pain point I so long had with exception, I'd got used to it as a fact of life.
 And then what do you do if you're depending on a particular field to
 be set, but it's not? Rethrow the exception? Then you have the stack
 trace reset problem.
Don't forget that Variant[string] does not preclude distinct exception types. It's not one or the other.
[...] Agreed. But it shouldn't be the be-all and end-all of data passed in exceptions. If anything, it should only be rarely used, with most exception classes using static fields to convey relevant information.
And to perfectly help code duplication everywhere.
 I can see the usefulness of using Variant[string] as a way of
 "decorating" exceptions with "extra attributes", but it shouldn't be the
 primary way of conveying information from the throw site to the catch
 site.

 As for iterating over the information in the most derived class, for
 formatting, etc., class reflection is the way to go.
Agreement rescinded as far as exceptions go. That doesn't make reflection any less necessary btw. It just reflects the dynamism of exception paths.
 We shouldn't be
 using Variant[string] for this, because there's another problem
 associated with it: suppose MyException sometimes gets "extra_info1" and
 "extra_info2" tacked onto it, on its way up the call stack, and
 sometimes not. Now what should the catcher do?
Use a central loop to render the information.
 How do you format this
 exception?
With a string template as has been discussed.
 Should the format string include extra_info1 and extra_info2,
 or not? If it doesn't, what's the use of this extra info? If it does,
 what happens if these fields are missing?
Decision belongs to the string template engine.
 This is what I mean by not being able to depend on whether some data is
 there. Ultimately, to do anything useful with the info in the object,
 you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
 Preferably, the object's type will tell
 you exactly what's there, then you do a simple map from type to list of
 available attributes (e.g., map exception type to format string with
 known, static list of attributes). But if the type doesn't guarantee
 what data will be present, then your code becomes vastly more complex,
 you have to deal with potentially all possible combinations of what's
 there and what isn't.
I think you are misunderstanding the mechanisms involved. There is no combinatorial code, just simple iteration and such. Dealing with distinct exceptions with distinct code IS combinatorial, repetitive, and non-scalable.
 Instead of a single format string for a single
 exception type, you now have a combinatorial explosion of format strings
 for every possible combination of missing/present fields in the
 exception object.
No, I'm afraid there's a sizeable misunderstanding here. Andrei
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 7:02 PM, Juan Manuel Cabo wrote:
 oops, sorry!! I just saw a post by someone named Jose. My thousand apollogies!!
I got confused. It was your argument I meant to refer to - adding info to the exception in flight. Andrei
Feb 20 2012
parent reply "foobar" <foo bar.com> writes:
On Tuesday, 21 February 2012 at 02:23:58 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 7:02 PM, Juan Manuel Cabo wrote:
 oops, sorry!! I just saw a post by someone named Jose. My 
 thousand apollogies!!
I got confused. It was your argument I meant to refer to - adding info to the exception in flight. Andrei
I'd implement this along these lines: class WithErrorCode(E) : E { int errorCode; this(Args)(int err, Args args) { this.errorCode = err; super(args); } } and add this wrapper where relevant. e.g. replace: throw new FileNotFoundException(...); with something like: throw new WithErrorCode!FileNotFoundException(-1, ...); This is a localized change that doesn't affect all uses of exceptions and it remains type-safe. surely this is a better solution than the hash table?
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 4:48 AM, foobar wrote:
 On Tuesday, 21 February 2012 at 02:23:58 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 7:02 PM, Juan Manuel Cabo wrote:
 oops, sorry!! I just saw a post by someone named Jose. My thousand
 apollogies!!
I got confused. It was your argument I meant to refer to - adding info to the exception in flight. Andrei
I'd implement this along these lines: class WithErrorCode(E) : E { int errorCode; this(Args)(int err, Args args) { this.errorCode = err; super(args); } } and add this wrapper where relevant. e.g. replace: throw new FileNotFoundException(...); with something like: throw new WithErrorCode!FileNotFoundException(-1, ...); This is a localized change that doesn't affect all uses of exceptions and it remains type-safe. surely this is a better solution than the hash table?
The two approaches don't compete as one is static and the other is dynamic. For example, how does one add contextual information "While opening table in {database}" to the current exception of type WithErrorCode!FileNotFoundException? Andrei
Feb 21 2012
parent reply "foobar" <foo bar.com> writes:
On Tuesday, 21 February 2012 at 14:13:55 UTC, Andrei Alexandrescu 
wrote:
 On 2/21/12 4:48 AM, foobar wrote:
 On Tuesday, 21 February 2012 at 02:23:58 UTC, Andrei 
 Alexandrescu wrote:
 On 2/20/12 7:02 PM, Juan Manuel Cabo wrote:
 oops, sorry!! I just saw a post by someone named Jose. My 
 thousand
 apollogies!!
I got confused. It was your argument I meant to refer to - adding info to the exception in flight. Andrei
I'd implement this along these lines: class WithErrorCode(E) : E { int errorCode; this(Args)(int err, Args args) { this.errorCode = err; super(args); } } and add this wrapper where relevant. e.g. replace: throw new FileNotFoundException(...); with something like: throw new WithErrorCode!FileNotFoundException(-1, ...); This is a localized change that doesn't affect all uses of exceptions and it remains type-safe. surely this is a better solution than the hash table?
The two approaches don't compete as one is static and the other is dynamic. For example, how does one add contextual information "While opening table in {database}" to the current exception of type WithErrorCode!FileNotFoundException? Andrei
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"); This approach fails if you don't know ahead of time what *fields* you want to add to your exception but I'd argue that this is unrealistic. An exception is thrown as a response to a specific erroneous condition which means you already know *what* the problem is and what kind of data is needed to describe it. Can you offer a real world use-case where the above isn't sufficient?
Feb 21 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 8:38 AM, foobar wrote:
 On Tuesday, 21 February 2012 at 14:13:55 UTC, Andrei Alexandrescu wrote:
 On 2/21/12 4:48 AM, foobar wrote:
 On Tuesday, 21 February 2012 at 02:23:58 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 7:02 PM, Juan Manuel Cabo wrote:
 oops, sorry!! I just saw a post by someone named Jose. My thousand
 apollogies!!
I got confused. It was your argument I meant to refer to - adding info to the exception in flight. Andrei
I'd implement this along these lines: class WithErrorCode(E) : E { int errorCode; this(Args)(int err, Args args) { this.errorCode = err; super(args); } } and add this wrapper where relevant. e.g. replace: throw new FileNotFoundException(...); with something like: throw new WithErrorCode!FileNotFoundException(-1, ...); This is a localized change that doesn't affect all uses of exceptions and it remains type-safe. surely this is a better solution than the hash table?
The two approaches don't compete as one is static and the other is dynamic. For example, how does one add contextual information "While opening table in {database}" to the current exception of type WithErrorCode!FileNotFoundException? Andrei
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");
I don't understand this example.
 This approach fails if you don't know ahead of time what *fields* you
 want to add to your exception but I'd argue that this is unrealistic. An
 exception is thrown as a response to a specific erroneous condition
 which means you already know *what* the problem is and what kind of data
 is needed to describe it.

 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
Feb 21 2012
parent "foobar" <foo bar.com> writes:
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?
Feb 21 2012
prev sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 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
Feb 21 2012
parent reply "foobar" <foo bar.com> writes:
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.
Feb 21 2012
parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 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
Feb 21 2012
next sibling parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
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
Feb 21 2012
prev sibling parent reply "foobar" <foo bar.com> writes:
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.
Feb 21 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 21 2012
parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
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
Feb 21 2012
next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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.
Feb 21 2012
prev sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
Feb 21 2012
prev sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 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.
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Feb 21 2012
next sibling parent reply Jacob Carlborg <doob me.com> writes:
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
Feb 21 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 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.
Feb 21 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-21 19:33, Juan Manuel Cabo wrote:
 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).
That's because we are limited by the database API. If you created a new database from scratch, completely written in D, perhaps even object oriented, you could return the correct object form the beginning.
 Do you have an alternative solution that allows to extend an exception
 object with extra information, while keeping it the same class?
No, but that's what subclasses are used for.
 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.
-- /Jacob Carlborg
Feb 21 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 12: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.
I think I do. It's also fair to ask you if you are sure you understood my point. Andrei
Feb 21 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-21 21:06, Andrei Alexandrescu wrote:
 On 2/21/12 12: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.
I think I do. It's also fair to ask you if you are sure you understood my point. Andrei
As I said, it seems you want to push up implementation details specific to a given subclass to the base class even though it shouldn't be pushed up. -- /Jacob Carlborg
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 2:26 PM, Jacob Carlborg wrote:
 As I said, it seems you want to push up implementation details specific
 to a given subclass to the base class even though it shouldn't be pushed
 up.
I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei
Feb 21 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-21 21:27, Andrei Alexandrescu wrote:
 On 2/21/12 2:26 PM, Jacob Carlborg wrote:
 As I said, it seems you want to push up implementation details specific
 to a given subclass to the base class even though it shouldn't be pushed
 up.
I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei
Well, I don't think that is the right approach. As many others have explained, error messages are only a small part of exception handling. If you do want to have a generic way of getting an error message out of an exception, what's wrong with toString? Or a new method that formats the error messages. No need to push up the instance variables to the base class. -- /Jacob Carlborg
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 2:42 PM, Jacob Carlborg wrote:
 On 2012-02-21 21:27, Andrei Alexandrescu wrote:
 On 2/21/12 2:26 PM, Jacob Carlborg wrote:
 As I said, it seems you want to push up implementation details specific
 to a given subclass to the base class even though it shouldn't be pushed
 up.
I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei
Well, I don't think that is the right approach. As many others have explained, error messages are only a small part of exception handling.
I agree. Also, one interface function is only a small part of a class hierarchy.
 If you do want to have a generic way of getting an error message out of
 an exception, what's wrong with toString? Or a new method that formats
 the error messages. No need to push up the instance variables to the
 base class.
This has been answered in the long thread. In brief, toString loses too much information and putting formatting inside exceptions is not the right place. Andrei
Feb 21 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-21 22:08, Andrei Alexandrescu wrote:
 On 2/21/12 2:42 PM, Jacob Carlborg wrote:
 On 2012-02-21 21:27, Andrei Alexandrescu wrote:
 On 2/21/12 2:26 PM, Jacob Carlborg wrote:
 As I said, it seems you want to push up implementation details specific
 to a given subclass to the base class even though it shouldn't be
 pushed
 up.
I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei
Well, I don't think that is the right approach. As many others have explained, error messages are only a small part of exception handling.
I agree. Also, one interface function is only a small part of a class hierarchy.
 If you do want to have a generic way of getting an error message out of
 an exception, what's wrong with toString? Or a new method that formats
 the error messages. No need to push up the instance variables to the
 base class.
This has been answered in the long thread. In brief, toString loses too much information and putting formatting inside exceptions is not the right place. Andrei
Now I'm completely lost. According to what I've read this is thread this is exactly what you want to do, put the formatting inside the exceptions. -- /Jacob Carlborg
Feb 21 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 08:22:21 Jacob Carlborg wrote:
 Now I'm completely lost. According to what I've read this is thread this
 is exactly what you want to do, put the formatting inside the exceptions.
No. He wants to provide a way for an external function to generically generate strings according to the format that you want when you generate the string. So, some function would take a formatting string of some kind and then read the corresponding values form the Variant[string] in Exception and generate a string according to that format string. How exactly that works, I don't understand (something about a string template language), but that's the idea. So, while you could still use toString, there would be a way to generate strings formatted the way that _you_ want rather than how toString would do it - and to do it in a generic manner. As long as this doesn't mean using Variant[string] as the way to inject all of the extra data into exceptions and we still use an exception hierarchy with the appropriate data members in derived exceptions, then I don't really see that as a problem. The problem is if we then also get rid of the hierarchy and/or try and put all of the data is the Variant[string] and only in the Variant[string].
From the sounds of it, we have _some_ agreement to have an exception hierarchy 
with data members in derived classes where appropriate but to add the Variant[string] bit to Exception to enable the passing of other data that you might want but is not in the exception type normally as well as enable the fancy string formatting stuff that Andrei wants. But this thread is so long and complicated that I think that many of us are just confused. - Jonathan M Davis
Feb 21 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-22 08:33, Jonathan M Davis wrote:
 On Wednesday, February 22, 2012 08:22:21 Jacob Carlborg wrote:
 Now I'm completely lost. According to what I've read this is thread this
 is exactly what you want to do, put the formatting inside the exceptions.
No. He wants to provide a way for an external function to generically generate strings according to the format that you want when you generate the string. So, some function would take a formatting string of some kind and then read the corresponding values form the Variant[string] in Exception and generate a string according to that format string. How exactly that works, I don't understand (something about a string template language), but that's the idea. So, while you could still use toString, there would be a way to generate strings formatted the way that _you_ want rather than how toString would do it - and to do it in a generic manner. As long as this doesn't mean using Variant[string] as the way to inject all of the extra data into exceptions and we still use an exception hierarchy with the appropriate data members in derived exceptions, then I don't really see that as a problem. The problem is if we then also get rid of the hierarchy and/or try and put all of the data is the Variant[string] and only in the Variant[string].
I agree.
 From the sounds of it, we have _some_ agreement to have an exception hierarchy
with data members in derived classes where appropriate but to add the Variant[string] bit to Exception to enable the passing of other data that you might want but is not in the exception type normally as well as enable the fancy string formatting stuff that Andrei wants. But this thread is so long and complicated that I think that many of us are just confused. - Jonathan M Davis
Ok, I see. -- /Jacob Carlborg
Feb 21 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/22/12 1:22 AM, Jacob Carlborg wrote:
 Now I'm completely lost. According to what I've read this is thread this
 is exactly what you want to do, put the formatting inside the exceptions.
No, just have exceptions inform an external formatter. Andrei
Feb 22 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-22 15:01, Andrei Alexandrescu wrote:
 On 2/22/12 1:22 AM, Jacob Carlborg wrote:
 Now I'm completely lost. According to what I've read this is thread this
 is exactly what you want to do, put the formatting inside the exceptions.
No, just have exceptions inform an external formatter. Andrei
Ok, I see. -- /Jacob Carlborg
Feb 22 2012
prev sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 1:17 PM, Jonathan M Davis wrote:
 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_.
Well this is just a series of assertions that conveys no information.
 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.
Again, just an assertion.
 Aside from printing strings,
 trying to deal with exceptions generically just does not make sense.
Assertion.
 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.
Assertion.
 I think that you're pushing the OO angle too hard onto exceptions.
I thought I was pushing the generics angle, and OO people explained it to me that that was wrong.
 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.
I'm sorry, I was unable to derive information from this post. It's a string of assertion without any backing. Andrei
Feb 21 2012
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, February 21, 2012 14:15:03 Andrei Alexandrescu wrote:
 I thought I was pushing the generics angle, and OO people explained it 
 to me that that was wrong.
You were just talking about applying OO policy to exceptions, which just doesn't make sense for most things, because they're just not polymorphic in terms of how they're handled.
 I'm sorry, I was unable to derive information from this post. It's a
 string of assertion without any backing.
Just look at exceptions. You catch them by type and then use that specific type. You don't generally operate on them via polymorphic functions. Derived classes usually hold more data but do not change the behavior of any existing functions. Rather, you look at the type of the exception and the values of the member variables in that concrete type to decide how to handle the exception based on that information. That's not particularly OO or polymorphic at all. Yes, you can catch more generic exceptions instead of the concrete ones, but that doesn't generally mean that you end up calling virtual functions on the base class. Rather, it means that your code cares less about what exactly went wrong. You're still not likely to be calling a virtual function on the exception type, because exceptions carry information, not behavior. The one major exception to this is toString. Having generic error message generating capabilities is useful. And not even that needs to be polymorphic. With Exception, the return value of toString is generated from the msg property which was passed in via its constructor. And toString isn't something that you normally override with Exception. Rather, it's the msg argument which changes. At minimum, if you do override toString, you need to call the base class' toString or you'll lose the stack trace. It's just not really designed with overriding in mind. I really don't see how anyone can make much of an argument for exceptions being OO beyond the fact that they're objects given that handling them means doing the complete opposite of typical OO. With exceptions, it's how they're handled that changes from type to type, not their internal behavior, whereas OO focuses on changing the behavior of functions in derived classes. And with exceptions, you use the concrete types and not an abstract interface, whereas in OO, the idea is to use an abstract interface. So, I don't see much point in trying to force OO principles on exceptions. Trying to treat them more generically with regard to generating error messages makes some sense, but that's about it. And that still isn't particularly OO - especially when the proposed solution is to use Variant[string] rather than to do something with toString. But if you want to change the message formatting at the catch point (just like all of the other exception behavior is generally done at the catch point), you can't do it with toString (at least, not without changing the internal state of the exception before calling toString). OO just doesn't fit. - Jonathan M Davis
Feb 21 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 07:43:32PM -0500, Jonathan M Davis wrote:
 On Tuesday, February 21, 2012 14:15:03 Andrei Alexandrescu wrote:
 I thought I was pushing the generics angle, and OO people explained
 it to me that that was wrong.
I've changed my mind. Now I'm trying to see if the generics angle has some possibilities. Maybe, maybe not, but we'll never know without experimenting with it. I think your is_transient idea can be expanded upon. The way exceptions are currently implemented, they only carry information, not behaviour, as Jonathan said rightly. The try/catch mechanism essentially reduces to "I've hit a problem I don't know how to solve, here's a description of it". There's no behaviour in there. The throwing code has already given up. It's up to the catcher to interpret the description of the problem and figure out how to recover. To recover well, the catcher must know the intimate details of the problem well. So you have the situation of a specific catcher catching a specific Exception subclass. This is not an ideal situation, because now high-level code needs to know the specifics of low-level errors. With your is_transient idea, though, this begins to change. Now we're no longer just describing the problem. When is_transient=1, it means the thrower is suggesting that perhaps retrying would help. Of course, it's up to the catcher whether or not to follow through with this suggestion, but it's one step up from "here's a description of the problem, figure out the solution yourself". But now the catcher doesn't necessarily have to know the specifics of the low-level problem. It knows at least one strategy that might fix the problem, regardless of what the problem is: retry the operation. This is good, because the low-level code, which knows the problem best, can offer a useful suggestion (retry). The high-level code can just take the suggestion or not; it no longer needs to know low-level details. But why stop there? Since the low-level code knows all the dirty details about the problem, it's in the best position to offer meaningful recovery suggestions. It just has to communicate these possible recovery strategies to the high-level code, and let the high-level code decide what to do. The high-level code doesn't need to know how to implement these strategies -- it's not in the best position to know that anyway. It just knows, here's a list of recovery strategies, I can go ahead with one of them, or just call it quits and unwind the stack. The low-level code is what implements each strategy. Of course, in order for the high-level code to meaningfully choose between alternative strategies, the strategies themselves must be generic concepts; otherwise we're still tying high-level code to low-level details. So we need to identify generic categories of exceptions for which this kind of generic recovery is meaningful -- which is what I've done in another post. I won't repeat the details here, but I just want to say that I think this angle merits some investigation. It allows us to factor out exceptions which can be resolved by commonly used recovery strategies so that we don't have to keep writing tons and tons of exception-specific recovery code everywhere. Some specific code is still needed, no doubt, there's always special cases that need specific handling. But if enough exceptions can be adequately dealt with generically, then we don't need to write specific code for them. We can simply reuse generic recovery solutions. T -- Real Programmers use "cat > a.out".
Feb 21 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 22/02/2012 06:47, H. S. Teoh a crit :
 On Tue, Feb 21, 2012 at 07:43:32PM -0500, Jonathan M Davis wrote:
 On Tuesday, February 21, 2012 14:15:03 Andrei Alexandrescu wrote:
 I thought I was pushing the generics angle, and OO people explained
 it to me that that was wrong.
I've changed my mind. Now I'm trying to see if the generics angle has some possibilities. Maybe, maybe not, but we'll never know without experimenting with it. I think your is_transient idea can be expanded upon. The way exceptions are currently implemented, they only carry information, not behaviour, as Jonathan said rightly. The try/catch mechanism essentially reduces to "I've hit a problem I don't know how to solve, here's a description of it". There's no behaviour in there. The throwing code has already given up. It's up to the catcher to interpret the description of the problem and figure out how to recover. To recover well, the catcher must know the intimate details of the problem well. So you have the situation of a specific catcher catching a specific Exception subclass. This is not an ideal situation, because now high-level code needs to know the specifics of low-level errors. With your is_transient idea, though, this begins to change. Now we're no longer just describing the problem. When is_transient=1, it means the thrower is suggesting that perhaps retrying would help. Of course, it's up to the catcher whether or not to follow through with this suggestion, but it's one step up from "here's a description of the problem, figure out the solution yourself". But now the catcher doesn't necessarily have to know the specifics of the low-level problem. It knows at least one strategy that might fix the problem, regardless of what the problem is: retry the operation. This is good, because the low-level code, which knows the problem best, can offer a useful suggestion (retry). The high-level code can just take the suggestion or not; it no longer needs to know low-level details. But why stop there? Since the low-level code knows all the dirty details about the problem, it's in the best position to offer meaningful recovery suggestions. It just has to communicate these possible recovery strategies to the high-level code, and let the high-level code decide what to do. The high-level code doesn't need to know how to implement these strategies -- it's not in the best position to know that anyway. It just knows, here's a list of recovery strategies, I can go ahead with one of them, or just call it quits and unwind the stack. The low-level code is what implements each strategy. Of course, in order for the high-level code to meaningfully choose between alternative strategies, the strategies themselves must be generic concepts; otherwise we're still tying high-level code to low-level details. So we need to identify generic categories of exceptions for which this kind of generic recovery is meaningful -- which is what I've done in another post. I won't repeat the details here, but I just want to say that I think this angle merits some investigation. It allows us to factor out exceptions which can be resolved by commonly used recovery strategies so that we don't have to keep writing tons and tons of exception-specific recovery code everywhere. Some specific code is still needed, no doubt, there's always special cases that need specific handling. But if enough exceptions can be adequately dealt with generically, then we don't need to write specific code for them. We can simply reuse generic recovery solutions. T
100% Agree. Additionnaly, I would mention that the transient isn't a caracteristic of the Exception, but of the recovery strategy.
Feb 22 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Feb 22, 2012 at 11:53:39AM +0100, deadalnix wrote:
[...]
 Additionnaly, I would mention that the transient isn't a caracteristic
 of the Exception, but of the recovery strategy.
Technically correct. Though I'm playing with the idea of making recovery strategies a property of an exception - since a recovery strategy is meaningless without an associated exception (or problem). I need to think this through a bit more, though, as to how to correctly implement this. T -- The volume of a pizza of thickness a and radius z can be described by the following formula: pi zz a. -- Wouter Verhelst
Feb 22 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 22/02/2012 18:50, H. S. Teoh a crit :
 On Wed, Feb 22, 2012 at 11:53:39AM +0100, deadalnix wrote:
 [...]
 Additionnaly, I would mention that the transient isn't a caracteristic
 of the Exception, but of the recovery strategy.
Technically correct. Though I'm playing with the idea of making recovery strategies a property of an exception - since a recovery strategy is meaningless without an associated exception (or problem). I need to think this through a bit more, though, as to how to correctly implement this.
I did though about this. This isn't the right way. Recovery strategy doesn't have any meaning at the catch point, so we shouldn't make it a property of Exception. And sometime you don't care about the Exception. If you try to connect something that is know to fail for exemple, you really don't want to know what went wrong. You just want to try again with some backoff. I do think you made a point with the handler getting an Exception and a recovery stratgy as parameter, but it is still unclear where all this goes to me.
Feb 22 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 10:01:03PM -0300, Juan Manuel Cabo wrote:
[...]
 Back to the nasty argument. I think that the example that everyone
 wants is this one. If anyone solves this one without Variant[string]
 then it's a better solution than Variant[string]. (I repaste it
 from an above reply I gave):
 
   [..]
   For instance: a C library wrapper, which gets the library errors encoded
   as some error code and throws them as exceptions. Shouldn't the library
   throw a FileNotFoundException when that's the error, instead of throwing
   a LibraryException that has the error code in a field?
 
   So the correct thing to do is: after a library call, the wrapper
   checks the last error code number with a switch statement, and deciding
   which standard exception type to throw (defaulting to whatever you like
   if the error code doesn't map to a standard D exception). Then you
   add the error code to the Variant[string], and any other extra info.
But why bother with the error code at all? If you get a FileNotFoundException, you already know all there is to know about the problem, adding errno to it is redundant and only encourages code that's bound to a specific implementation. Instead, Phobos should present a self-consistent API that's independent of what it uses to implement it, be it C stdio (errno) or C++ iostreams or Oracle driver (Oracle-specific error codes) or Postgresql driver (Postgresql-specific error codes), or what have you. For error codes that *don't* have a direct mapping to standard exceptions, you can just encapsulate the errno (or whatever) inside a specific catch-all exception type dedicated to catch these sorts of unmapped cases, so that code that *does* know what errno can just catch this exception and interpret what happened. General, platform-independent code need not know what this exception is at all, they can just treat it as a general problem and react accordingly. We don't (and shouldn't) expect every app out there to know or care about the errno of a failed operation, especially if it doesn't map to one of the standard exception types.
   That way, exception types can be standard.
 
   So, to keep D exception types standard reusable and respected by
   future code, you must follow the Open-Closed design principle
   (nicest principle of OO design ever).
   [..]
 
 Adding the Variant[string] is considered applying the great
 Open-Closed Design Principle:
 	-Open for reuse.
 	-Closed for modification.
         http://www.objectmentor.com/resources/articles/ocp.pdf
[...] Please bear in mind, I'm not saying that Variant[string] is *completely* useless. I'm just saying that most of the time it's not necessary. Sure there are some cases where it's useful, I've no problem with it being used in those cases. But we shouldn't be using it for all kinds of stuff that can be handled in better ways, e.g., static fields in a derived exception class. T -- My program has no bugs! Only undocumented features...
Feb 20 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
Well... then why did this mistakes exist?:

In dot NET:

	ComException - Exception encapsulating COM HRESULT information
        SEHException	Exception encapsulating Win32 structured exception
handling information.z

	http://msdn.microsoft.com/en-us/library/z4c5tckx%28v=VS.71%29.aspx

And why do you think that a thing like standardizing DatabaseException
never survives users, and that each database manager library defines its
own top *DatabaseException base class?

This is a universal problem with transversal traits of exceptions.

--jm



On 02/20/2012 10:22 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 10:01:03PM -0300, Juan Manuel Cabo wrote:
 [...]
 Back to the nasty argument. I think that the example that everyone
 wants is this one. If anyone solves this one without Variant[string]
 then it's a better solution than Variant[string]. (I repaste it
 from an above reply I gave):

   [..]
   For instance: a C library wrapper, which gets the library errors encoded
   as some error code and throws them as exceptions. Shouldn't the library
   throw a FileNotFoundException when that's the error, instead of throwing
   a LibraryException that has the error code in a field?

   So the correct thing to do is: after a library call, the wrapper
   checks the last error code number with a switch statement, and deciding
   which standard exception type to throw (defaulting to whatever you like
   if the error code doesn't map to a standard D exception). Then you
   add the error code to the Variant[string], and any other extra info.
But why bother with the error code at all? If you get a FileNotFoundException, you already know all there is to know about the problem, adding errno to it is redundant and only encourages code that's bound to a specific implementation. Instead, Phobos should present a self-consistent API that's independent of what it uses to implement it, be it C stdio (errno) or C++ iostreams or Oracle driver (Oracle-specific error codes) or Postgresql driver (Postgresql-specific error codes), or what have you. For error codes that *don't* have a direct mapping to standard exceptions, you can just encapsulate the errno (or whatever) inside a specific catch-all exception type dedicated to catch these sorts of unmapped cases, so that code that *does* know what errno can just catch this exception and interpret what happened. General, platform-independent code need not know what this exception is at all, they can just treat it as a general problem and react accordingly. We don't (and shouldn't) expect every app out there to know or care about the errno of a failed operation, especially if it doesn't map to one of the standard exception types.
   That way, exception types can be standard.

   So, to keep D exception types standard reusable and respected by
   future code, you must follow the Open-Closed design principle
   (nicest principle of OO design ever).
   [..]

 Adding the Variant[string] is considered applying the great
 Open-Closed Design Principle:
 	-Open for reuse.
 	-Closed for modification.
         http://www.objectmentor.com/resources/articles/ocp.pdf
[...] Please bear in mind, I'm not saying that Variant[string] is *completely* useless. I'm just saying that most of the time it's not necessary. Sure there are some cases where it's useful, I've no problem with it being used in those cases. But we shouldn't be using it for all kinds of stuff that can be handled in better ways, e.g., static fields in a derived exception class. T
Feb 20 2012
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 10:32:48PM -0300, Juan Manuel Cabo wrote:
 Well... then why did this mistakes exist?:
 
 In dot NET:
 
 	ComException - Exception encapsulating COM HRESULT information
         SEHException	Exception encapsulating Win32 structured exception
handling information.z
 
 	http://msdn.microsoft.com/en-us/library/z4c5tckx%28v=VS.71%29.aspx
I couldn't tell you, I didn't design this.
 And why do you think that a thing like standardizing DatabaseException
 never survives users, and that each database manager library defines
 its own top *DatabaseException base class?
Perhaps because when users chose to use the Oracle engine, they want to deal with Oracle-specific features including Oracle error codes directly? So a generalized SQL exception designed under some idealized database framework would only get in their way?
 This is a universal problem with transversal traits of exceptions.
[...] True, so now we're trying to fix this by having *both* an exception class hierarchy *and* what amounts to a generic varbind that stores transversal information? If Oracle error codes are what the user wants, then giving them a pretty exception class hierarchy is sorta pointless. They just want an OracleException containing the Oracle error code. They couldn't care less if there was a SQLParseErrorException or a SQLCannotAcquireLockException. It would be a waste of effort to build a pretty hierarchy for them if they're just going to ignore it and use Oracle error codes anyway. If we're really trying to solve transversal problems, then I have the feeling that this will require something more than just slapping a Variant[string] onto the Exception class. We need to re-examine the entire model of what exceptions are and what's the best way to deal with them. In the case of Phobos, we're not really dealing with the same kind of situation (not yet, anyway). Phobos modules, in general, aren't merely thin wrappers around a self-contained external module the same way an Oracle API is just a thin wrapper over an independent Oracle driver built by an independent party. So we get to define our own game, in a sense. There's no Oracle error code to map, and we can enforce the proper use of an exception class hierarchy. If someone wants direct access to errno, say, they could just call the C API directly -- D was designed not to require wrappers in that case. But still, I concede that class hierarchies don't solve everything. Sometimes we do need something more. But slapping on a Variant[string] onto the existing system seems like merely a workaround, not a real solution. T -- Study gravitation, it's a field with a lot of potential.
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 7:32 PM, Juan Manuel Cabo wrote:
 Well... then why did this mistakes exist?:

 In dot NET:

 	ComException - Exception encapsulating COM HRESULT information
          SEHException	Exception encapsulating Win32 structured exception
handling information.z

 	http://msdn.microsoft.com/en-us/library/z4c5tckx%28v=VS.71%29.aspx

 And why do you think that a thing like standardizing DatabaseException
 never survives users, and that each database manager library defines its
 own top *DatabaseException base class?

 This is a universal problem with transversal traits of exceptions.
Yes, exactly. Exceptions are poorly equipped to address cross-cutting concerns, which is odd because they are themselves a cross-cutting matter :o). The solution is, I think, to find improved abstractions. Andrei
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 7:22 PM, H. S. Teoh wrote:
 Please bear in mind, I'm not saying that Variant[string] is *completely*
 useless. I'm just saying that most of the time it's not necessary. Sure
 there are some cases where it's useful, I've no problem with it being
 used in those cases. But we shouldn't be using it for all kinds of stuff
 that can be handled in better ways, e.g., static fields in a derived
 exception class.
On the contrary, I predict that once Variant[string] info() is present in Exception, people will start using it with a collective sigh of relief. Andrei
Feb 20 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 06:38:08PM -0600, Andrei Alexandrescu wrote:
[...]
We shouldn't be using Variant[string] for this, because there's
another problem associated with it: suppose MyException sometimes
gets "extra_info1" and "extra_info2" tacked onto it, on its way up
the call stack, and sometimes not. Now what should the catcher do?
Use a central loop to render the information.
Then it's useless for i18n, unless you can magically translate a sequence of unknown values into a different language in a consistent way. It sounds like all this is good for is to print a list of "name=value" pairs. Which is useful, I'll admit, for development/debugging purposes. I don't see how such a thing is useful in i18n. [...]
This is what I mean by not being able to depend on whether some data
is there. Ultimately, to do anything useful with the info in the
object, you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
And the way OO does this is by having the *derived classes* do useful stuff behind a polymorphic interface. What the Variant[string] does is, as you have said previously, to replace the need for many derived classes save a few, thereby throwing away the polymorphism and presenting what amounts to an array of void* to the catch block. For you to now accuse me of going against OO principles is a really strange argument, I must say. I was in fact *advocating* the use of OO by putting the useful information in the derived classes, where they belong.
Preferably, the object's type will tell you exactly what's there,
then you do a simple map from type to list of available attributes
(e.g., map exception type to format string with known, static list of
attributes). But if the type doesn't guarantee what data will be
present, then your code becomes vastly more complex, you have to deal
with potentially all possible combinations of what's there and what
isn't.
I think you are misunderstanding the mechanisms involved. There is no combinatorial code, just simple iteration and such. Dealing with distinct exceptions with distinct code IS combinatorial, repetitive, and non-scalable.
Then pray tell how "simple iteration" will achieve the translation of the data in Variant[string] into a localized string, when there is no guarantee any field will be in the hash at all. Format strings obviously won't work, since you can't have a format string unless you already know what arguments are present. Or are you just going to individually print out "name=value" pairs? In which case, we've been talking at cross purposes. There is no i18n here at all. T -- Those who've learned LaTeX swear by it. Those who are learning LaTeX swear at it. -- Pete Bleackley
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 7:08 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 06:38:08PM -0600, Andrei Alexandrescu wrote:
 [...]
 We shouldn't be using Variant[string] for this, because there's
 another problem associated with it: suppose MyException sometimes
 gets "extra_info1" and "extra_info2" tacked onto it, on its way up
 the call stack, and sometimes not. Now what should the catcher do?
Use a central loop to render the information.
Then it's useless for i18n, unless you can magically translate a sequence of unknown values into a different language in a consistent way. It sounds like all this is good for is to print a list of "name=value" pairs. Which is useful, I'll admit, for development/debugging purposes. I don't see how such a thing is useful in i18n.
As I said, string templates in conjunction with name/value bindings is all that's needed for i18n.
 [...]
 This is what I mean by not being able to depend on whether some data
 is there. Ultimately, to do anything useful with the info in the
 object, you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
And the way OO does this is by having the *derived classes* do useful stuff behind a polymorphic interface.
Exactly, and with this you just destroyed your own design. For it does not build any polymorphic interface, no abstraction. It forever forces code to toil in the concrete (_fileName, _ipAddress, _userName) and fails to elevate any common interface that would foster reusable code.
 What the Variant[string] does is,
 as you have said previously, to replace the need for many derived
 classes save a few, thereby throwing away the polymorphism and
 presenting what amounts to an array of void* to the catch block.
On the contrary. This builds abstraction: class Exception { bool transient(); Variant[string] info(); ... } because it allows client code to treat different types, even types that haven't even been defined yet, uniformly. This does not build anything: class Exception {} class UserNameException : Exception { string _userName; } class NetException : Exception { string _ipAddress; } ...
 For you to now accuse me of going against OO principles is a really
 strange argument, I must say. I was in fact *advocating* the use of OO
 by putting the useful information in the derived classes, where they
 belong.
I'm not accusing of anything, merely noting that your design does not stand the scrutiny of your own principles (which are correct). Both can't be sustained simultaneously. Try a fresh run of your principles over your design. It'll get totaled.
 I think you are misunderstanding the mechanisms involved. There is no
 combinatorial code, just simple iteration and such. Dealing with
 distinct exceptions with distinct code IS combinatorial, repetitive,
 and non-scalable.
Then pray tell how "simple iteration" will achieve the translation of the data in Variant[string] into a localized string, when there is no guarantee any field will be in the hash at all. Format strings obviously won't work, since you can't have a format string unless you already know what arguments are present.
There's no guarantee. That'll be a runtime check in the formatting engine. Andrei
Feb 20 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 08:32:08PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 7:08 PM, H. S. Teoh wrote:
On Mon, Feb 20, 2012 at 06:38:08PM -0600, Andrei Alexandrescu wrote:
[...]
We shouldn't be using Variant[string] for this, because there's
another problem associated with it: suppose MyException sometimes
gets "extra_info1" and "extra_info2" tacked onto it, on its way up
the call stack, and sometimes not. Now what should the catcher do?
Use a central loop to render the information.
Then it's useless for i18n, unless you can magically translate a sequence of unknown values into a different language in a consistent way. It sounds like all this is good for is to print a list of "name=value" pairs. Which is useful, I'll admit, for development/debugging purposes. I don't see how such a thing is useful in i18n.
As I said, string templates in conjunction with name/value bindings is all that's needed for i18n.
And how would such templates be designed, if what is available in the bindings changes depending on the code path that led to the exception? I agree with you that this allows the catch block to do stuff like pass the entire hash to the i18n formatter without needing to know what's in it. And conceivably, the formatter doesn't need to know either, it just gives the bindings to the string templates. But the string templates themselves have to know what's in the bindings. In fact, they *assume* that certain bindings are in there. And when the expected bindings are not there, then the template can't be applied. And you couldn't possibly know this until runtime. Whereas if the fields were fixed at compile-time, then the compiler could verify that what the code thinks is there, is actually there. [...]
 Exactly, and with this you just destroyed your own design. For it does
 not build any polymorphic interface, no abstraction. It forever forces
 code to toil in the concrete (_fileName, _ipAddress, _userName) and
 fails to elevate any common interface that would foster reusable code.
No. With RTTI, there's no need to toil in the concrete at all. Loop over whatever fields happen to be in the object, hand them off to the formatter or whatever it is wants to use them, job finished. The only time you explicitly refer to _fileName, _ipAddress, etc., is when you *specifically want to deal with them*. This is where you *want* the compiler to statically verify that _fileName actually exists in that object. Rather than wait till runtime and then the catch block realizes oops, the field doesn't exist. I'm not negating the fact that the hash is useful for *some* things. I'm just saying that there are occasions where it *shouldn't* be used. There are times when you need compile-type type-checking. [...]
 On the contrary. This builds abstraction:
 
 class Exception
 {
     bool transient();
     Variant[string] info();
     ...
 }
 
 because it allows client code to treat different types, even types
 that haven't even been defined yet, uniformly.
I see your point. But doesn't RTTI already fill the need of genericity? Why not take advantage of compiler-time type checking where it's possible? You can still have the hash for runtime-added stuff, like adding properties to exceptions in transit. I can see a use for that. But why must *everything* be stuffed into the hash? [...]
Then pray tell how "simple iteration" will achieve the translation of
the data in Variant[string] into a localized string, when there is no
guarantee any field will be in the hash at all. Format strings
obviously won't work, since you can't have a format string unless you
already know what arguments are present.
There's no guarantee. That'll be a runtime check in the formatting engine.
[...] So in order for the format string to *not* fail at runtime, the exception must always have the same fields in the bindings, right? Isn't that the same thing as defining the fields statically and using RTTI to iterate over them? That way, the compiler can verify at compile time that the fields are actually there. T -- Real Programmers use "cat > a.out".
Feb 20 2012
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 18:38:08 Andrei Alexandrescu wrote:
 On 2/20/12 6:25 PM, H. S. Teoh wrote:
 This is what I mean by not being able to depend on whether some data is
 there. Ultimately, to do anything useful with the info in the object,
 you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
Exceptions aren't very OO really. Aside from getting a string out of them, you generally don't have any polymorphism involved, and even with the string, there's a good chance that you don't. For instance, the message field which toString uses to generate its string is passed as an argument to Exception's constructor. With exceptions, it's the types that matter. It's not a case of changing behavior based on implementation. With OO, you try _not_ to care about what the type is. With exceptions, on the other hand, you care a great deal. It's inverted from OO. Exceptions hold data but not much behavior. The behavior goes in the catch blocks. If we had a construct which allowed inheritance but not polymorphism (which we don't, since for better or wors, inheritance is conflated with polymorphism in D), then that would make a lot of sense for exceptions. It doesn't really hurt us that exceptions have polymorphism, but it doesn't generally help them do their job at all. If you're going to do anything beyond simply print an error message, you need to know what the exception type is and what data it has. And that's not OO at all. - Jonathan M Davis
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 7:07 PM, Jonathan M Davis wrote:
 On Monday, February 20, 2012 18:38:08 Andrei Alexandrescu wrote:
 On 2/20/12 6:25 PM, H. S. Teoh wrote:
 This is what I mean by not being able to depend on whether some data is
 there. Ultimately, to do anything useful with the info in the object,
 you need to know what's there.
No. You do realize you are positioning yourself straight against every OO principle there is? The entire OO world is predicated on the assumption you _can_ do useful stuff with an object WITHOUT knowing what's there.
Exceptions aren't very OO really. Aside from getting a string out of them, you generally don't have any polymorphism involved, and even with the string, there's a good chance that you don't. For instance, the message field which toString uses to generate its string is passed as an argument to Exception's constructor.
(Cloning and comparison are also essential polymorphic methods to implement.) But then again the view "exceptions are wildebeests" is coming straight from inside the box. We want to improve on that.
 With exceptions, it's the types that matter. It's not a case of changing
 behavior based on implementation. With OO, you try _not_ to care about what
 the type is. With exceptions, on the other hand, you care a great deal. It's
 inverted from OO. Exceptions hold data but not much behavior. The behavior
 goes in the catch blocks.

 If we had a construct which allowed inheritance but not polymorphism (which we
 don't, since for better or wors, inheritance is conflated with polymorphism in
 D), then that would make a lot of sense for exceptions.
I don't think inheritance without subtyping would help any. C++ has it (non-public inheritance) but that didn't improve exceptions one bit.
 It doesn't really hurt us that exceptions have polymorphism, but it doesn't
 generally help them do their job at all. If you're going to do anything beyond
 simply print an error message, you need to know what the exception type is and
 what data it has. And that's not OO at all.
Or you need to implement a key-value interface that allows printing of an elaborate error message. Andrei
Feb 20 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 20:36:16 Andrei Alexandrescu wrote:
 On 2/20/12 7:07 PM, Jonathan M Davis wrote:
 (Cloning and comparison are also essential polymorphic methods to
 implement.) But then again the view "exceptions are wildebeests" is
 coming straight from inside the box. We want to improve on that.
Neither of which need to be done much with exceptions.
 I don't think inheritance without subtyping would help any. C++ has it
 (non-public inheritance) but that didn't improve exceptions one bit.
I don't think that it would necessarily improve them. My point was that polymorphism isn't of much use for exceptions. So whether they are polymorphic or not is generally irrelevant.
 It doesn't really hurt us that exceptions have polymorphism, but it
 doesn't
 generally help them do their job at all. If you're going to do anything
 beyond simply print an error message, you need to know what the exception
 type is and what data it has. And that's not OO at all.
Or you need to implement a key-value interface that allows printing of an elaborate error message.
You need to know the type regardless. And the information that an exception is going to have is very strongly tied to its type. I'd argue that in the general case, using a key-value interface is a huge step down from having the information be fields in the type that it goes with. It may help with printing elaborate error messages, and it would allow you to add information to an exception which wouldn't generally be there, but it helps little-to-none in actually handling exceptions and in fact makes it worse, because then you're moving what would be compilation errors to runtime. It may be that the hashmap may be worth adding, but I'd consider it bad practice to use it when putting the variables in the type itself would work. It's only when you need to add information which wouldn't normally be in the exception that it makes much sense. - Jonathan M Davis
Feb 20 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 01:38, Andrei Alexandrescu a crit :
 On 2/20/12 6:25 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote:
 Formatting should use class reflection. We already discussed that, and
 we already agreed that was the superior approach.
Jose's argument convinced me otherwise. I retract my agreement.
 When you're catching a specific exception, you're catching it with the
 view that it will contain precisely information X, Y, Z that you need to
 recover from the problem. If you don't need to catch something, then
 don't put the catch block there.
That's extremely rare in my experience, and only present in toy examples that contain a ton of "..." magic.
I think you experience here is biased by C++ .
Feb 21 2012
prev sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 15:12:08 Andrei Alexandrescu wrote:
 On 2/20/12 2:53 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies
 with tables! ...
 
 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.
Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly
 redundant exception classes:
[snip] Yah, it does look we need to better investigate the Variant[string] info() primitive.
In my experience, the type is very much tied to the context. When a particular type of error occurs, there's a particular set of information that goes with that, and that doesn't generally change. So, you don't end up with a bunch of types which are solely there to add additional information. Using variant means moving to dynamic typing and problems that you don't see until runtime, whereas having the data as direct member variables is statically checked. Having the ability to add extra information via Variant[string] may not be a problem, but doing that for everything _would_ be. It's far more bug prone, and the fact that it would be error handling code which would have the bugs would make them _far_ harder to catch and fix. I definitely think that we should favor putting data in member variables, not in a hashtable of Variants. - Jonathan M Davis
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 4:25 PM, Jonathan M Davis wrote:
 In my experience, the type is very much tied to the context.
I thought you said the type is tied to "what happened, not where it happened", which is the polar opposite of the above.
 When a particular
 type of error occurs, there's a particular set of information that goes with
 that, and that doesn't generally change. So, you don't end up with a bunch of
 types which are solely there to add additional information.
But on the way up there's additional contextual information. "Total amount must be a positive number" is more descriptive "Conversion error", although it originated as the latter. Insisting that all that must be encoded as types seems overly rigid, not to mention non-scalable.
 Using variant means moving to dynamic typing and problems that you don't see
 until runtime, whereas having the data as direct member variables is
 statically checked.
That's a given. However I think the most frequent use of exception interfaces is to extract data for formatting purposes, and the exception interface would do good to help with that.
 Having the ability to add extra information via Variant[string] may not be a
 problem, but doing that for everything _would_ be. It's far more bug prone,
 and the fact that it would be error handling code which would have the bugs
 would make them _far_ harder to catch and fix.

 I definitely think that we should favor putting data in member variables, not
 in a hashtable of Variants.
Whenever you put data in member variables you set yourself up for code bloat. Do you agree? Andrei
Feb 20 2012
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 17:22:13 Andrei Alexandrescu wrote:
 On 2/20/12 4:25 PM, Jonathan M Davis wrote:
 In my experience, the type is very much tied to the context.
I thought you said the type is tied to "what happened, not where it happened", which is the polar opposite of the above.
A particular exception type says that a particular thing happened, and that's almost always tied to a particular set of information. If you have a SocketException, then there's always an IP and port involved no matter what function it's thrown from and _that_ context doesn't change. Certain types of data almost always go with certain problems and therefore certain exception types. If you mean context in terms of where it's being thrown from, then no, that's not generally connected at all.
 Whenever you put data in member variables you set yourself up for code
 bloat. Do you agree?
Not really. You put them in there if you need them. And shifting them into an AA of Variants doesn't help any. It just reduces the size of the class itself. You still have to set them, and code that uses them still has to reference them. You also end up using more memory. And when it comes to exceptions, having a few more pieces of data that you don't always need but might be useful doesn't really cost you much, because you don't construct them often, and you're not going to have very many instantiated at once. They almost always have a very short lifespan. I'd be far more worried about restricting the number of member variables in types which are going to actually be commonly instantiated and potentially have many of them in memory. We obviously shouldn't go to town adding everything and the kitchen sink to every exception type, but having a few extra member variables on them doesn't seem like a big deal to me. - Jonathan M Davis
Feb 20 2012
prev sibling parent reply Piotr Szturmaj <bncrbme jadamspam.pl> writes:
Andrei Alexandrescu wrote:
 On 2/20/12 4:25 PM, Jonathan M Davis wrote:
 I definitely think that we should favor putting data in member
 variables, not
 in a hashtable of Variants.
+1
 Whenever you put data in member variables you set yourself up for code
 bloat. Do you agree?
It's a problem of classes as a whole, not just exceptions. I'm afraid such 'pressure' will limit the use of classes in Phobos.
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 7:09 PM, Piotr Szturmaj wrote:
 Andrei Alexandrescu wrote:
 On 2/20/12 4:25 PM, Jonathan M Davis wrote:
 I definitely think that we should favor putting data in member
 variables, not
 in a hashtable of Variants.
+1
 Whenever you put data in member variables you set yourself up for code
 bloat. Do you agree?
It's a problem of classes as a whole, not just exceptions. I'm afraid such 'pressure' will limit the use of classes in Phobos.
The problem is solved by pushing interface up, i.e. defining good methods in the base classes. Andrei
Feb 20 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 17:31:28 Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies
 with tables! ...
 
 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
Having derived exceptions with additional information is a _huge_ boon, and I contend that it's vasty better with variant, which would be highly error prone, because it's not properly statically checked. Changes to what's put in the variant could kill code at runtime - code which by its very definiton is not supposed to be the normal code path, so you're less likely to actually run into the problem before you ship your product. Whereas with the information in actual member variables, if they get changed, you get a compilation error, and you know that you have to fix your code. Rethrowing is a separate issue. And in many cases, the correct thing to do is to chain exceptions. You catch one, do something with it, and then you throw a new one which took the first one as an argument. Then you get both. That functionality is already built into Exception. - Jonathan M Davis
Feb 20 2012
next sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
I like one golden rule that I have:

 "You should only create a new exception type, if it makes sense to write
  a  catch(MyNewShinyType ex){}  "

other reasons for creating a new exception class I don't consider them valid
(but that's just me!).
This is because, exception types (in my head) are only a way to distinguish
whether to select them or let them go when I write a catch(), and to
help the catch recover.  Now, if I can't distinguish the *what* of the error,
I cannot recover well. The *cause* of the error goes inside the exception
object, not encoded in the type. Other details of the error go inside of
the exception object, not encoded in the type name.

So all I care about is the *what* of the error, so that it will fall in
the correct catch statement. Other criteria obscures that.

The Variant[string] helps keep the hierarchy clean. The hierachy should
tell the *what* of the error so that I can pick one when writing a catch block.

--jm


On 02/20/2012 05:51 PM, Jonathan M Davis wrote:
 On Monday, February 20, 2012 17:31:28 Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies
 with tables! ...

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
Having derived exceptions with additional information is a _huge_ boon, and I contend that it's vasty better with variant, which would be highly error prone, because it's not properly statically checked. Changes to what's put in the variant could kill code at runtime - code which by its very definiton is not supposed to be the normal code path, so you're less likely to actually run into the problem before you ship your product. Whereas with the information in actual member variables, if they get changed, you get a compilation error, and you know that you have to fix your code. Rethrowing is a separate issue. And in many cases, the correct thing to do is to chain exceptions. You catch one, do something with it, and then you throw a new one which took the first one as an argument. Then you get both. That functionality is already built into Exception. - Jonathan M Davis
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 3:06 PM, Juan Manuel Cabo wrote:
 I like one golden rule that I have:

   "You should only create a new exception type, if it makes sense to write
    a  catch(MyNewShinyType ex){}  "
The problem with this is that anyone may come with an example created on the spot where they would, actually, want to catch a specific exception. It's very hard to make non-existential proofs.
 other reasons for creating a new exception class I don't consider them valid
 (but that's just me!).
 This is because, exception types (in my head) are only a way to distinguish
 whether to select them or let them go when I write a catch(), and to
 help the catch recover.  Now, if I can't distinguish the *what* of the error,
 I cannot recover well. The *cause* of the error goes inside the exception
 object, not encoded in the type. Other details of the error go inside of
 the exception object, not encoded in the type name.

 So all I care about is the *what* of the error, so that it will fall in
 the correct catch statement. Other criteria obscures that.

 The Variant[string] helps keep the hierarchy clean. The hierachy should
 tell the *what* of the error so that I can pick one when writing a catch block.
Don't forget that you always have the option of creating a type on the spot. void fun() { if (oops) { class MyException : Exception { string reasonForOops; ... } throw new MyException; // RTTI will find reasonForOops } } But after seeing a variety of pros and cons, I tend to agree that the Variant[string] is more attractive. Andrei
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 2:51 PM, Jonathan M Davis wrote:
 On Monday, February 20, 2012 17:31:28 Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies
 with tables! ...

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
Having derived exceptions with additional information is a _huge_ boon, and I contend that it's vasty better with variant, which would be highly error prone, because it's not properly statically checked. Changes to what's put in the variant could kill code at runtime - code which by its very definiton is not supposed to be the normal code path, so you're less likely to actually run into the problem before you ship your product. Whereas with the information in actual member variables, if they get changed, you get a compilation error, and you know that you have to fix your code. Rethrowing is a separate issue. And in many cases, the correct thing to do is to chain exceptions. You catch one, do something with it, and then you throw a new one which took the first one as an argument. Then you get both. That functionality is already built into Exception.
I think we'd be quick to dismiss Juan's point. There are many execution contexts for any given exception, and summarily saying that we'll handle them with distinct types doesn't sound exactly scalable. I've long wanted to be able to plant context information in a scope, that was automatically added to any exception possibly leaving that scope. Andrei
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 2:31 PM, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception hierarchies with
tables!
 ...

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
Good point. One thing somewhat particular to D, you can always throw a new exception linked to the current one. Andrei
Feb 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 22:05, Andrei Alexandrescu a écrit :
 On 2/20/12 2:31 PM, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception
 hierarchies with tables!
 ...

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
Good point. One thing somewhat particular to D, you can always throw a new exception linked to the current one.
And this is great design. We should continue in that direction.
 Andrei
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 3:14 PM, deadalnix wrote:
 Le 20/02/2012 22:05, Andrei Alexandrescu a écrit :
 On 2/20/12 2:31 PM, Juan Manuel Cabo wrote:
 ...
 Sure. Again, this is not advocating replacement of exception
 hierarchies with tables!
 ...

 Andrei
I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies. The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
Good point. One thing somewhat particular to D, you can always throw a new exception linked to the current one.
And this is great design.
I'm not so sure. It's more like an incompetent design by someone who obviously knew nothing about exceptions.
 We should continue in that direction.
That has its own disadvantages, particularly the proliferation of types simply to embody state, but it does work. Andrei
Feb 20 2012
prev sibling parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
On 02/20/2012 03:23 PM, Jonathan M Davis wrote:
 I don't see how you could possibly make that uniform. It's very non-uniform by 
 its very nature. The handling _needs_ to be non-uniform.
 
The handling might need to be non-uniform, but the exception hierarchy doesn't. Not talking about i18n formatting now. Sometimes I'd like to add a 'trait' to an exception, but find myself needing to create a new exception type just for that, which will sit oddly in the hierarchy. Consider the case of rethrowing an exception with added detail. --jm
Feb 20 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
 On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
[...]
 Exactly. I don't see how a disagreement follows from here. So isn't
 it reasonable to design the exception such that it can offer
 information pertaining to what went wrong, in a uniform manner?
[...]
 I don't see how you could possibly make that uniform. It's very
 non-uniform by its very nature. The handling _needs_ to be
 non-uniform.
I think there's a misunderstanding here. What Andrei is trying to achieve is something like this: class Exception : Error { Variant[string] data; string formatMsg(LocaleInfo li) { return formatLocale(li, msg, data); } } class GetOptException : Exception { this() { data["..."] = ...; ... } } I contend that using Variant here is not necessary. You can easily do this instead: class Exception : Error { string formatMsg(LocaleInfo li) { auto members = typeid(this).getMembers(null); string msg; foreach (member; members) { if (member.name.startsWith("info")) { ... //format this field } } return msg; } } class GetOptException : Exception { string infoOption; // this gets picked up by formatMsg int internalData; // this is ignored // No need to declare anything else except ctor to set // the above fields. } This allows for a much cleaner, type-checked access to infoOption, should the catching code know how to deal with GetOptException specifically. T -- The day Microsoft makes something that doesn't suck is probably the day they start making vacuum cleaners... -- Slashdotter
Feb 20 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 19:42, H. S. Teoh a crit :
 On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
 On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
[...]
 Exactly. I don't see how a disagreement follows from here. So isn't
 it reasonable to design the exception such that it can offer
 information pertaining to what went wrong, in a uniform manner?
[...]
 I don't see how you could possibly make that uniform. It's very
 non-uniform by its very nature. The handling _needs_ to be
 non-uniform.
I think there's a misunderstanding here. What Andrei is trying to achieve is something like this: class Exception : Error { Variant[string] data; string formatMsg(LocaleInfo li) { return formatLocale(li, msg, data); } } class GetOptException : Exception { this() { data["..."] = ...; ... } } I contend that using Variant here is not necessary. You can easily do this instead: class Exception : Error { string formatMsg(LocaleInfo li) { auto members = typeid(this).getMembers(null); string msg; foreach (member; members) { if (member.name.startsWith("info")) { ... //format this field } } return msg; } } class GetOptException : Exception { string infoOption; // this gets picked up by formatMsg int internalData; // this is ignored // No need to declare anything else except ctor to set // the above fields. } This allows for a much cleaner, type-checked access to infoOption, should the catching code know how to deal with GetOptException specifically. T
This is bad design IMO. Exception are here to provide information about what is wrong. It has nothing to do with internationalisation whatsoever.
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 12:50 PM, deadalnix wrote:
 This is bad design IMO. Exception are here to provide information about
 what is wrong. It has nothing to do with internationalisation whatsoever.
100% agree. Andrei
Feb 20 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-20 19:47, Andrei Alexandrescu wrote:
 On 2/20/12 12:50 PM, deadalnix wrote:
 This is bad design IMO. Exception are here to provide information about
 what is wrong. It has nothing to do with internationalisation whatsoever.
100% agree. Andrei
It sure seems you want to but internationalization into exceptions. -- /Jacob Carlborg
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 2:45 PM, Jacob Carlborg wrote:
 On 2012-02-20 19:47, Andrei Alexandrescu wrote:
 On 2/20/12 12:50 PM, deadalnix wrote:
 This is bad design IMO. Exception are here to provide information about
 what is wrong. It has nothing to do with internationalisation
 whatsoever.
100% agree. Andrei
It sure seems you want to but internationalization into exceptions.
How did you infer that? I don't want to put internationalization into exceptions /at all/. Andrei
Feb 20 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhucla$1tuf$7 digitalmars.com...
 On 2/20/12 2:45 PM, Jacob Carlborg wrote:
 On 2012-02-20 19:47, Andrei Alexandrescu wrote:
 On 2/20/12 12:50 PM, deadalnix wrote:
 This is bad design IMO. Exception are here to provide information about
 what is wrong. It has nothing to do with internationalisation
 whatsoever.
100% agree. Andrei
It sure seems you want to but internationalization into exceptions.
How did you infer that? I don't want to put internationalization into exceptions /at all/.
You've suggested adding "Variant[string] info" to Exception for the sake of i18n. I think that's what he's referring to. You *could* argue that's not technically i18n, but so far i18n seems to be the only real use-case for it (although even that much has been disputed in light of reflection).
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 5:46 PM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhucla$1tuf$7 digitalmars.com...
 On 2/20/12 2:45 PM, Jacob Carlborg wrote:
 On 2012-02-20 19:47, Andrei Alexandrescu wrote:
 On 2/20/12 12:50 PM, deadalnix wrote:
 This is bad design IMO. Exception are here to provide information about
 what is wrong. It has nothing to do with internationalisation
 whatsoever.
100% agree. Andrei
It sure seems you want to but internationalization into exceptions.
How did you infer that? I don't want to put internationalization into exceptions /at all/.
You've suggested adding "Variant[string] info" to Exception for the sake of i18n. I think that's what he's referring to. You *could* argue that's not technically i18n, but so far i18n seems to be the only real use-case for it (although even that much has been disputed in light of reflection).
All formatting and rendering of the exception information is helped. And I think that's great because that's the most common activity one would do with exceptions. Andrei
Feb 20 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 06:19:32PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 5:46 PM, Nick Sabalausky wrote:
[...]
You've suggested adding "Variant[string] info" to Exception for the
sake of i18n. I think that's what he's referring to. You *could*
argue that's not technically i18n, but so far i18n seems to be the
only real use-case for it (although even that much has been disputed
in light of reflection).
All formatting and rendering of the exception information is helped. And I think that's great because that's the most common activity one would do with exceptions.
[...] Then, obviously, different development environments are leading to vastly different conclusions, because I can't for the life of me imagine that the most common activity you would do with an exception is to print it. That may be true for a certain class of applications, say command-line tools like compilers and stuff. But that's absolutely not the case at my work project. There, exceptions are usually handled by code and almost never output anywhere, and even when they are, for the most part they just print a simple string literal right from the source code into a debug log channel. Only a tiny percentage of exceptions actually make it all the way to being formatted into a nice localized string displayed to the user. For the more common case of printing to the debug log, the current Exception facilities (string msg) are more than good enough (since the intended audience is the developers themselves). The i18n stuff is handled by catching the relevant exception by type and looking up the localized string at the catcher level. Any additional info inside the exception object itself isn't used for formatting the localized string anyway, since the user has no idea what it all means. T -- The day Microsoft makes something that doesn't suck is probably the day they start making vacuum cleaners... -- Slashdotter
Feb 20 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 6:51 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 06:19:32PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 5:46 PM, Nick Sabalausky wrote:
[...]
 You've suggested adding "Variant[string] info" to Exception for the
 sake of i18n. I think that's what he's referring to. You *could*
 argue that's not technically i18n, but so far i18n seems to be the
 only real use-case for it (although even that much has been disputed
 in light of reflection).
All formatting and rendering of the exception information is helped. And I think that's great because that's the most common activity one would do with exceptions.
[...] Then, obviously, different development environments are leading to vastly different conclusions, because I can't for the life of me imagine that the most common activity you would do with an exception is to print it.
Different strokes for different folks I'd say. I think I could claim a little authority on diversity. I've worked on an extremely diverse range of applications; in fact I've made a point to acquire broad specialization. In virtually all systems I've worked on, rendering meaningful error messages out of exception information has been a major concern, and in most of them the problem has been poorly addressed. It is very exciting to have an opportunity to improve on the state of affairs. Andrei
Feb 20 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-21 03:34, Andrei Alexandrescu wrote:
 On 2/20/12 6:51 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 06:19:32PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 5:46 PM, Nick Sabalausky wrote:
[...]
 You've suggested adding "Variant[string] info" to Exception for the
 sake of i18n. I think that's what he's referring to. You *could*
 argue that's not technically i18n, but so far i18n seems to be the
 only real use-case for it (although even that much has been disputed
 in light of reflection).
All formatting and rendering of the exception information is helped. And I think that's great because that's the most common activity one would do with exceptions.
[...] Then, obviously, different development environments are leading to vastly different conclusions, because I can't for the life of me imagine that the most common activity you would do with an exception is to print it.
Different strokes for different folks I'd say. I think I could claim a little authority on diversity. I've worked on an extremely diverse range of applications; in fact I've made a point to acquire broad specialization. In virtually all systems I've worked on, rendering meaningful error messages out of exception information has been a major concern, and in most of them the problem has been poorly addressed. It is very exciting to have an opportunity to improve on the state of affairs. Andrei
I think the correct way of handling this is provide enough information in the exception so a message can be built where the exception is caught. It might happen the you want to catch the same exception in different parts of the code and build different messages. -- /Jacob Carlborg
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 5:11 AM, Jacob Carlborg wrote:
 On 2012-02-21 03:34, Andrei Alexandrescu wrote:
 I think the correct way of handling this is provide enough information
 in the exception so a message can be built where the exception is
 caught.
Quite so. I'd add "using a unified interface so reusable code can work with heterogeneous concrete exceptions".
 It might happen the you want to catch the same exception in
 different parts of the code and build different messages.
Yah, that's different string tables. Andrei
Feb 21 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-21 15:16, Andrei Alexandrescu wrote:
 On 2/21/12 5:11 AM, Jacob Carlborg wrote:
 On 2012-02-21 03:34, Andrei Alexandrescu wrote:
 I think the correct way of handling this is provide enough information
 in the exception so a message can be built where the exception is
 caught.
Quite so. I'd add "using a unified interface so reusable code can work with heterogeneous concrete exceptions".
 It might happen the you want to catch the same exception in
 different parts of the code and build different messages.
Yah, that's different string tables.
So you would set the string table where the exception is caught? -- /Jacob Carlborg
Feb 21 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-02-21 01:51, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 06:19:32PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 5:46 PM, Nick Sabalausky wrote:
[...]
 You've suggested adding "Variant[string] info" to Exception for the
 sake of i18n. I think that's what he's referring to. You *could*
 argue that's not technically i18n, but so far i18n seems to be the
 only real use-case for it (although even that much has been disputed
 in light of reflection).
All formatting and rendering of the exception information is helped. And I think that's great because that's the most common activity one would do with exceptions.
[...] Then, obviously, different development environments are leading to vastly different conclusions, because I can't for the life of me imagine that the most common activity you would do with an exception is to print it. That may be true for a certain class of applications, say command-line tools like compilers and stuff. But that's absolutely not the case at my work project. There, exceptions are usually handled by code and almost never output anywhere, and even when they are, for the most part they just print a simple string literal right from the source code into a debug log channel. Only a tiny percentage of exceptions actually make it all the way to being formatted into a nice localized string displayed to the user. For the more common case of printing to the debug log, the current Exception facilities (string msg) are more than good enough (since the intended audience is the developers themselves). The i18n stuff is handled by catching the relevant exception by type and looking up the localized string at the catcher level. Any additional info inside the exception object itself isn't used for formatting the localized string anyway, since the user has no idea what it all means. T
I completely agree. -- /Jacob Carlborg
Feb 21 2012
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 12:42 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
 On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
[...]
 Exactly. I don't see how a disagreement follows from here. So isn't
 it reasonable to design the exception such that it can offer
 information pertaining to what went wrong, in a uniform manner?
[...]
 I don't see how you could possibly make that uniform. It's very
 non-uniform by its very nature. The handling _needs_ to be
 non-uniform.
I think there's a misunderstanding here. What Andrei is trying to achieve is something like this: class Exception : Error { Variant[string] data; string formatMsg(LocaleInfo li) { return formatLocale(li, msg, data); } } class GetOptException : Exception { this() { data["..."] = ...; ... } } I contend that using Variant here is not necessary. You can easily do this instead: class Exception : Error { string formatMsg(LocaleInfo li) { auto members = typeid(this).getMembers(null); string msg; foreach (member; members) { if (member.name.startsWith("info")) { ... //format this field } } return msg; } } class GetOptException : Exception { string infoOption; // this gets picked up by formatMsg int internalData; // this is ignored // No need to declare anything else except ctor to set // the above fields. } This allows for a much cleaner, type-checked access to infoOption, should the catching code know how to deal with GetOptException specifically.
But this moves i18n code straight inside the exception, which Jonathan argues against. Separated concerns call for separated modules. Sorry, I don't know what to write beyond the one line above! Andrei
Feb 20 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-02-20 19:42, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
 On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
[...]
 Exactly. I don't see how a disagreement follows from here. So isn't
 it reasonable to design the exception such that it can offer
 information pertaining to what went wrong, in a uniform manner?
[...]
 I don't see how you could possibly make that uniform. It's very
 non-uniform by its very nature. The handling _needs_ to be
 non-uniform.
I think there's a misunderstanding here. What Andrei is trying to achieve is something like this: class Exception : Error { Variant[string] data; string formatMsg(LocaleInfo li) { return formatLocale(li, msg, data); } } class GetOptException : Exception { this() { data["..."] = ...; ... } } I contend that using Variant here is not necessary. You can easily do this instead: class Exception : Error { string formatMsg(LocaleInfo li) { auto members = typeid(this).getMembers(null); string msg; foreach (member; members) { if (member.name.startsWith("info")) { ... //format this field } } return msg; } } class GetOptException : Exception { string infoOption; // this gets picked up by formatMsg int internalData; // this is ignored // No need to declare anything else except ctor to set // the above fields. } This allows for a much cleaner, type-checked access to infoOption, should the catching code know how to deal with GetOptException specifically. T
Currently getMembers doesn't work and if I recall correctly it's not possible to get the value out of what getMembers returns. But that might just be considered implementation issues. -- /Jacob Carlborg
Feb 20 2012
prev sibling next sibling parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
On 02/20/2012 02:57 PM, Andrei Alexandrescu wrote:
 On 2/20/12 11:44 AM, foobar wrote:
 This extra processing is orthogonal to the exception. the same exception
 can be logged to a file, processed (per above example) and generate
 graphical notification to the user, etc. The exception contains the
 information pertaining only to what went wrong. the rest is not part of
 this discussion.
Exactly. I don't see how a disagreement follows from here. So isn't it reasonable to design the exception such that it can offer information pertaining to what went wrong, in a uniform manner?
 The exact same exception in the example would also be thrown on a
 mistyped URL in an application that tries to scrape some info from a
 website for further processing. The error is still the same - the url is
 incorrect but different use cases handle it differently. In the former
 example I might call to a i18n lib (someone already mentioned gettext)
 while in the latter I'll call a logging library with the the mistyped
 url (for statistics' sake).
 in the first I use the url to find a closest match, in the second I want
 to log said requested url. Both handled by *other* mechanisms.
 in both cases the exception needs a url field and in both cases I have
 no need for the Variant[string] map.
The Variant[string] map saves a lot of duplication whenever you want to format a human-readable string (which is a common activity with exceptions). It transforms this (I'm too lazy to write code anew by hand, so I'll paste Jonathan's): try getopt(args, ...) catch(MissingArgumentException mae) { stderr.writefln("%s is missing an argument", mae.flag); return -1; } catch(InvalidArgumentException iae) { stderr.writelfln("%s is not a valid argument for %s. You must give it a %s.", mae.arg, mae.flag, mae.expectedType); return -1; } catch(UnknownFlagException ufe) { stderr.writefln("%s is not a known flag.", ufe.ufe); return -1; } catch(GetOptException goe) { stderr.writefln("There was an error with %s", goe.flag); return -1; } //A delegate that you passed to getopt threw an exception. catch(YourException ye) { //... } catch(Exception e) { stderr.writeln("An unexpected error occured."); return -1; } into this: try getopt(args, ...) catch(Exception e) { stderr.writeln(stringTemplate(typeid(e).toString(), e.info)); return -1; } The stringTemplate function loads the formatting template from a table indexed on typeid(e).toString() and formats it with the info. It's simple factorization. Andrei
Feb 20 2012
prev sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
I like the idea!

Remember please for anyone reading: Use positional arguments in
format strings. Otherwise:

	"The '%s' file's size is %d which is wrong"

        translated to

	"El tamaño %d es incorrecto para el archivo %s"

will be trouble. Instead please do:

        "The '%1$s' file's size is %2$d which is wrong"

specially for standard library messages. This would be very helpful!

--jm



 into this:

 try
     getopt(args, ...)
 catch(Exception e)
 {
     stderr.writeln(stringTemplate(typeid(e).toString(), e.info));
     return -1;
 }

 The stringTemplate function loads the formatting template from a table indexed
on typeid(e).toString() and formats it
 with the info. It's simple factorization.


 Andrei
Feb 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Why don't we use .tupleof for such a thing ?

Le 20/02/2012 20:17, Juan Manuel Cabo a écrit :
 I like the idea!

 Remember please for anyone reading: Use positional arguments in
 format strings. Otherwise:

 	"The '%s' file's size is %d which is wrong"

          translated to

 	"El tamaño %d es incorrecto para el archivo %s"

 will be trouble. Instead please do:

          "The '%1$s' file's size is %2$d which is wrong"

 specially for standard library messages. This would be very helpful!

 --jm



 into this:

 try
      getopt(args, ...)
 catch(Exception e)
 {
      stderr.writeln(stringTemplate(typeid(e).toString(), e.info));
      return -1;
 }

 The stringTemplate function loads the formatting template from a table indexed
on typeid(e).toString() and formats it
 with the info. It's simple factorization.


 Andrei
Feb 20 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 2/20/12, Juan Manuel Cabo <juanmanuel.cabo gmail.com> wrote:
 will be trouble. Instead please do:

         "The '%1$s' file's size is %2$d which is wrong"
That is the shittiest formatting specifier ever invented. The unreadability of it is why I never, ever, use it. Python solved this nicely with its {0} {1} syntax:
 print '{0} and {1}'.format('foo', 'bar')
Feb 20 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 08:36:56PM +0100, Andrej Mitrovic wrote:
 On 2/20/12, Juan Manuel Cabo <juanmanuel.cabo gmail.com> wrote:
 will be trouble. Instead please do:

         "The '%1$s' file's size is %2$d which is wrong"
That is the shittiest formatting specifier ever invented. The unreadability of it is why I never, ever, use it. Python solved this nicely with its {0} {1} syntax:
 print '{0} and {1}'.format('foo', 'bar')
Actually, even that isn't ideal. How is the translator to know what on earth {0} and {1} are? Sometimes you need to know in order to make a good translation. This would be even better: "The ${file}'s size is ${size}, which is wrong" The usefulness of named arguments is even more apparent in complex message like this one: "${file}:${line}: Expecting ${expectedtoken}, got ${inputtoken}" Without named parameters, you'd have: "{0}:{1}: expecting {2}, got {3}" which is almost impossible to translate. What are {0} and {1}? What are {2} and {3}? Does it mean "12:30pm: expecting program to succeed, got general protection fault"? Using named parameters makes it clear this is a parser error, not something else. This difference may mean using a completely different grammatical structure to translate the message. T -- Never ascribe to malice that which is adequately explained by incompetence. -- Napoleon Bonaparte
Feb 20 2012
next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote in message 
news:mailman.704.1329767254.20196.digitalmars-d puremagic.com...
 On Mon, Feb 20, 2012 at 08:36:56PM +0100, Andrej Mitrovic wrote:
 On 2/20/12, Juan Manuel Cabo <juanmanuel.cabo gmail.com> wrote:
 will be trouble. Instead please do:

         "The '%1$s' file's size is %2$d which is wrong"
That is the shittiest formatting specifier ever invented. The unreadability of it is why I never, ever, use it. Python solved this nicely with its {0} {1} syntax:
 print '{0} and {1}'.format('foo', 'bar')
Actually, even that isn't ideal. How is the translator to know what on earth {0} and {1} are? Sometimes you need to know in order to make a good translation. This would be even better: "The ${file}'s size is ${size}, which is wrong" The usefulness of named arguments is even more apparent in complex message like this one: "${file}:${line}: Expecting ${expectedtoken}, got ${inputtoken}" Without named parameters, you'd have: "{0}:{1}: expecting {2}, got {3}" which is almost impossible to translate. What are {0} and {1}? What are {2} and {3}? Does it mean "12:30pm: expecting program to succeed, got general protection fault"? Using named parameters makes it clear this is a parser error, not something else. This difference may mean using a completely different grammatical structure to translate the message.
vote++; I've been drooling over the idea of named argument format strings in D for a long while. I also agree with posix-style specifiers being barely readable. I fell in adopted it. Then I moved to Phobos2, and have been missing those wonderful curly braces ever since.
Feb 20 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-20 21:25, Nick Sabalausky wrote:
 "H. S. Teoh"<hsteoh quickfur.ath.cx>  wrote in message
 news:mailman.704.1329767254.20196.digitalmars-d puremagic.com...
 On Mon, Feb 20, 2012 at 08:36:56PM +0100, Andrej Mitrovic wrote:
 On 2/20/12, Juan Manuel Cabo<juanmanuel.cabo gmail.com>  wrote:
 will be trouble. Instead please do:

          "The '%1$s' file's size is %2$d which is wrong"
That is the shittiest formatting specifier ever invented. The unreadability of it is why I never, ever, use it. Python solved this nicely with its {0} {1} syntax:
 print '{0} and {1}'.format('foo', 'bar')
Actually, even that isn't ideal. How is the translator to know what on earth {0} and {1} are? Sometimes you need to know in order to make a good translation. This would be even better: "The ${file}'s size is ${size}, which is wrong" The usefulness of named arguments is even more apparent in complex message like this one: "${file}:${line}: Expecting ${expectedtoken}, got ${inputtoken}" Without named parameters, you'd have: "{0}:{1}: expecting {2}, got {3}" which is almost impossible to translate. What are {0} and {1}? What are {2} and {3}? Does it mean "12:30pm: expecting program to succeed, got general protection fault"? Using named parameters makes it clear this is a parser error, not something else. This difference may mean using a completely different grammatical structure to translate the message.
vote++; I've been drooling over the idea of named argument format strings in D for a long while. I also agree with posix-style specifiers being barely readable. I fell in adopted it. Then I moved to Phobos2, and have been missing those wonderful curly braces ever since.
Tango doesn't even require you put a number in the parameter, just "Hello {}". It is still possible to put a number if you want, something like this "Hello {2} {1} {1}". -- /Jacob Carlborg
Feb 21 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-02-20 20:49, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 08:36:56PM +0100, Andrej Mitrovic wrote:
 On 2/20/12, Juan Manuel Cabo<juanmanuel.cabo gmail.com>  wrote:
 will be trouble. Instead please do:

          "The '%1$s' file's size is %2$d which is wrong"
That is the shittiest formatting specifier ever invented. The unreadability of it is why I never, ever, use it. Python solved this nicely with its {0} {1} syntax:
 print '{0} and {1}'.format('foo', 'bar')
Actually, even that isn't ideal. How is the translator to know what on earth {0} and {1} are? Sometimes you need to know in order to make a good translation. This would be even better: "The ${file}'s size is ${size}, which is wrong" The usefulness of named arguments is even more apparent in complex message like this one: "${file}:${line}: Expecting ${expectedtoken}, got ${inputtoken}" Without named parameters, you'd have: "{0}:{1}: expecting {2}, got {3}" which is almost impossible to translate. What are {0} and {1}? What are {2} and {3}? Does it mean "12:30pm: expecting program to succeed, got general protection fault"? Using named parameters makes it clear this is a parser error, not something else. This difference may mean using a completely different grammatical structure to translate the message. T
Ruby on Rails uses name parameters: http://guides.rubyonrails.org/i18n.html -- /Jacob Carlborg
Feb 20 2012
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:17 PM, Juan Manuel Cabo wrote:
 I like the idea!

 Remember please for anyone reading: Use positional arguments in
 format strings.
Not positional, but instead Symbolic is the way. Andrei
Feb 20 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 02:46:52PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 1:17 PM, Juan Manuel Cabo wrote:
I like the idea!

Remember please for anyone reading: Use positional arguments in
format strings.
Not positional, but instead Symbolic is the way.
[...] +1. Nothing worse than a format string like this: "{0}:{1} {2} occurred in {3} {4}: {5}" Much better is: "${srcfile}:${linenum} ${errortype} occurred in ${objecttype} ${objectname}: ${detailmsg}" T -- It is the quality rather than the quantity that matters. -- Lucius Annaeus Seneca
Feb 20 2012
prev sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 2/20/12, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:
 Actually, even that isn't ideal. How is the translator to know what on
 earth {0} and {1} are?
Sorry, I didn't mean this in the concept of exceptions but generally when calling writefln & format. This exceptions thread has exploded so fast I haven't kept up with the discussions. :P
Feb 21 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 18:11, Andrei Alexandrescu a écrit :
 On 2/20/12 11:05 AM, foobar wrote:
 On Monday, 20 February 2012 at 16:37:28 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org> wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string]
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception.
At the end of the day, a human-readable error message must be properly formatted given some exception that signaled an error. So I disagree that exceptions are meant for the developer. They are mechanism, a means to an end.
 Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in her
 browser, she does not know or care what 404 means. instead she gets a
 formated page suggesting her to check her spelling and probably a
 suggestion to try google.com instead.
Sure, and the question is how the message gets created.
 the exception notifies the developer of the error, the developer does
 extra processing (e.g. to suggest similar valid websites) and the user
 get a friendly notification. clearly it doesn't make sense to put all
 this into the exception.
That extra processing must format the message given the information passed by the exception. _Definitely_ it doesn't make sense to put the formatting processing in the exception. That's why shipping the information outside the exception in a generic format is necessary, hence the Variant[string]. Andrei
And so variant is the way to go ? Clearly, this is a very strong arguement in favor of typed Exception, that provide usefull information about what went wrong. This is a safe approach. Because this Variant stuff is going to require massive ducktyping of Exceptions, with all possible errors involved. The keys in the Variant[string] will depend on the Exception the dev is facing. This should be avoided and should warn us about the requirement of typed Exceptions.
Feb 20 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:55 AM, deadalnix wrote:
 Le 20/02/2012 18:11, Andrei Alexandrescu a écrit :
 On 2/20/12 11:05 AM, foobar wrote:
 On Monday, 20 February 2012 at 16:37:28 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org> wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string]
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception.
At the end of the day, a human-readable error message must be properly formatted given some exception that signaled an error. So I disagree that exceptions are meant for the developer. They are mechanism, a means to an end.
 Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in her
 browser, she does not know or care what 404 means. instead she gets a
 formated page suggesting her to check her spelling and probably a
 suggestion to try google.com instead.
Sure, and the question is how the message gets created.
 the exception notifies the developer of the error, the developer does
 extra processing (e.g. to suggest similar valid websites) and the user
 get a friendly notification. clearly it doesn't make sense to put all
 this into the exception.
That extra processing must format the message given the information passed by the exception. _Definitely_ it doesn't make sense to put the formatting processing in the exception. That's why shipping the information outside the exception in a generic format is necessary, hence the Variant[string]. Andrei
And so variant is the way to go ? Clearly, this is a very strong arguement in favor of typed Exception, that provide usefull information about what went wrong. This is a safe approach.
This does not pit typed exceptions against uniform interfaces. The hash simply provides information in a uniform format across exception types. That way, code that needs to extract and format information in a generic manner can be written in one place.
 Because this Variant stuff is going to require massive ducktyping of
 Exceptions, with all possible errors involved. The keys in the
 Variant[string] will depend on the Exception the dev is facing. This
 should be avoided and should warn us about the requirement of typed
 Exceptions.
I agree. Again, this is not one against the other. Andrei
Feb 20 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 06:55:13PM +0100, deadalnix wrote:
 Le 20/02/2012 18:11, Andrei Alexandrescu a crit :
[...]
That extra processing must format the message given the information
passed by the exception. _Definitely_ it doesn't make sense to put
the formatting processing in the exception. That's why shipping the
information outside the exception in a generic format is necessary,
hence the Variant[string].


Andrei
And so variant is the way to go ? Clearly, this is a very strong arguement in favor of typed Exception, that provide usefull information about what went wrong. This is a safe approach. Because this Variant stuff is going to require massive ducktyping of Exceptions, with all possible errors involved. The keys in the Variant[string] will depend on the Exception the dev is facing. This should be avoided and should warn us about the requirement of typed Exceptions.
Exactly! Just because you use a Variant doesn't magically free you from needing to know what exactly is that exception that you caught in the first place. To make any sense of what's stored in the Variant, you still have to know what is the exception type, and based on the type interpret the Variant. So you end up with: catch(Exception e) { switch(e.type) { case BlahException: handleBlahException(e.data.blah_info); break; case BlehException: handleBlehException(e.data.bleh_info); break; case BluhException: handleBluhException(e.data.bluh_info); break; ... } } which is exactly the kind of ugliness that class hierarchies were invented to solve. T -- ASCII stupid question, getty stupid ANSI.
Feb 20 2012
next sibling parent dennis luehring <dl.soluz gmx.net> writes:
Am 20.02.2012 19:02, schrieb H. S. Teoh:
 Exactly! Just because you use a Variant doesn't magically free you from
 needing to know what exactly is that exception that you caught in the
 first place. To make any sense of what's stored in the Variant, you
 still have to know what is the exception type, and based on the type
 interpret the Variant. So you end up with:

 	catch(Exception e) {
 		switch(e.type) {
 			case BlahException:
 				handleBlahException(e.data.blah_info);
 				break;
 			case BlehException:
 				handleBlehException(e.data.bleh_info);
 				break;
 			case BluhException:
 				handleBluhException(e.data.bluh_info);
 				break;
 			...
 		}
 	}
Thx Teoh - i also don't know how Andreis Variant[string] would help here in any way
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 12:02 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 06:55:13PM +0100, deadalnix wrote:
 Le 20/02/2012 18:11, Andrei Alexandrescu a crit :
[...]
 That extra processing must format the message given the information
 passed by the exception. _Definitely_ it doesn't make sense to put
 the formatting processing in the exception. That's why shipping the
 information outside the exception in a generic format is necessary,
 hence the Variant[string].


 Andrei
And so variant is the way to go ? Clearly, this is a very strong arguement in favor of typed Exception, that provide usefull information about what went wrong. This is a safe approach. Because this Variant stuff is going to require massive ducktyping of Exceptions, with all possible errors involved. The keys in the Variant[string] will depend on the Exception the dev is facing. This should be avoided and should warn us about the requirement of typed Exceptions.
Exactly! Just because you use a Variant doesn't magically free you from needing to know what exactly is that exception that you caught in the first place.
I think this is a confusion. You seem to advocate that exceptions are somehow exempt from class hierarchy design.
 To make any sense of what's stored in the Variant, you
 still have to know what is the exception type, and based on the type
 interpret the Variant. So you end up with:

 	catch(Exception e) {
 		switch(e.type) {
 			case BlahException:
 				handleBlahException(e.data.blah_info);
 				break;
 			case BlehException:
 				handleBlehException(e.data.bleh_info);
 				break;
 			case BluhException:
 				handleBluhException(e.data.bluh_info);
 				break;
 			...
 		}
 	}

 which is exactly the kind of ugliness that class hierarchies were
 invented to solve.
No, you don't end up with that at all. In fact it's your code that "knows exactly" the concrete type that suffers from duplication. I'm sorry, I think this is a complete confusion. Hopefully my post of a minute ago will help it. Andrei
Feb 20 2012
prev sibling next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
On Feb 20, 2012, at 9:55 AM, deadalnix wrote:
=20
 And so variant is the way to go ?
=20
 Clearly, this is a very strong arguement in favor of typed Exception, =
that provide usefull information about what went wrong. This is a safe = approach.
=20
 Because this Variant stuff is going to require massive ducktyping of =
Exceptions, with all possible errors involved. The keys in the = Variant[string] will depend on the Exception the dev is facing. This = should be avoided and should warn us about the requirement of typed = Exceptions. I think its debatable whether a Variant[string] or string[string] is = ideal here, but either way I think the point of the table is for = localized error messages. I wouldn't expect any data relevant for = filtering the exception within the table. =20
Feb 20 2012
next sibling parent Johannes Pfau <nospam example.com> writes:
Am Mon, 20 Feb 2012 11:13:29 -0800
schrieb Sean Kelly <sean invisibleduck.org>:

 On Feb 20, 2012, at 9:55 AM, deadalnix wrote:
 
 And so variant is the way to go ?
 
 Clearly, this is a very strong arguement in favor of typed
 Exception, that provide usefull information about what went wrong.
 This is a safe approach.
 
 Because this Variant stuff is going to require massive ducktyping
 of Exceptions, with all possible errors involved. The keys in the
 Variant[string] will depend on the Exception the dev is facing.
 This should be avoided and should warn us about the requirement of
 typed Exceptions.
I think its debatable whether a Variant[string] or string[string] is ideal here, but either way I think the point of the table is for localized error messages. I wouldn't expect any data relevant for filtering the exception within the table.
I think this whole localization discussion is useless as long as we have no std.localization module. For example this Variant[string] / string[string] stuff could be part of a TranslatedMessage struct, which could be used like this auto msg = TranslatedMessage("Couldn't connect to IP %s", ip); TranslatedMessage could then automatically convert ip to a string and store it in a Variant[string] / string[string] internally. Then when msg.toString() is called, TranslatedMessage would translate the format string and use the entries in the Variant/string table to construct the message. Exception could then simply store a TranslatedMessage and this could be useful for other code, not only for exceptions. I don't see why we should invent some special exception only localization now.
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:13 PM, Sean Kelly wrote:
 On Feb 20, 2012, at 9:55 AM, deadalnix wrote:
 And so variant is the way to go ?

 Clearly, this is a very strong arguement in favor of typed
 Exception, that provide usefull information about what went wrong.
 This is a safe approach.

 Because this Variant stuff is going to require massive ducktyping
 of Exceptions, with all possible errors involved. The keys in the
 Variant[string] will depend on the Exception the dev is facing.
 This should be avoided and should warn us about the requirement of
 typed Exceptions.
I think its debatable whether a Variant[string] or string[string] is ideal here, but either way I think the point of the table is for localized error messages. I wouldn't expect any data relevant for filtering the exception within the table.
Exactly. The need for Variant instead of string is mainly to distinguish numeric info from string info when doing things like singular vs. plural or Arabic numeral rendering vs. textual ("one", "two" etc). Andrei
Feb 20 2012
parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhub5b$1rj5$2 digitalmars.com...
 On 2/20/12 1:13 PM, Sean Kelly wrote:
 but either way I think the point of the table is for
 localized error messages.  I wouldn't expect any data relevant for
 filtering the exception within the table.
Exactly. The need for Variant instead of string is mainly to distinguish numeric info from string info when doing things like singular vs. plural or Arabic numeral rendering vs. textual ("one", "two" etc).
So like Jacob mentioned and you denied, you *are* advocating putting at least some i18n into exceptions.
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 5:51 PM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhub5b$1rj5$2 digitalmars.com...
 On 2/20/12 1:13 PM, Sean Kelly wrote:
 but either way I think the point of the table is for
 localized error messages.  I wouldn't expect any data relevant for
 filtering the exception within the table.
Exactly. The need for Variant instead of string is mainly to distinguish numeric info from string info when doing things like singular vs. plural or Arabic numeral rendering vs. textual ("one", "two" etc).
So like Jacob mentioned and you denied, you *are* advocating putting at least some i18n into exceptions.
No. I am simply enabling a variety of applications, with i18n as an obvious one. Encoding numbers as strings may force decoding later. It's just unnecessary. Andrei
Feb 20 2012
parent Jose Armando Garcia <jsancio gmail.com> writes:
On Mon, Feb 20, 2012 at 9:33 PM, Nick Sabalausky <a a.a> wrote:
 "Jose Armando Garcia" <jsancio gmail.com> wrote in message
 news:mailman.672.1329758926.20196.digitalmars-d puremagic.com...
 This may not be D. Gettext says to solve it as follow:

 throw new Exception(gettext("Cool English message at
 %s.").format(DateTime.now()))

 The gettext "compiler" goes through the code an generates all the strings
 that need to be localized. The translation teams modifies those string and
 you store them in file/map for that language. At runtime the i18n library
 turns gettext(...) into a query into that map and returns the actual
 localized string. There is a map for the entire process.

 Localization can also be disable at compile time by making gettext a
 template and generating a "noop" for that operation.

 I have been thinking of making a module for i18n model after gettext but
 taking advantage of D's language features. Is there some interest in this?
I have some comments on that, but first a little story: At a previous job with a company that shall remain unnamed, our flagship product was this VB6 thing that handled i18n by wrapping all strings intended to be seen by the user like this: XLate("Hello, all ") + (UBound(customerArr)-LBound(customerArr)) + XLate(" of you suckers!") For non-english users, that XLate function would then lookup the right localization for the given message. IIRC, any missing translation just returned the original english text. This was the first time I had actually dealt with i18n, and yet I was pretty certain that was a completely moronic approach (not that it was a stretch to believe: there wasn't much going on at main sequence techn...errmm...I mean at unnamed company, that wasn't completely moronic). I always felt it would be far better ("better" == "won't screw up everytime someone sneezes near the code" and "doesn't make certain things impossible to translate correctly") to have a statically-checked *identifier* that represented a particular message (and not message fragments, either). And then the user-facing string would be looked up via identifier and any appropriate values substituted in. A fairly obvious way to improve^Wfix the system, really.
I used to work a large engineering firm that used a different system but had similar problems to the ones you mentioned below. Instead of using a human readable string they would use an assumed to be unique key. My main problem with this is that the person reading the code has no idea what the message is suppose to say. They would have to look it up in this translation database. One of the cool things of the system was that it used "run-time" reflection to look up the parameter. "Run-time" reflection worked in our C++ environment because a lot of the abstract classes (interfaces) were generated from an IDL. Another interesting part of the system was that it can do recursive translation but if my memory serves me correctly they they needed it because of other limitations in the system. I think the basic problem with both system is that the set of keys in the translation table is decouple from the code. This "gettext compiler" is a way of making this connection.
 Ok, end of story.

 Your mention of gettext, and specifically a "gettext 'compiler'", does
 introduce a way that the same basic idea could be made to actually work:

 Obviously, the big problems with XLate above are:

 1. No way to make sure all messages actually *are* translated (or to prune
 unused translations).

 2. Changing the english text embedded in the code will automatically break
 all translations.

 3. It fails to handle cases where two inserted values need to be rearranged.

 4. If two messages happen to have a common message fragments, the fragments
 will always be translated the same even if their differing context dictates
 they be translated differently


 with named placeholders and passing the substitution data into xlate(). So
 those issues are alredy out of the way.
These are some really good observations and suggestion. Lets move the conversation to I18N module thread.
 By "gettext 'compiler'" I assume you're taking about some preprocessor.
 Probably appropriate for C/C++, but seems kinda sloppy, potentially error
 prone, and inefficient. Fortunately, D has kick-ass metaprogramming, so if
 you require all translations be compiled in (not sure how realistic that is
 in general), you could do:

 xlate!"Hello world"

 And then generate a compile-time error (or a mere notice) for all messages
 that are unhandled. Of course, if you're loading localizations at runtime,
 you could still do something similar and just throw a runtime error, or log
 a warning, etc., that lists all unhandled messages upon loading a
 localization. Or, hell, a hybrid approach: Compile-time errors for built-in
 localizations and runtime for the rest. Or generate a list of all messages
 when compiled with a special version(), which is then handed off to the
 translation team. All sorts of possibilities. That's all sufficient to solve


 Although there is still the other problem Andrei mentioned of over-eagerness
 (not an issue in the compile-time case, though).
Feb 20 2012
prev sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 And so variant is the way to go ?
 
 Clearly, this is a very strong arguement in favor of typed Exception, that
provide usefull information about what went
 wrong. This is a safe approach.
 
 Because this Variant stuff is going to require massive ducktyping of
Exceptions, with all possible errors involved. The
 keys in the Variant[string] will depend on the Exception the dev is facing.
This should be avoided and should warn us
 about the requirement of typed Exceptions.
Some of the things that characterize an exception, their traits, are transversal to any hierachy that you can imagine, now and in the future. You can choose to keep changing a hierarchy, or build in some mechanism to the Exception base class, that will allow you to get your traits without downcasting. Say that at your place of work the boss decides that all exception classes should have a COM error code, or that all exception classes should provide the URL of the request that generated it. You know what will happen? Your boss will require you to derive all your exception classes from COMException or from WebRequestException and then redefine FileNotFound as a subclass of them. So you will have your FileNotFoundException different than the standard library exception. Don't believe me? It happened to .NET... they've got a COMException that wraps any other kind of error during a COM call. --jm
Feb 20 2012
parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
So, if your boss wants the URL of the request that was made
when the standard library threw you a FileNotFoundException,
you can do:


	try {
	      ...
        } catch (Exception ex) {
                //Rethrow the exception with the added detail:
		ex.details["custom_url"] = getenv("URI");
                throw ex;
        }

(don't beat me if URI wasn't the envvar that apache sends for uri, I
don't remember it at the moment).

--jm



On 02/20/2012 04:27 PM, Juan Manuel Cabo wrote:
 And so variant is the way to go ?

 Clearly, this is a very strong arguement in favor of typed Exception, that
provide usefull information about what went
 wrong. This is a safe approach.

 Because this Variant stuff is going to require massive ducktyping of
Exceptions, with all possible errors involved. The
 keys in the Variant[string] will depend on the Exception the dev is facing.
This should be avoided and should warn us
 about the requirement of typed Exceptions.
Some of the things that characterize an exception, their traits, are transversal to any hierachy that you can imagine, now and in the future. You can choose to keep changing a hierarchy, or build in some mechanism to the Exception base class, that will allow you to get your traits without downcasting. Say that at your place of work the boss decides that all exception classes should have a COM error code, or that all exception classes should provide the URL of the request that generated it. You know what will happen? Your boss will require you to derive all your exception classes from COMException or from WebRequestException and then redefine FileNotFound as a subclass of them. So you will have your FileNotFoundException different than the standard library exception. Don't believe me? It happened to .NET... they've got a COMException that wraps any other kind of error during a COM call. --jm
Feb 20 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 20:32, Juan Manuel Cabo a écrit :
 So, if your boss wants the URL of the request that was made
 when the standard library threw you a FileNotFoundException,
 you can do:


 	try {
 	      ...
          } catch (Exception ex) {
                  //Rethrow the exception with the added detail:
 		ex.details["custom_url"] = getenv("URI");
                  throw ex;
          }

 (don't beat me if URI wasn't the envvar that apache sends for uri, I
 don't remember it at the moment).

 --jm



 On 02/20/2012 04:27 PM, Juan Manuel Cabo wrote:
 And so variant is the way to go ?

 Clearly, this is a very strong arguement in favor of typed Exception, that
provide usefull information about what went
 wrong. This is a safe approach.

 Because this Variant stuff is going to require massive ducktyping of
Exceptions, with all possible errors involved. The
 keys in the Variant[string] will depend on the Exception the dev is facing.
This should be avoided and should warn us
 about the requirement of typed Exceptions.
Some of the things that characterize an exception, their traits, are transversal to any hierachy that you can imagine, now and in the future. You can choose to keep changing a hierarchy, or build in some mechanism to the Exception base class, that will allow you to get your traits without downcasting. Say that at your place of work the boss decides that all exception classes should have a COM error code, or that all exception classes should provide the URL of the request that generated it. You know what will happen? Your boss will require you to derive all your exception classes from COMException or from WebRequestException and then redefine FileNotFound as a subclass of them. So you will have your FileNotFoundException different than the standard library exception. Don't believe me? It happened to .NET... they've got a COMException that wraps any other kind of error during a COM call. --jm
That wouldn't work, because you'll erase the stacktrace. Plus, you are confusing inheritance with composition here. What you want is throw a COMException and link it to the original Exception. You have to consider Exception as a linkedlist, one being the cause of another.
Feb 20 2012
next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
On Feb 20, 2012, at 11:44 AM, deadalnix wrote:

 That wouldn't work, because you'll erase the stacktrace.
It wouldn't be difficult to not overwrite a stack trace if one already = exists on throw.=
Feb 20 2012
parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
On 02/20/2012 04:44 PM, Sean Kelly wrote:
 On Feb 20, 2012, at 11:44 AM, deadalnix wrote:
 
 That wouldn't work, because you'll erase the stacktrace.
It wouldn't be difficult to not overwrite a stack trace if one already exists on throw.
That would be very nice!! Java doesn't have a stacktrace reset problem: I tried the following in java. You can see by the output that the stacktrace of the exception object is preserved (I didn't leave blank lines on purpouse so you can count the line number shown in the output unequivocally): public class bla { public static void main(String[] args) throws Exception { anotherfunc(); } public static void anotherfunc() throws Exception { try { System.out.println("another func"); badfunc(); } catch (Exception ex) { //rethrow the same exception: throw ex; } } public static void badfunc() throws Exception { System.out.println("bad func"); throw new Exception("badfunc"); } } another func bad func Exception in thread "main" java.lang.Exception: badfunc at bla.badfunc(bla.java:16) at bla.anotherfunc(bla.java:8) at bla.main(bla.java:3) --jm
Feb 20 2012
prev sibling parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 That wouldn't work, because you'll erase the stacktrace.

 Plus, you are confusing inheritance with composition here. What you want is
throw a COMException and link it to the
 original Exception. You have to consider Exception as a linkedlist, one being
the cause of another.
You are correct. But it doesn't change the FILE and LINE attributes of the exception. The code below changes the msg of the exception and rethrows it. Please note that the stacktrace is changed as you say. But the: object.Exception t.d(17): another points to the site where it was produced originally: import std.stdio; void main () { anotherFunc(); } void anotherFunc() { try { writeln("another func"); badfunc(); } catch (Exception ex) { ex.msg = "another"; throw ex; } } void badfunc() { writeln("bad func"); throw new Exception("badfunc"); } another func bad func object.Exception t.d(17): another ---------------- ./t(void t.anotherFunc()+0x2b) [0x42a1c7] ./t(_Dmain+0x9) [0x42a195] ./t(extern (C) int rt.dmain2.main(int, char**).void runMain()+0x17) [0x43c003] ./t(extern (C) int rt.dmain2.main(int, char**).void tryExec(scope void delegate())+0x2a) [0x43b97a] ./t(extern (C) int rt.dmain2.main(int, char**).void runAll()+0x42) [0x43c056] ./t(extern (C) int rt.dmain2.main(int, char**).void tryExec(scope void delegate())+0x2a) [0x43b97a] ./t(main+0xd3) [0x43b90b] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xff) [0x7fc83b628eff] ---------------- On 02/20/2012 04:44 PM, deadalnix wrote:
 Le 20/02/2012 20:32, Juan Manuel Cabo a écrit :
 So, if your boss wants the URL of the request that was made
 when the standard library threw you a FileNotFoundException,
 you can do:


     try {
           ...
          } catch (Exception ex) {
                  //Rethrow the exception with the added detail:
         ex.details["custom_url"] = getenv("URI");
                  throw ex;
          }

 (don't beat me if URI wasn't the envvar that apache sends for uri, I
 don't remember it at the moment).

 --jm



 On 02/20/2012 04:27 PM, Juan Manuel Cabo wrote:
 And so variant is the way to go ?

 Clearly, this is a very strong arguement in favor of typed Exception, that
provide usefull information about what went
 wrong. This is a safe approach.

 Because this Variant stuff is going to require massive ducktyping of
Exceptions, with all possible errors involved. The
 keys in the Variant[string] will depend on the Exception the dev is facing.
This should be avoided and should warn us
 about the requirement of typed Exceptions.
Some of the things that characterize an exception, their traits, are transversal to any hierachy that you can imagine, now and in the future. You can choose to keep changing a hierarchy, or build in some mechanism to the Exception base class, that will allow you to get your traits without downcasting. Say that at your place of work the boss decides that all exception classes should have a COM error code, or that all exception classes should provide the URL of the request that generated it. You know what will happen? Your boss will require you to derive all your exception classes from COMException or from WebRequestException and then redefine FileNotFound as a subclass of them. So you will have your FileNotFoundException different than the standard library exception. Don't believe me? It happened to .NET... they've got a COMException that wraps any other kind of error during a COM call. --jm
Feb 20 2012
parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
About this part:

 What you want is throw a COMException and link it to the original
 Exception. You have to consider Exception as a linkedlist, one
 being the cause of another.
The Variant[string] is an idea to help avoid people creating new kinds of exception types that don't add nothing. I guess that you are proving my point. --jm On 02/20/2012 04:48 PM, Juan Manuel Cabo wrote:
 That wouldn't work, because you'll erase the stacktrace.

 Plus, you are confusing inheritance with composition here. What you want is
throw a COMException and link it to the
 original Exception. You have to consider Exception as a linkedlist, one being
the cause of another.
You are correct. But it doesn't change the FILE and LINE attributes of the exception. The code below changes the msg of the exception and rethrows it. Please note that the stacktrace is changed as you say. But the: object.Exception t.d(17): another points to the site where it was produced originally: import std.stdio; void main () { anotherFunc(); } void anotherFunc() { try { writeln("another func"); badfunc(); } catch (Exception ex) { ex.msg = "another"; throw ex; } } void badfunc() { writeln("bad func"); throw new Exception("badfunc"); } another func bad func object.Exception t.d(17): another ---------------- ./t(void t.anotherFunc()+0x2b) [0x42a1c7] ./t(_Dmain+0x9) [0x42a195] ./t(extern (C) int rt.dmain2.main(int, char**).void runMain()+0x17) [0x43c003] ./t(extern (C) int rt.dmain2.main(int, char**).void tryExec(scope void delegate())+0x2a) [0x43b97a] ./t(extern (C) int rt.dmain2.main(int, char**).void runAll()+0x42) [0x43c056] ./t(extern (C) int rt.dmain2.main(int, char**).void tryExec(scope void delegate())+0x2a) [0x43b97a] ./t(main+0xd3) [0x43b90b] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xff) [0x7fc83b628eff] ---------------- On 02/20/2012 04:44 PM, deadalnix wrote:
 Le 20/02/2012 20:32, Juan Manuel Cabo a écrit :
 So, if your boss wants the URL of the request that was made
 when the standard library threw you a FileNotFoundException,
 you can do:


     try {
           ...
          } catch (Exception ex) {
                  //Rethrow the exception with the added detail:
         ex.details["custom_url"] = getenv("URI");
                  throw ex;
          }

 (don't beat me if URI wasn't the envvar that apache sends for uri, I
 don't remember it at the moment).

 --jm



 On 02/20/2012 04:27 PM, Juan Manuel Cabo wrote:
 And so variant is the way to go ?

 Clearly, this is a very strong arguement in favor of typed Exception, that
provide usefull information about what went
 wrong. This is a safe approach.

 Because this Variant stuff is going to require massive ducktyping of
Exceptions, with all possible errors involved. The
 keys in the Variant[string] will depend on the Exception the dev is facing.
This should be avoided and should warn us
 about the requirement of typed Exceptions.
Some of the things that characterize an exception, their traits, are transversal to any hierachy that you can imagine, now and in the future. You can choose to keep changing a hierarchy, or build in some mechanism to the Exception base class, that will allow you to get your traits without downcasting. Say that at your place of work the boss decides that all exception classes should have a COM error code, or that all exception classes should provide the URL of the request that generated it. You know what will happen? Your boss will require you to derive all your exception classes from COMException or from WebRequestException and then redefine FileNotFound as a subclass of them. So you will have your FileNotFoundException different than the standard library exception. Don't believe me? It happened to .NET... they've got a COMException that wraps any other kind of error during a COM call. --jm
Feb 20 2012
parent reply Sean Kelly <sean invisibleduck.org> writes:
On Feb 20, 2012, at 11:54 AM, Juan Manuel Cabo wrote:

 About this part:
=20
 What you want is throw a COMException and link it to the original
 Exception. You have to consider Exception as a linkedlist, one
 being the cause of another.
=20 The Variant[string] is an idea to help avoid people creating new kinds of exception types that don't add nothing.
I don't think this makes sense. To effectively use whatever's in the = table I pretty much have to know what error I'm handling, and this isn't = possible if type information is lost. Unless this determination is = moved to a run-time check of some field within the exception, and then = I'm making my code that much messier and less efficient by putting in = tests of this identifier against a list of constants. Personally, I = don't see any use for this table beyond providing context, much like we = already have with file, line, etc.=
Feb 20 2012
parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
I agree to disagree.

But about your argument for efficiency, any check that you do when
examining an exception doesn't need to be lightning fast, after all,
if you are looking at an exception object, it means that the stack
got unwound, and any nano seconds that you spend doing this:

	catch (Exception ex) {
		if ("mycustomfield" in ex) {
			.. do something ..
 		}
	}

which is just an "in" check in an associative array, which might never
have more than 10 elements (so even linear search is appropiate),
is overshadowed by the time that the runtime took to unwind the stack
and serve the exception to your catch block.

--jm


On 02/20/2012 05:05 PM, Sean Kelly wrote:
 On Feb 20, 2012, at 11:54 AM, Juan Manuel Cabo wrote:
 
 About this part:

 What you want is throw a COMException and link it to the original
 Exception. You have to consider Exception as a linkedlist, one
 being the cause of another.
The Variant[string] is an idea to help avoid people creating new kinds of exception types that don't add nothing.
I don't think this makes sense. To effectively use whatever's in the table I pretty much have to know what error I'm handling, and this isn't possible if type information is lost. Unless this determination is moved to a run-time check of some field within the exception, and then I'm making my code that much messier and less efficient by putting in tests of this identifier against a list of constants. Personally, I don't see any use for this table beyond providing context, much like we already have with file, line, etc.
Feb 20 2012
parent Sean Kelly <sean invisibleduck.org> writes:
I meant more that by ignoring the type matching system in favor of our =
own runtime convention we've prevented the compiler from doing any =
optimizations (or error checking) that may have been possible.

On Feb 20, 2012, at 12:15 PM, Juan Manuel Cabo wrote:

 I agree to disagree.
=20
 But about your argument for efficiency, any check that you do when
 examining an exception doesn't need to be lightning fast, after all,
 if you are looking at an exception object, it means that the stack
 got unwound, and any nano seconds that you spend doing this:
=20
 	catch (Exception ex) {
 		if ("mycustomfield" in ex) {
 			.. do something ..
 		}
 	}
=20
 which is just an "in" check in an associative array, which might never
 have more than 10 elements (so even linear search is appropiate),
 is overshadowed by the time that the runtime took to unwind the stack
 and serve the exception to your catch block.
=20
 --jm
=20
=20
 On 02/20/2012 05:05 PM, Sean Kelly wrote:
 On Feb 20, 2012, at 11:54 AM, Juan Manuel Cabo wrote:
=20
 About this part:
=20
 What you want is throw a COMException and link it to the original
 Exception. You have to consider Exception as a linkedlist, one
 being the cause of another.
=20 The Variant[string] is an idea to help avoid people creating new =
kinds
 of exception types that don't add nothing.
=20 I don't think this makes sense. To effectively use whatever's in the =
table I pretty much have to know what error I'm handling, and this isn't = possible if type information is lost. Unless this determination is = moved to a run-time check of some field within the exception, and then = I'm making my code that much messier and less efficient by putting in = tests of this identifier against a list of constants. Personally, I = don't see any use for this table beyond providing context, much like we = already have with file, line, etc.
=20
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:32 PM, Juan Manuel Cabo wrote:
 So, if your boss wants the URL of the request that was made
 when the standard library threw you a FileNotFoundException,
 you can do:


 	try {
 	      ...
          } catch (Exception ex) {
                  //Rethrow the exception with the added detail:
 		ex.details["custom_url"] = getenv("URI");
                  throw ex;
          }
That's a very interesting angle! Andrei
Feb 20 2012
next sibling parent Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
 That's a very interesting angle!

 Andrei
Thanks!! The main point I was making is, that otherwise, a user would be forced to create a new exception kind and chain the original exception to it. Now... what about the catch blocks defined upstream in the call tree? So this solves that then. The code upstream can still make a catch (FileNotFound) that obviously distinguishes FileNotFound from other exception types. Otherwise, if all one does is ie: COMExceptions that chain other exceptions, the distinction is lost, and rethrows mess everything. --jm On 02/20/2012 05:49 PM, Andrei Alexandrescu wrote:
 On 2/20/12 1:32 PM, Juan Manuel Cabo wrote:
 So, if your boss wants the URL of the request that was made
 when the standard library threw you a FileNotFoundException,
 you can do:


     try {
           ...
          } catch (Exception ex) {
                  //Rethrow the exception with the added detail:
         ex.details["custom_url"] = getenv("URI");
                  throw ex;
          }
That's a very interesting angle! Andrei
Feb 20 2012
prev sibling next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 21:49, Andrei Alexandrescu a écrit :
 On 2/20/12 1:32 PM, Juan Manuel Cabo wrote:
 So, if your boss wants the URL of the request that was made
 when the standard library threw you a FileNotFoundException,
 you can do:


 try {
 ...
 } catch (Exception ex) {
 //Rethrow the exception with the added detail:
 ex.details["custom_url"] = getenv("URI");
 throw ex;
 }
That's a very interesting angle! Andrei
That is terrible. If ones begin to do this, then you cannot rely on what you'll find in the Exception infos. And if people start to do that before any code is writen, It is very bad sign.
Feb 20 2012
prev sibling parent dennis luehring <dl.soluz gmx.net> writes:
Am 20.02.2012 21:49, schrieb Andrei Alexandrescu:
 On 2/20/12 1:32 PM, Juan Manuel Cabo wrote:
  So, if your boss wants the URL of the request that was made
  when the standard library threw you a FileNotFoundException,
  you can do:


  	try {
  	...
           } catch (Exception ex) {
                   //Rethrow the exception with the added detail:
  		ex.details["custom_url"] = getenv("URI");
                   throw ex;
           }
That's a very interesting angle! Andrei
that is sooooo bad - fuck signatures just add information ... now im able to just catch always Exception (sweet) ... but my damn handle code needs to know the real interface ... crippled into details["custom_url"]... so in the end we will come to if( detail[x] is in there) if( detail[y] is in there ) if ( detail[z] is in threre ) shit i agree 100% - its easy for just giving out the information, but awfull for handling the exception
Feb 20 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 11:11:08AM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 11:05 AM, foobar wrote:
[...]
Separation of concerns - exceptions are meant to notify the
*developer* of errors. User facing error messages is a separate
concern that exceptions should not be responsible for. it's not just
outsourcing the translation strings, it's the developer's job to
determine what if at all should be done with the exception.
At the end of the day, a human-readable error message must be properly formatted given some exception that signaled an error. So I disagree that exceptions are meant for the developer. They are mechanism, a means to an end.
No, exceptions *are* meant for the developer, because it's the developer who decides whether and how to to display it to the user.
Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in
her browser, she does not know or care what 404 means. instead she
gets a formated page suggesting her to check her spelling and
probably a suggestion to try google.com instead.
Sure, and the question is how the message gets created.
By encoding *useful* information in the exception, not just some generic stuff lacking in semantic meaning, so that the code that catches the exception knows what the problem is, and can make a sensible decision as to how to display it (or not display it, but react in some other way). Again, this brings us back to class hierarchies. In order to react sensibly to an exception, the code that catches it *has* to know what it is. There's simply no way around this. Just saying "a problem happened" is unhelpful. Code cannot divine the right course of action just by being told that a problem happened. It *needs* to know *what* happened. And the details of what happened depends entirely on the context in which it happened, so the most sensible solution is to use a class hierarchy where you add information to the bottom levels -- that's because that's where the information is!! At the end of the day, using a Variant is no better than using a deep class hierarchy. You're just encoding the exact same structure of information in a different way. You *still* have to know what kind of information is available in the Variant, just like you need to know which exception subclass to catch so that you can access the member variable that tells you what went wrong. For user-facing code, you *need* the catching code to understand what kinds of exceptions can happen so that it can decide whether/how to display it to the user. At my work project, we have a user-facing client-side GUI, and a server-side infrastructure which includes, among other things, a SQL database. There's a middle layer built over the SQL layer that provides various application-specific functions. Both layers can encounter any number of errors. But you know what? The GUI side code displays almost all of these errors as "internal error" to the user. Why? Because the user doesn't know, nor care, that SQL error 1234 occurred. They don't care that middle layer exception 2345 occurred. None of this makes any sense to them. The *developers* care which error it is, so these errors are logged to a *debug channel* that only the developers care to read. It's not even in the regular syslog, because the user admins who read the syslog wouldn't understand what the heck the messages mean anyway. All that's logged is "internal error". Only a few relevant errors from the lower layers are actually translated by the GUI code. User-relevant errors such as "name already exists", "serial number mismatch", and things like that. Which are only a small subset of problems that could potentially occur. In this light, it doesn't make sense to have a fully generic, full-fledged i18n system encoded into Exception (i.e., into every single error the system might encounter). Only a tiny subset of exceptions even *need* to see the light of day, and require i18n. The developers don't care about i18n -- in fact, it's harmful, because it obscures exactly what the error was. If the code throws "Bad parameter error", the developers want to see the exact string "Bad parameter error". The last thing they want is to read this string in Japanese, and then have to figure out what on earth it corresponds with in the code. Translation is only needed for that small subset of errors that is actually meaningful to the end-user. Errors meant for the developers need not, and should not, be translated at all. So then, how does the GUI code know what to translate and what not to translate? That's where an exception class hierarchy is necessary. A hierarchy that allows the code to specifically catch FileNotFoundException and translate that, and leave out other stuff like OracleError1234, ConfigFileParseError, and all the stuff that users don't even remotely understand. Phobos should *not* be the one making this kind of decision. What it needs to do is full information disclosure, and let the catch code make use of it as it sees fit.
the exception notifies the developer of the error, the developer does
extra processing (e.g. to suggest similar valid websites) and the
user get a friendly notification. clearly it doesn't make sense to
put all this into the exception.
That extra processing must format the message given the information passed by the exception. _Definitely_ it doesn't make sense to put the formatting processing in the exception. That's why shipping the information outside the exception in a generic format is necessary, hence the Variant[string].
[...] You're just reinventing class hierarchies using variants. To what end? T -- IBM = I Blame Microsoft
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:56 AM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 11:11:08AM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 11:05 AM, foobar wrote:
[...]
 Separation of concerns - exceptions are meant to notify the
 *developer* of errors. User facing error messages is a separate
 concern that exceptions should not be responsible for. it's not just
 outsourcing the translation strings, it's the developer's job to
 determine what if at all should be done with the exception.
At the end of the day, a human-readable error message must be properly formatted given some exception that signaled an error. So I disagree that exceptions are meant for the developer. They are mechanism, a means to an end.
No, exceptions *are* meant for the developer, because it's the developer who decides whether and how to to display it to the user.
And when she does decide so, isn't it desirable that the exception hierarchy provides good means to do so?
 Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in
 her browser, she does not know or care what 404 means. instead she
 gets a formated page suggesting her to check her spelling and
 probably a suggestion to try google.com instead.
Sure, and the question is how the message gets created.
By encoding *useful* information in the exception, not just some generic stuff lacking in semantic meaning, so that the code that catches the exception knows what the problem is, and can make a sensible decision as to how to display it (or not display it, but react in some other way).
Sure. But to the extent the exception hierarchy can serve a helpful interface, that would only help.
 Again, this brings us back to class hierarchies. In order to react
 sensibly to an exception, the code that catches it *has* to know what it
 is.
Often code has a generic response to all exceptions or categories thereof.
 There's simply no way around this. Just saying "a problem happened"
 is unhelpful.
Of course. That's why it's worth looking into enhancing the functionality of the exception hierarchy. It's a classic of OO design: if you want reuse, push policy up.
 Code cannot divine the right course of action just by
 being told that a problem happened. It *needs* to know *what* happened.
 And the details of what happened depends entirely on the context in
 which it happened, so the most sensible solution is to use a class
 hierarchy where you add information to the bottom levels -- that's
 because that's where the information is!!
I don't think the details depend entirely on the context. Some aspects depend on the origin of the error, and some others depend on the context in which the error occurred. Both are needed.
 At the end of the day, using a Variant is no better than using a deep
 class hierarchy.
It is better because it pushes policy up and replaces formatting code duplicated across catch sites, with reusable table-based code.
 You're just encoding the exact same structure of
 information in a different way.
Yes. It's a way that is uniform, which makes it possible to push in the base class.
 You *still* have to know what kind of
 information is available in the Variant, just like you need to know
 which exception subclass to catch so that you can access the member
 variable that tells you what went wrong.
Yes. That's where the formatting template comes together with the concrete information.
 For user-facing code, you *need* the catching code to understand what
 kinds of exceptions can happen so that it can decide whether/how to
 display it to the user.
That doesn't contradict, prevent, or preclude designing class hierarchies for uniform interfaces.
 In this light, it doesn't make sense to have a fully generic,
 full-fledged i18n system encoded into Exception (i.e., into every single
 error the system might encounter).
Of course not. I'm just discussing adding interface to enable that possibility (and others).
 Only a tiny subset of exceptions even
 *need* to see the light of day, and require i18n.
Agreed.
 You're just reinventing class hierarchies using variants. To what end?
I don't think so. What happens here is simple OO design - pushing interface up so as to allow manipulation of bases instead of duplicating code dealing with concrete classes. Maybe this is all a misunderstanding. Allow me to explain what the intent is. I suggest we add a method (better than a member, upon further thinking): class Exception : Error { Variant[string] info() { return null; // no extra info here } ... } Classes that inherit Exception may override info: class GetOptException : Exception { Variant[string] info() { return [ "flag" : _flag ]; } ... string _flag; } Now code that wants to handle GetOptException specifically can definitely do so. The advantage of defining info() is that now we get to write code that does not need duplication across all concrete types, but instead gets to use Exception.info in conjunction with various formatting and rendering engines. Again, it's simple OO design. Andrei
Feb 20 2012
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 18:05:38 foobar wrote:
 Separation of concerns - exceptions are meant to notify the
 *developer* of errors. User facing error messages is a separate
 concern that exceptions should not be responsible for. it's not
 just outsourcing the translation strings, it's the developer's
 job to determine what if at all should be done with the exception.
Agreed. Users shouldn't be seeing exception messages. They are intended for the developer. Users don't know or care about them. You don't display error codes to users when something goes wrong do you? The point of exceptions is to allow the _program_ to react appropriately. In some cases, that means printing out a message to the user or popping up a dialogue. In many, it doesn't. We need exceptions to give the program the information that it needs to programmatically react to what went wrong, not print a nice, internationalized error message. And adding an internationalization mechanism would actually make things _worse_, because it complicates exceptions for something that they don't generally need, and it makes debugging harder, because you can't see the message as easily in the debugger. The fields and functions that exceptions have should be geared towards facilitating the program processing and recovering from the exception, not printing out error messages. And a big part of that is giving them fields which can be processed programmatically, not ways to print internationalized strings. - Jonathan M Davis
Feb 20 2012
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 12:28:27 Andrei Alexandrescu wrote:
 On 2/20/12 12:20 PM, Jonathan M Davis wrote:
 On Monday, February 20, 2012 18:05:38 foobar wrote:
 Separation of concerns - exceptions are meant to notify the
 *developer* of errors. User facing error messages is a separate
 concern that exceptions should not be responsible for. it's not
 just outsourcing the translation strings, it's the developer's
 job to determine what if at all should be done with the exception.
Agreed. Users shouldn't be seeing exception messages. They are intended for the developer. Users don't know or care about them. You don't display error codes to users when something goes wrong do you?
You don't want to duplicate all over the place formatting code do you?
Most of the time that won't happen simply because you need different formatting code for different types of errors and contexts. And if it does, you can just write a function which takes the exception and prints out the message that you want with the formatting that you want. Also, for contexts where you're going to give a message to the user, toString doesn't cut it, because it's doing stuff like putting the stack trace in the message. So, do you intend to effectively have _two_ error messages with every exception - one for the developer and one for the user? So, in the cases where you need to save on formatting code, I don't think that it's hard to do so without adding anything to Exception, and it's stuff which frequently needs to be application-specific anyway, in which case, Exception couldn't possibly provide the right message anyway.
 And adding an
 internationalization mechanism would actually make things _worse_, because
 it complicates exceptions for something that they don't generally need,
 and it makes debugging harder, because you can't see the message as
 easily in the debugger.
 
 The fields and functions that exceptions have should be geared towards
 facilitating the program processing and recovering from the exception, not
 printing out error messages. And a big part of that is giving them fields
 which can be processed programmatically, not ways to print
 internationalized strings.
Yes. Programmatic processing in a class hierarchy means you want to push policy up and implementation down. If you don't do this, you need to duplicate client code to work with each derived class, instead of having client code deal with good primitives.
So, instead of catch(SpecificException1 e) { //use fields specific to this exception to do whatever you need to do } catch(SpecificException2 e) { //use fields specific to this exception to do whatever you need to do } catch(GenericException e) { //handle the generic exception as best you can given the lack of //a specific one } you end up with something effectively along the lines of catch(GenericException e) { if(/* e is SpecificException1 */) { //use fields specific to this exception to do whatever you need to do } else if(/* e is SpecificException2 */) { //use fields specific to this exception to do whatever you need to do } else { //handle the generic exception as best you can given the lack of //a specific one } } How is that better? It's throwing away catch's ability to put the program execution in the correct handling code. I don't see how your suggestion is an improvement over using an actual class hierarchy. You can't generally make the handling uniform unless the catch block is simply going to do something like log the message rather than handle it. And if you're doing that, you can just catch the more generic exception. It seems like you want to effectively do something like add a method to Exception along the lines of handleException which exception handling code calls so that it's nice and generic. But it's the exception handling code which needs to do specific stuff and thus frequently _can't_ be generic. Also, how would the whole variant hash work with purity? You can access global variables in pure functions. So, I wouldn't expect it to work in a pure function. Class hierarchies do. I would think that that would kill the whole idea right there, even if it were better than using a class hierarchy (which I don't think that it is). - Jonathan M Davis
Feb 20 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 01:52:15PM -0500, Jonathan M Davis wrote:
[...]
 So, instead of
 
 catch(SpecificException1 e)
 {
  //use fields specific to this exception to do whatever you need to do
 }
 catch(SpecificException2 e)
 {
  //use fields specific to this exception to do whatever you need to do
 }
 catch(GenericException e)
 {
  //handle the generic exception as best you can given the lack of
  //a specific one
 }
 
 you end up with something effectively along the lines of
 
 catch(GenericException e)
 {
  if(/* e is SpecificException1 */)
  {
  //use fields specific to this exception to do whatever you need to do
  }
  else if(/* e is SpecificException2 */)
  {
  //use fields specific to this exception to do whatever you need to do
  }
  else
  {
  //handle the generic exception as best you can given the lack of
  //a specific one
  }
 }
[...] I think what Andrei wants to do is more along these lines: catch(Exception e) { writeln(formatLocaleString(e.fmt, e.data)); } I think there's some merit to this idea. However, I'm still unsure about storing stuff in a Variant. For one thing, you either need some sort of format string in the exception object (as I have above), which is bad, as somebody else pointed out, because now you're mixing i18n code into exception code, or you need some way of figuring out what format to use for which exception. So ultimately, you'll still end up with with a huge switch statement, or a global table of all exceptions (not good for maintenance, now every time someone changes an exception he has to remember to update the table). One solution, perhaps, is to have an i18n file containing mappings of exception types to message formats. So that you can have: class Exception : Error { Variant[string] info; ... } string[string] exceptionFormats = loadLocaleData(); string formatExceptionMsg(LocaleInfo li, Exception e) { if (typeid(e).name in exceptionFormats) { return format(exceptionFormats[typeid(e)], e.info); } return e.msg; } This may be acceptable, if the catching code knows which formats are actually defined in the locale data, so it will only call formatExceptionMsg() if there's actually a format defined for it. This way, you don't need to have *every* exception translated, just those that your catching code will actually use. The one objection I have with this, though, is that if the catching code wants to specifically catch, say, FileNotFoundException, and extract the offending filename from the exception object, it would have to do a string lookup in Exception.info, rather than just accessing FileNotFoundException.filename directly. Then if the Phobos maintainer renames the field, the code still compiles since the compiler has no way to know that Exception.info["filename"] is no longer set by the ctor of FileNotFoundException, so the problem will go undetected until the exception actually occurs, at which time the catching code gets a runtime error (very bad). Having the field directly in FileNotFoundException is better, because you get a compile-time error when the field is renamed, so the developer can fix it before it ships, rather than have the customer run into the runtime error. 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. T -- Never wrestle a pig. You both get covered in mud, and the pig likes it.
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:15 PM, H. S. Teoh wrote:
 I think what Andrei wants to do is more along these lines:

 	catch(Exception e) {
 		writeln(formatLocaleString(e.fmt, e.data));
 	}
Appproximately, except the formatting string is not inside the exception.
 I think there's some merit to this idea. However, I'm still unsure about
 storing stuff in a Variant.

 For one thing, you either need some sort of format string in the
 exception object (as I have above), which is bad, as somebody else
 pointed out, because now you're mixing i18n code into exception code, or
 you need some way of figuring out what format to use for which
 exception. So ultimately, you'll still end up with with a huge switch
 statement,
no
 or a global table of all exceptions (not good for
 maintenance, now every time someone changes an exception he has to
 remember to update the table).
Tables (including database-store ones etc.) are a way of life in international applications.
 One solution, perhaps, is to have an i18n file containing mappings of
 exception types to message formats. So that you can have:

 	class Exception : Error {
 		Variant[string] info;
 		...
 	}

 	string[string] exceptionFormats = loadLocaleData();

 	string formatExceptionMsg(LocaleInfo li, Exception e) {
 		if (typeid(e).name in exceptionFormats) {
 			return format(exceptionFormats[typeid(e)],
 				e.info);
 		}
 		return e.msg;
 	}
That's quite sensible.
 This may be acceptable, if the catching code knows which formats are
 actually defined in the locale data, so it will only call
 formatExceptionMsg() if there's actually a format defined for it. This
 way, you don't need to have *every* exception translated, just those
 that your catching code will actually use.
Yah.
 The one objection I have with this, though, is that if the catching code
 wants to specifically catch, say, FileNotFoundException, and extract the
 offending filename from the exception object, it would have to do a
 string lookup in Exception.info, rather than just accessing
 FileNotFoundException.filename directly.
Speed of rendering exception messages is secondary, but point taken.
 Then if the Phobos maintainer renames the field, the code still compiles
 since the compiler has no way to know that Exception.info["filename"] is
 no longer set by the ctor of FileNotFoundException, so the problem will
 go undetected until the exception actually occurs, at which time the
 catching code gets a runtime error (very bad).

 Having the field directly in FileNotFoundException is better, because
 you get a compile-time error when the field is renamed, so the developer
 can fix it before it ships, rather than have the customer run into the
 runtime error.
Yah, it also means there's a lot of duplicated code which may contain other bugs.
 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.
Runtime reflection is indeed a more general realization of the Variant[string] thingie. A naming convention (e.g. prefix all members in Exception relevant to rendering with "info" would complete a nice scheme. Andrei
Feb 20 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-02-20 18:05, foobar wrote:
 On Monday, 20 February 2012 at 16:37:28 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org> wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string]
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages? Andrei
Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception. Trivial example: My mom enters a misspelled URL (e.g. goggle.com) in her browser, she does not know or care what 404 means. instead she gets a formated page suggesting her to check her spelling and probably a suggestion to try google.com instead. the exception notifies the developer of the error, the developer does extra processing (e.g. to suggest similar valid websites) and the user get a friendly notification. clearly it doesn't make sense to put all this into the exception.
I completely agree, that's exactly how it should be done. I can't understand how difficult it seems to not see this. -- /Jacob Carlborg
Feb 20 2012
prev sibling next sibling parent reply Jose Armando Garcia <jsancio gmail.com> writes:
On Mon, Feb 20, 2012 at 2:37 PM, Andrei Alexandrescu <
SeeWebsiteForEmail erdani.org> wrote:

 On 2/20/12 10:16 AM, Nick Sabalausky wrote:

 "Andrei Alexandrescu"<SeeWebsiteForEma**il erdani.org<SeeWebsiteForEmail erdani.org>>
  wrote in message
 news:jhtq31$u8q$1 digitalmars.**com...

 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages?
This may not be D. Gettext says to solve it as follow: throw new Exception(gettext("Cool English message at %s.").format(DateTime.now())) The gettext "compiler" goes through the code an generates all the strings that need to be localized. The translation teams modifies those string and you store them in file/map for that language. At runtime the i18n library turns gettext(...) into a query into that map and returns the actual localized string. There is a map for the entire process. Localization can also be disable at compile time by making gettext a template and generating a "noop" for that operation. I have been thinking of making a module for i18n model after gettext but taking advantage of D's language features. Is there some interest in this? Thanks, -Jose
Feb 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 18:28, Jose Armando Garcia a crit :
 On Mon, Feb 20, 2012 at 2:37 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>>
 wrote:

     On 2/20/12 10:16 AM, Nick Sabalausky wrote:

         "Andrei Alexandrescu"<SeeWebsiteForEma__il erdani.org
         <mailto:SeeWebsiteForEmail erdani.org>>  wrote in message
         news:jhtq31$u8q$1 digitalmars.__com...


             Again, I think this thread clarified we need the
             "Variant[string] info;"
             member however we define the hierarchy.


         I disagree. I don't see a need for that.


     How would we address custom formatting of e.g. error messages?


 This may not be D. Gettext says to solve it as follow:

 throw new Exception(gettext("Cool English message at
 %s.").format(DateTime.now()))

 The gettext "compiler" goes through the code an generates all the
 strings that need to be localized. The translation teams modifies those
 string and you store them in file/map for that language. At runtime the
 i18n library turns gettext(...) into a query into that map and returns
 the actual localized string. There is a map for the entire process.

 Localization can also be disable at compile time by making gettext a
 template and generating a "noop" for that operation.

 I have been thinking of making a module for i18n model after gettext but
 taking advantage of D's language features. Is there some interest in this?
This is definitively something that should be integrated into phobos IMO.
 Thanks,
 -Jose
Feb 20 2012
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:28 AM, Jose Armando Garcia wrote:
 On Mon, Feb 20, 2012 at 2:37 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>>
 wrote:

     On 2/20/12 10:16 AM, Nick Sabalausky wrote:

         "Andrei Alexandrescu"<SeeWebsiteForEma__il erdani.org
         <mailto:SeeWebsiteForEmail erdani.org>>  wrote in message
         news:jhtq31$u8q$1 digitalmars.__com...


             Again, I think this thread clarified we need the
             "Variant[string] info;"
             member however we define the hierarchy.


         I disagree. I don't see a need for that.


     How would we address custom formatting of e.g. error messages?


 This may not be D. Gettext says to solve it as follow:

 throw new Exception(gettext("Cool English message at
 %s.").format(DateTime.now()))

 The gettext "compiler" goes through the code an generates all the
 strings that need to be localized. The translation teams modifies those
 string and you store them in file/map for that language. At runtime the
 i18n library turns gettext(...) into a query into that map and returns
 the actual localized string. There is a map for the entire process.

 Localization can also be disable at compile time by making gettext a
 template and generating a "noop" for that operation.
So this moves formatting from the catch point to the throw point. That's fine, but it forces the formatting effort upfront even when the message is not actually used and leads to duplication because each call site would need to provide the message entirely. I think it's worth investigating formatting at the catch site.
 I have been thinking of making a module for i18n model after gettext but
 taking advantage of D's language features. Is there some interest in this?
Yah, this would be of interest. Andrei
Feb 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 19:02, Andrei Alexandrescu a crit :
 On 2/20/12 11:28 AM, Jose Armando Garcia wrote:
 On Mon, Feb 20, 2012 at 2:37 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>>
 wrote:

 On 2/20/12 10:16 AM, Nick Sabalausky wrote:

 "Andrei Alexandrescu"<SeeWebsiteForEma__il erdani.org
 <mailto:SeeWebsiteForEmail erdani.org>> wrote in message
 news:jhtq31$u8q$1 digitalmars.__com...


 Again, I think this thread clarified we need the
 "Variant[string] info;"
 member however we define the hierarchy.


 I disagree. I don't see a need for that.


 How would we address custom formatting of e.g. error messages?


 This may not be D. Gettext says to solve it as follow:

 throw new Exception(gettext("Cool English message at
 %s.").format(DateTime.now()))

 The gettext "compiler" goes through the code an generates all the
 strings that need to be localized. The translation teams modifies those
 string and you store them in file/map for that language. At runtime the
 i18n library turns gettext(...) into a query into that map and returns
 the actual localized string. There is a map for the entire process.

 Localization can also be disable at compile time by making gettext a
 template and generating a "noop" for that operation.
So this moves formatting from the catch point to the throw point. That's fine, but it forces the formatting effort upfront even when the message is not actually used and leads to duplication because each call site would need to provide the message entirely. I think it's worth investigating formatting at the catch site.
This is not the way we want to go. You have a piece of code to describe what the problem is, another to descide what to do with that problem and again another to do something that may go wrong. Formatting is an error handling problem, and so formatting at the throw point is a bad separation of concerns. Plus, the way you want to format stuff is likely to depend on the application.
Feb 20 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 12:02:21PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 11:28 AM, Jose Armando Garcia wrote:
[...]
This may not be D. Gettext says to solve it as follow:

throw new Exception(gettext("Cool English message at
%s.").format(DateTime.now()))

The gettext "compiler" goes through the code an generates all the
strings that need to be localized. The translation teams modifies
those string and you store them in file/map for that language. At
runtime the i18n library turns gettext(...) into a query into that
map and returns the actual localized string. There is a map for the
entire process.

Localization can also be disable at compile time by making gettext a
template and generating a "noop" for that operation.
So this moves formatting from the catch point to the throw point. That's fine, but it forces the formatting effort upfront even when the message is not actually used and leads to duplication because each call site would need to provide the message entirely. I think it's worth investigating formatting at the catch site.
Ah, I see what you're getting at. What about this then: class MyException : Exception { int info1; string info2; ... // class Exception would provide a default formatMsg() // that simply prints Exception.msg, perhaps also with // translation. override string formatMsg(LocaleInfo li) { return formatI18n(li, "...", info1, info2, ...); } } This way, the catcher doesn't have to know anything about info1, info2, ..., unless it specifically wants to catch MyException and do error recovery based on info1, info2, ... etc.. And the formatting isn't actually done until it's needed. The thrower only sets info1, info2, ..., only when the catcher wants a human-readable message out of it, it will call formatMsg().
I have been thinking of making a module for i18n model after gettext
but taking advantage of D's language features. Is there some interest
in this?
Yah, this would be of interest.
[...] Seconded. T -- I'm still trying to find a pun for "punishment"...
Feb 20 2012
prev sibling next sibling parent reply Johannes Pfau <nospam example.com> writes:
Am Mon, 20 Feb 2012 15:28:33 -0200
schrieb Jose Armando Garcia <jsancio gmail.com>:

 I have been thinking of making a module for i18n model after gettext
 but taking advantage of D's language features. Is there some interest
 in this?
Yep we need this. I also thought about developing an i18n module maybe we can work together on one. I won't have time for it the next 3 weeks though. Anyway, I think we should base our i18n module on boost.locale http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html but boost.locale is pretty big, we should probably start with the translation part only: http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html The API is quite good imho. It'll need some changes to fit D better, but I think this could become a nice D style API. Boost.locale uses the gettext format internally which is important. This way we can use all the gettext tools & websites and the translator community (https://www.transifex.net/start/). BTW: I already have code to read the gettext format, so that could be used.
 
 Thanks,
 -Jose
 
Feb 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 20:04, Johannes Pfau a crit :
 Am Mon, 20 Feb 2012 15:28:33 -0200
 schrieb Jose Armando Garcia<jsancio gmail.com>:

 I have been thinking of making a module for i18n model after gettext
 but taking advantage of D's language features. Is there some interest
 in this?
Yep we need this. I also thought about developing an i18n module maybe we can work together on one. I won't have time for it the next 3 weeks though. Anyway, I think we should base our i18n module on boost.locale http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html but boost.locale is pretty big, we should probably start with the translation part only: http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html The API is quite good imho. It'll need some changes to fit D better, but I think this could become a nice D style API. Boost.locale uses the gettext format internally which is important. This way we can use all the gettext tools& websites and the translator community (https://www.transifex.net/start/). BTW: I already have code to read the gettext format, so that could be used.
 Thanks,
 -Jose
Great initiative, but can you please start this in a new thread ? This one is already quite hard to follow :D
Feb 20 2012
prev sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Mon, Feb 20, 2012 at 5:04 PM, Johannes Pfau <nospam example.com> wrote:

 Am Mon, 20 Feb 2012 15:28:33 -0200
 schrieb Jose Armando Garcia <jsancio gmail.com>:

 I have been thinking of making a module for i18n model after gettext
 but taking advantage of D's language features. Is there some interest
 in this?
Yep we need this. I also thought about developing an i18n module maybe we can work together on one. I won't have time for it the next 3 weeks though. Anyway, I think we should base our i18n module on boost.locale http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html but boost.locale is pretty big, we should probably start with the translation part only: http://www.boost.org/doc/libs/1_48_0/libs/locale/doc/html/index.html
Sounds good! I'll probably wont start before that time either. Let me think about it some more and we can talk after the std.log review.
 The API is quite good imho. It'll need some changes to fit D better,
 but I think this could become a nice D style API.

 Boost.locale uses the gettext format internally which is important.
 This way we can use all the gettext tools & websites and the translator
 community (https://www.transifex.net/start/).

 BTW: I already have code to read the gettext format, so that could be
 used.
That is great! It would be great if we can make a state of the art I18N module. Looking forward to it!
 Thanks,
 -Jose
Feb 20 2012
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-02-20 18:28, Jose Armando Garcia wrote:
 On Mon, Feb 20, 2012 at 2:37 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org <mailto:SeeWebsiteForEmail erdani.org>>
 wrote:

     On 2/20/12 10:16 AM, Nick Sabalausky wrote:

         "Andrei Alexandrescu"<SeeWebsiteForEma__il erdani.org
         <mailto:SeeWebsiteForEmail erdani.org>>  wrote in message
         news:jhtq31$u8q$1 digitalmars.__com...


             Again, I think this thread clarified we need the
             "Variant[string] info;"
             member however we define the hierarchy.


         I disagree. I don't see a need for that.


     How would we address custom formatting of e.g. error messages?


 This may not be D. Gettext says to solve it as follow:

 throw new Exception(gettext("Cool English message at
 %s.").format(DateTime.now()))

 The gettext "compiler" goes through the code an generates all the
 strings that need to be localized. The translation teams modifies those
 string and you store them in file/map for that language. At runtime the
 i18n library turns gettext(...) into a query into that map and returns
 the actual localized string. There is a map for the entire process.

 Localization can also be disable at compile time by making gettext a
 template and generating a "noop" for that operation.

 I have been thinking of making a module for i18n model after gettext but
 taking advantage of D's language features. Is there some interest in this?

 Thanks,
 -Jose
Yes, but not for this purpose. -- /Jacob Carlborg
Feb 20 2012
prev sibling parent "Nick Sabalausky" <a a.a> writes:
"Jose Armando Garcia" <jsancio gmail.com> wrote in message 
news:mailman.672.1329758926.20196.digitalmars-d puremagic.com...
 This may not be D. Gettext says to solve it as follow:

 throw new Exception(gettext("Cool English message at
 %s.").format(DateTime.now()))

 The gettext "compiler" goes through the code an generates all the strings
 that need to be localized. The translation teams modifies those string and
 you store them in file/map for that language. At runtime the i18n library
 turns gettext(...) into a query into that map and returns the actual
 localized string. There is a map for the entire process.

 Localization can also be disable at compile time by making gettext a
 template and generating a "noop" for that operation.

 I have been thinking of making a module for i18n model after gettext but
 taking advantage of D's language features. Is there some interest in this?
I have some comments on that, but first a little story: At a previous job with a company that shall remain unnamed, our flagship product was this VB6 thing that handled i18n by wrapping all strings intended to be seen by the user like this: XLate("Hello, all ") + (UBound(customerArr)-LBound(customerArr)) + XLate(" of you suckers!") For non-english users, that XLate function would then lookup the right localization for the given message. IIRC, any missing translation just returned the original english text. This was the first time I had actually dealt with i18n, and yet I was pretty certain that was a completely moronic approach (not that it was a stretch to believe: there wasn't much going on at main sequence techn...errmm...I mean at unnamed company, that wasn't completely moronic). I always felt it would be far better ("better" == "won't screw up everytime someone sneezes near the code" and "doesn't make certain things impossible to translate correctly") to have a statically-checked *identifier* that represented a particular message (and not message fragments, either). And then the user-facing string would be looked up via identifier and any appropriate values substituted in. A fairly obvious way to improve^Wfix the system, really. Ok, end of story. Your mention of gettext, and specifically a "gettext 'compiler'", does introduce a way that the same basic idea could be made to actually work: Obviously, the big problems with XLate above are: 1. No way to make sure all messages actually *are* translated (or to prune unused translations). 2. Changing the english text embedded in the code will automatically break all translations. 3. It fails to handle cases where two inserted values need to be rearranged. 4. If two messages happen to have a common message fragments, the fragments will always be translated the same even if their differing context dictates they be translated differently with named placeholders and passing the substitution data into xlate(). So those issues are alredy out of the way. By "gettext 'compiler'" I assume you're taking about some preprocessor. Probably appropriate for C/C++, but seems kinda sloppy, potentially error prone, and inefficient. Fortunately, D has kick-ass metaprogramming, so if you require all translations be compiled in (not sure how realistic that is in general), you could do: xlate!"Hello world" And then generate a compile-time error (or a mere notice) for all messages that are unhandled. Of course, if you're loading localizations at runtime, you could still do something similar and just throw a runtime error, or log a warning, etc., that lists all unhandled messages upon loading a localization. Or, hell, a hybrid approach: Compile-time errors for built-in localizations and runtime for the rest. Or generate a list of all messages when compiled with a special version(), which is then handed off to the translation team. All sorts of possibilities. That's all sufficient to solve Although there is still the other problem Andrei mentioned of over-eagerness (not an issue in the compile-time case, though).
Feb 20 2012
prev sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhtss8$12mq$3 digitalmars.com...
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages?
Maybe I've misunderstood your intent for this "Variant[string] info;" My understanding is that, for example, you want to replace this: ---------------------------------------------------- class Exception {} class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; } } ---------------------------------------------------- With this: ---------------------------------------------------- class Exception { Variant[string] info; } class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; info["fooName"] = fooName; info["fooID"] = fooID; info["didBarOccur"] = didBarOccur; } } ---------------------------------------------------- If so, then I don't see any usefulness of "Variant[string] info" other than to start treating exceptions like JS's abomination of an "object" (Or at least AS2's objects anyway - not 100% certain how much of AS2 is taken from JS). If not, then could you clarify what you meant? In either case, I am interested to hear in more detail how you see "Variant[string] info" being used to address i18n. I haven't really dealt with a lot of i18n myself.
Feb 20 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 20:12, Nick Sabalausky a écrit :
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhtss8$12mq$3 digitalmars.com...
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>   wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages?
Maybe I've misunderstood your intent for this "Variant[string] info;" My understanding is that, for example, you want to replace this: ---------------------------------------------------- class Exception {} class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; } } ---------------------------------------------------- With this: ---------------------------------------------------- class Exception { Variant[string] info; } class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; info["fooName"] = fooName; info["fooID"] = fooID; info["didBarOccur"] = didBarOccur; } } ---------------------------------------------------- If so, then I don't see any usefulness of "Variant[string] info" other than to start treating exceptions like JS's abomination of an "object" (Or at least AS2's objects anyway - not 100% certain how much of AS2 is taken from JS). If not, then could you clarify what you meant? In either case, I am interested to hear in more detail how you see "Variant[string] info" being used to address i18n. I haven't really dealt with a lot of i18n myself.
Why not use tupleof on the Exception instead of Variant[string] ?
Feb 20 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 08:23:05PM +0100, deadalnix wrote:
[...]
 Why not use tupleof on the Exception instead of Variant[string] ?
+1. Compile-time type-checking and runtime generic access to fields. Best of both worlds. Excellent idea! T -- Ruby is essentially Perl minus Wall.
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:38 PM, H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 08:23:05PM +0100, deadalnix wrote:
 [...]
 Why not use tupleof on the Exception instead of Variant[string] ?
+1. Compile-time type-checking and runtime generic access to fields. Best of both worlds. Excellent idea!
Doesn't scale. You need to literally write one catch handler for each exception type caught. Andrei
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:23 PM, deadalnix wrote:
 Why not use tupleof on the Exception instead of Variant[string] ?
The one-liner police shall be alerted. You can't use tupleof because that's compile-time information. Andrei
Feb 20 2012
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:jhu61p$1jpm$1 digitalmars.com...
 "Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
 news:jhtss8$12mq$3 digitalmars.com...
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] 
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages?
Maybe I've misunderstood your intent for this "Variant[string] info;" My understanding is that, for example, you want to replace this: ---------------------------------------------------- class Exception {} class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; } } ---------------------------------------------------- With this: ---------------------------------------------------- class Exception { Variant[string] info; } class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; info["fooName"] = fooName; info["fooID"] = fooID; info["didBarOccur"] = didBarOccur; } } ---------------------------------------------------- If so, then I don't see any usefulness of "Variant[string] info" other than to start treating exceptions like JS's abomination of an "object" (Or at least AS2's objects anyway - not 100% certain how much of AS2 is taken from JS). If not, then could you clarify what you meant? In either case, I am interested to hear in more detail how you see "Variant[string] info" being used to address i18n. I haven't really dealt with a lot of i18n myself.
Ok, I just saw your explanation here: http://forum.dlang.org/post/jhu1hk$1cb8$1 digitalmars.com And, IIUC, I assume that inside "stringTemplate()", you'd then access some table that roughly amounts to (yea, I know we don't have 2D AAs): string[string, Locale] i18nTable; i18nTable["FileNotFoundExcepton", English] = "File ${filename} not found"; Then stringTemplate() would look that up, find the string "File ${filename} not found" and essentially do: return "File "~e.info("filename")~" not found"; Although obviously not hardcoded like that. Is that right? In that case, I like the general idea, *but* why not just use reflection to access the members instead of essentially creating a JS-style "class" with AAs? That way we don't have to either A. throw away the benefits that class members have over AAs or B. violate DRY by duplicating field members in an AA.
Feb 20 2012
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:jhu7kj$1mae$1 digitalmars.com...
 "Nick Sabalausky" <a a.a> wrote in message 
 news:jhu61p$1jpm$1 digitalmars.com...
 "Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
 news:jhtss8$12mq$3 digitalmars.com...
 On 2/20/12 10:16 AM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:jhtq31$u8q$1 digitalmars.com...
 Again, I think this thread clarified we need the "Variant[string] 
 info;"
 member however we define the hierarchy.
I disagree. I don't see a need for that.
How would we address custom formatting of e.g. error messages?
Maybe I've misunderstood your intent for this "Variant[string] info;" My understanding is that, for example, you want to replace this: ---------------------------------------------------- class Exception {} class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; } } ---------------------------------------------------- With this: ---------------------------------------------------- class Exception { Variant[string] info; } class FooException { string fooName; int fooID; bool didBarOccur; this(string fooName, int fooID, bool didBarOccur) { this.fooName = fooName; this.fooID= fooID; this.didBarOccur= didBarOccur; info["fooName"] = fooName; info["fooID"] = fooID; info["didBarOccur"] = didBarOccur; } } ---------------------------------------------------- If so, then I don't see any usefulness of "Variant[string] info" other than to start treating exceptions like JS's abomination of an "object" (Or at least AS2's objects anyway - not 100% certain how much of AS2 is taken from JS). If not, then could you clarify what you meant? In either case, I am interested to hear in more detail how you see "Variant[string] info" being used to address i18n. I haven't really dealt with a lot of i18n myself.
Ok, I just saw your explanation here: http://forum.dlang.org/post/jhu1hk$1cb8$1 digitalmars.com And, IIUC, I assume that inside "stringTemplate()", you'd then access some table that roughly amounts to (yea, I know we don't have 2D AAs): string[string, Locale] i18nTable; i18nTable["FileNotFoundExcepton", English] = "File ${filename} not found"; Then stringTemplate() would look that up, find the string "File ${filename} not found" and essentially do: return "File "~e.info("filename")~" not found"; Although obviously not hardcoded like that. Is that right? In that case, I like the general idea, *but* why not just use reflection to access the members instead of essentially creating a JS-style "class" with AAs? That way we don't have to either A. throw away the benefits that class members have over AAs or B. violate DRY by duplicating field members in an AA.
Using reflection instead of "Variant[string] info" has another benefit: It allows the i18n table to *automatically* support any type, not just Exceptions. By contrast, with "Variant[string] info", a member has to be explicity (or via inheritance) added to every type that the i18n table wants to handle. So you're actally *creating* unnecessary cupling with i18n by using "Variant[string] info". If you replace "Variant[string] info" with reflection, then you've *completely* factored out i18n. Admittedly, if the i18n engine populates its tables at runtime, then the reflection would have to be runtime reflection, and I don't know offhand if we currently have that particular form of runtime reflection yet or not. But if not, then this is a good use-case for why it should get implemented sooner or later.
Feb 20 2012
prev sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 19:41:07 UTC, Nick Sabalausky 
wrote:
 Ok, I just saw your explanation here:
 http://forum.dlang.org/post/jhu1hk$1cb8$1 digitalmars.com

 And, IIUC, I assume that inside "stringTemplate()", you'd then 
 access some
 table that roughly amounts to (yea, I know we don't have 2D 
 AAs):

 string[string, Locale] i18nTable;
 i18nTable["FileNotFoundExcepton", English] = "File ${filename} 
 not found";

 Then stringTemplate() would look that up, find the string "File 
 ${filename}
 not found" and essentially do:

 return "File "~e.info("filename")~" not found";

 Although obviously not hardcoded like that.

 Is that right?

 In that case, I like the general idea, *but* why not just use 
 reflection to
 access the members instead of essentially creating a JS-style 
 "class" with
 AAs? That way we don't have to either A. throw away the 
 benefits that class
 members have over AAs or B. violate DRY by duplicating field 
 members in an
 AA.
Seconded. Reflection seems a much better solution for this. The i18n table would map (exception type) -> (language, format string) and the i18n formatter would use reflection to map field values to the format string. a format string would be e.g. "File {filename} not found" and the formatter would replace {filename} with e.class.getField("filename"). This does not require any modifications to the exception mechanism.
Feb 20 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 09:01:27PM +0100, foobar wrote:
 On Monday, 20 February 2012 at 19:41:07 UTC, Nick Sabalausky wrote:
[...]
In that case, I like the general idea, *but* why not just use
reflection to access the members instead of essentially creating a
JS-style "class" with AAs? That way we don't have to either A. throw
away the benefits that class members have over AAs or B. violate DRY
by duplicating field members in an AA.
Seconded. Reflection seems a much better solution for this. The i18n table would map (exception type) -> (language, format string) and the i18n formatter would use reflection to map field values to the format string. a format string would be e.g. "File {filename} not found" and the formatter would replace {filename} with e.class.getField("filename"). This does not require any modifications to the exception mechanism.
Unfortunately, I don't think D supports runtime reflection yet. At least, not to the point we need (accessing field values of a general Object). Although, as some have said, it's possible to work around this by using .tupleof, which is still a better solution than Variant[string], because at least you'd have compile-time type checking. T -- The peace of mind---from knowing that viruses which exploit Microsoft system vulnerabilities cannot touch Linux---is priceless. -- Frustrated system administrator.
Feb 20 2012
parent "Nick Sabalausky" <a a.a> writes:
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote in message 
news:mailman.710.1329769408.20196.digitalmars-d puremagic.com...
 Unfortunately, I don't think D supports runtime reflection yet. At
 least, not to the point we need (accessing field values of a general
 Object).

 Although, as some have said, it's possible to work around this by using
 .tupleof, which is still a better solution than Variant[string], because
 at least you'd have compile-time type checking.
...And compile-time *existence* checking.
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 2:01 PM, foobar wrote:
 Seconded. Reflection seems a much better solution for this.
Absolutely.
 The i18n table would map (exception type) -> (language, format string)
 and the i18n formatter would use reflection to map field values to the
 format string.
 a format string would be e.g. "File {filename} not found" and the
 formatter would replace {filename} with e.class.getField("filename").

 This does not require any modifications to the exception mechanism.
Yah, but the reflection engine would have to present RTTI in a uniform manner... such as, Variant[string]. Bane of my life: I'm always right :o). Andrei
Feb 20 2012
next sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 21:02:21 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 2:01 PM, foobar wrote:
 Seconded. Reflection seems a much better solution for this.
Absolutely.
 The i18n table would map (exception type) -> (language, format 
 string)
 and the i18n formatter would use reflection to map field 
 values to the
 format string.
 a format string would be e.g. "File {filename} not found" and 
 the
 formatter would replace {filename} with 
 e.class.getField("filename").

 This does not require any modifications to the exception 
 mechanism.
Yah, but the reflection engine would have to present RTTI in a uniform manner... such as, Variant[string]. Bane of my life: I'm always right :o). Andrei
Unfortunately you aren't :) The Variant[string] approach causes coupling between two separate concerns: i18n and error handling. Reflection does *not* cause such coupling. More over, Reflection is a general language mechanism with many benefits such as allowing the user to use the i18n library with other non exception types. Lastly, good RTTI is hardly a simple Variant[string]. Java for instance has pretty good RTTI API whereas the c++ one is horrible (and incomplete). Conclusion - *not* the same thing!
Feb 20 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 4:46 PM, foobar wrote:
 On Monday, 20 February 2012 at 21:02:21 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 2:01 PM, foobar wrote:
 Seconded. Reflection seems a much better solution for this.
Absolutely.
 The i18n table would map (exception type) -> (language, format string)
 and the i18n formatter would use reflection to map field values to the
 format string.
 a format string would be e.g. "File {filename} not found" and the
 formatter would replace {filename} with e.class.getField("filename").

 This does not require any modifications to the exception mechanism.
Yah, but the reflection engine would have to present RTTI in a uniform manner... such as, Variant[string]. Bane of my life: I'm always right :o). Andrei
Unfortunately you aren't :) The Variant[string] approach causes coupling between two separate concerns: i18n and error handling.
Not at all. It simply exposes information for any rendering purposes. In fact I imagine a simple renderer would just display to the developer the message formatted as a simple table with <name, value> for each property.
 Reflection does *not* cause such
 coupling. More over, Reflection is a general language mechanism with
 many benefits such as allowing the user to use the i18n library with
 other non exception types.
Sure. The question is whether reflection is enough. Juan made the case that dynamic paths would want to enhance an exception with additional information. Using reflection for that would be forced.
 Lastly, good RTTI is hardly a simple Variant[string]. Java for instance
 has pretty good RTTI API whereas the c++ one is horrible (and incomplete).

 Conclusion - *not* the same thing!
Yah, full-blown RTTI would be a fair amount more. Andrei
Feb 20 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 4:46 PM, foobar wrote:
 On Monday, 20 February 2012 at 21:02:21 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 2:01 PM, foobar wrote:
 Seconded. Reflection seems a much better solution for this.
Absolutely.
 The i18n table would map (exception type) -> (language, format string)
 and the i18n formatter would use reflection to map field values to the
 format string.
 a format string would be e.g. "File {filename} not found" and the
 formatter would replace {filename} with e.class.getField("filename").

 This does not require any modifications to the exception mechanism.
Yah, but the reflection engine would have to present RTTI in a uniform manner... such as, Variant[string]. Bane of my life: I'm always right :o). Andrei
Unfortunately you aren't :)
Esprit d'escalier! "Bane of my life: nobody admits I'm always right." Damn I wish I'd come up with that a minute ago before submitting. Andrei
Feb 20 2012
prev sibling parent "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhuccs$1tuf$4 digitalmars.com...
 On 2/20/12 2:01 PM, foobar wrote:
 Seconded. Reflection seems a much better solution for this.
Absolutely.
 The i18n table would map (exception type) -> (language, format string)
 and the i18n formatter would use reflection to map field values to the
 format string.
 a format string would be e.g. "File {filename} not found" and the
 formatter would replace {filename} with e.class.getField("filename").

 This does not require any modifications to the exception mechanism.
Yah, but the reflection engine would have to present RTTI in a uniform manner... such as, Variant[string]. Bane of my life: I'm always right :o).
It's easy to always be right when you twist your argument after-the-fact to match what was eventually agreed.
Feb 20 2012
prev sibling next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
On Feb 20, 2012, at 11:12 AM, Nick Sabalausky wrote:
=20
 ----------------------------------------------------
 class Exception
 {
    Variant[string] info;
 }
 class FooException
 {
    string fooName;
    int fooID;
    bool didBarOccur;
=20
    this(string fooName, int fooID, bool didBarOccur)
    {
        this.fooName =3D fooName;
        this.fooID=3D fooID;
        this.didBarOccur=3D didBarOccur;
=20
        info["fooName"] =3D fooName;
        info["fooID"] =3D fooID;
        info["didBarOccur"] =3D didBarOccur;
    }
 }
 ----------------------------------------------------
=20
 If so, then I don't see any usefulness of "Variant[string] info" other =
than=20
 to start treating exceptions like JS's abomination of an "object" (Or =
at=20
 least AS2's objects anyway - not 100% certain how much of AS2 is taken =
from=20
 JS). If not, then could you clarify what you meant?
=20
 In either case, I am interested to hear in more detail how you see=20
 "Variant[string] info" being used to address i18n. I haven't really =
dealt=20
 with a lot of i18n myself.
Localized error messages are typically generated by a localization team = and placed in some sort of a lookup table, indexed by language and error = code. Say the code is roughly like this: displayLocalizedError(Exception e) { auto t =3D loadTable(e.info["lang"]); // info["lang"] returns "ja" = for japanese, etc. string m =3D t.findMessage(typeid(e).toString); writeln(buildLocalizedMessage(m, e.info)); } Where m (in english) may be something like: "Saving to {LocationType} {!LocationName} failed because the destination = is full." The message is parsed and when {LocalizationType} is encountered, the = parser knows to replace it with another localized string indexed by = "LocationType". Then {!LocationName} is replaced by = e.info["LocationName"], which is specific to the actual error that = occurred. In essence, each line that has to be localized has a monetary = (and time) cost for doing so, so error lines are reused when possible. = Then specifics that may or may not themselves be localized are = potentially pasted in to generate the final message.=
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:41 PM, Sean Kelly wrote:
 Localized error messages are typically generated by a localization
 team and placed in some sort of a lookup table, indexed by language
 and error code.  Say the code is roughly like this:

 displayLocalizedError(Exception e) { auto t =
 loadTable(e.info["lang"]); // info["lang"] returns "ja" for japanese,
 etc. string m = t.findMessage(typeid(e).toString);
 writeln(buildLocalizedMessage(m, e.info)); }
I'd amend your example a bit as follows: displayLocalizedError(Exception e) { auto t = loadTable(g_lang); // g_lang contains "ja" for japanese, etc. string m = t.findMessage(typeid(e).toString); writeln(buildLocalizedMessage(m, e.info)); } I mean the exception has no notion of "lang"uage.
 Where m (in english) may be something like:

 "Saving to {LocationType} {!LocationName} failed because the
 destination is full."

 The message is parsed and when {LocalizationType} is encountered, the
 parser knows to replace it with another localized string indexed by
 "LocationType".  Then {!LocationName} is replaced by
 e.info["LocationName"], which is specific to the actual error that
 occurred.  In essence, each line that has to be localized has a
 monetary (and time) cost for doing so, so error lines are reused when
 possible.  Then specifics that may or may not themselves be localized
 are potentially pasted in to generate the final message.
Yup, yup, and yup. Andrei
Feb 20 2012
parent Sean Kelly <sean invisibleduck.org> writes:
On Feb 20, 2012, at 12:54 PM, Andrei Alexandrescu wrote:

 On 2/20/12 1:41 PM, Sean Kelly wrote:
 Localized error messages are typically generated by a localization
 team and placed in some sort of a lookup table, indexed by language
 and error code.  Say the code is roughly like this:
=20
 displayLocalizedError(Exception e) { auto t =3D
 loadTable(e.info["lang"]); // info["lang"] returns "ja" for japanese,
 etc. string m =3D t.findMessage(typeid(e).toString);
 writeln(buildLocalizedMessage(m, e.info)); }
=20 I'd amend your example a bit as follows: =20 displayLocalizedError(Exception e) { auto t =3D loadTable(g_lang); // g_lang contains "ja" for japanese, =
etc.
    string m =3D t.findMessage(typeid(e).toString);
    writeln(buildLocalizedMessage(m, e.info));
 }
=20
 I mean the exception has no notion of "lang"uage.
I wrote it that way because the systems I work on don't have a global = concept of language. It's request-specific. I wasn't sure how to = succinctly represent this in the code, so I just dumped the field in the = lookup table.=
Feb 20 2012
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-20 20:12, Nick Sabalausky wrote:
 If so, then I don't see any usefulness of "Variant[string] info" other than
 to start treating exceptions like JS's abomination of an "object" (Or at
 least AS2's objects anyway - not 100% certain how much of AS2 is taken from
 JS). If not, then could you clarify what you meant?

 In either case, I am interested to hear in more detail how you see
 "Variant[string] info" being used to address i18n. I haven't really dealt
 with a lot of i18n myself.
Internationalization in Ruby on Rails works something like this: I18n.locale = :se This will then, at an appropriate time load, a YAML file containing the translations, defaults too <rails_app>/config/<locale>.yml. The YAML file can look something like this: se: customer: address: first_name: Frnamn last_name: Efternamn Then to use the translation, it looks like this: I18n.translate("customer.address.first_name") Results in "Frnamn". It's also possible to pass variables to the translation: se: product: price: %{price} kr I18n.translate("product.price", :price => 300) ":price => 300" is a hash map-literal. -- /Jacob Carlborg
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 3:08 PM, Jacob Carlborg wrote:
 On 2012-02-20 20:12, Nick Sabalausky wrote:
 If so, then I don't see any usefulness of "Variant[string] info" other
 than
 to start treating exceptions like JS's abomination of an "object" (Or at
 least AS2's objects anyway - not 100% certain how much of AS2 is taken
 from
 JS). If not, then could you clarify what you meant?

 In either case, I am interested to hear in more detail how you see
 "Variant[string] info" being used to address i18n. I haven't really dealt
 with a lot of i18n myself.
Internationalization in Ruby on Rails works something like this: I18n.locale = :se This will then, at an appropriate time load, a YAML file containing the translations, defaults too <rails_app>/config/<locale>.yml. The YAML file can look something like this: se: customer: address: first_name: Frnamn last_name: Efternamn Then to use the translation, it looks like this: I18n.translate("customer.address.first_name") Results in "Frnamn". It's also possible to pass variables to the translation: se: product: price: %{price} kr I18n.translate("product.price", :price => 300) ":price => 300" is a hash map-literal.
This uses very nicely Ruby's built-in, elegant dynamic type information. The closest thing in D would use a hash Variant[string]. Andrei
Feb 20 2012
prev sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
On Feb 20, 2012, at 7:49 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani.=
org> wrote:
=20
 Also, I think we can do better than defining the boilerplate constructor (=
see e.g. https://github.com/D-Programming-Language/phobos/pull/439). It's ju= st a function. Consider:
=20
 // this goes in the stdlib
 void raise(ConcreteException)(string message, Throwable t =3D null, string=
f =3D __FILE__, size_t l =3D __LINE__)
 {
  auto r =3D new ConcreteException;
  r.message =3D message;
  r.file =3D f;
  r.line =3D l;
  r.next =3D t;
  throw r;
 }
=20
 class AcmeException : Exception {}
=20
 Now whenever you want to raise AcmeException, you say raise!AcmeException(=
"message"). Also, raise may accept additional data that fills the Variant[st= ring]. That makes exception definitions one-liners.=20 What is gained here over the current approach: throw new AcmeException("message"); Just eliminate the need for the ctor definition in the exception class?=
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 10:31 AM, Sean Kelly wrote:
 On Feb 20, 2012, at 7:49 AM, Andrei
Alexandrescu<SeeWebsiteForEmail erdani.org>  wrote:
 Also, I think we can do better than defining the boilerplate constructor (see
e.g. https://github.com/D-Programming-Language/phobos/pull/439). It's just a
function. Consider:

 // this goes in the stdlib
 void raise(ConcreteException)(string message, Throwable t = null, string f =
__FILE__, size_t l = __LINE__)
 {
   auto r = new ConcreteException;
   r.message = message;
   r.file = f;
   r.line = l;
   r.next = t;
   throw r;
 }

 class AcmeException : Exception {}

 Now whenever you want to raise AcmeException, you say
raise!AcmeException("message"). Also, raise may accept additional data that
fills the Variant[string]. That makes exception definitions one-liners.
What is gained here over the current approach: throw new AcmeException("message"); Just eliminate the need for the ctor definition in the exception class?
Also possibly the definition of other constructors that set state. It's simple factorization. Andrei
Feb 20 2012
parent reply Mafi <mafi example.org> writes:
Am 20.02.2012 17:41, schrieb Andrei Alexandrescu:
 On 2/20/12 10:31 AM, Sean Kelly wrote:
 On Feb 20, 2012, at 7:49 AM, Andrei
 Alexandrescu<SeeWebsiteForEmail erdani.org> wrote:
 Also, I think we can do better than defining the boilerplate
 constructor (see e.g.
 https://github.com/D-Programming-Language/phobos/pull/439). It's just
 a function. Consider:

 // this goes in the stdlib
 void raise(ConcreteException)(string message, Throwable t = null,
 string f = __FILE__, size_t l = __LINE__)
 {
 auto r = new ConcreteException;
 r.message = message;
 r.file = f;
 r.line = l;
 r.next = t;
 throw r;
 }

 class AcmeException : Exception {}

 Now whenever you want to raise AcmeException, you say
 raise!AcmeException("message"). Also, raise may accept additional
 data that fills the Variant[string]. That makes exception definitions
 one-liners.
What is gained here over the current approach: throw new AcmeException("message"); Just eliminate the need for the ctor definition in the exception class?
Also possibly the definition of other constructors that set state. It's simple factorization. Andrei
If it's supposed to be simple factorization, then you should replace "throw r" with "return r". Then the name of that function doesn't make much sense anymore. But then you can better search for throw in user code and the stack traces aren't obfuscated anymore. throw createEx!AcmeException("...."); Mafi
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:08 AM, Mafi wrote:
 If it's supposed to be simple factorization, then you should replace
 "throw r" with "return r". Then the name of that function doesn't make
 much sense anymore. But then you can better search for throw in user
 code and the stack traces aren't obfuscated anymore.

 throw createEx!AcmeException("....");
I think that's a great idea, thanks. Andrei
Feb 20 2012
parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 17:12:17 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 11:08 AM, Mafi wrote:
 If it's supposed to be simple factorization, then you should 
 replace
 "throw r" with "return r". Then the name of that function 
 doesn't make
 much sense anymore. But then you can better search for throw 
 in user
 code and the stack traces aren't obfuscated anymore.

 throw createEx!AcmeException("....");
I think that's a great idea, thanks. Andrei
I fail to see the point in this. Why is the above better than throw AcmeException("...."); If you want to avoid boilerplate code in the definition of AcmeException, this can be better accomplished with a mixin.
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 11:32 AM, foobar wrote:
 On Monday, 20 February 2012 at 17:12:17 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 11:08 AM, Mafi wrote:
 If it's supposed to be simple factorization, then you should replace
 "throw r" with "return r". Then the name of that function doesn't make
 much sense anymore. But then you can better search for throw in user
 code and the stack traces aren't obfuscated anymore.

 throw createEx!AcmeException("....");
I think that's a great idea, thanks. Andrei
I fail to see the point in this. Why is the above better than throw AcmeException("...."); If you want to avoid boilerplate code in the definition of AcmeException, this can be better accomplished with a mixin.
The advantage is that e.g. the compiler can see that flow ends at throw. Other languages have a "none" type that function may return to signal they never end. Andrei
Feb 20 2012
parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 17:47:35 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 11:32 AM, foobar wrote:
 On Monday, 20 February 2012 at 17:12:17 UTC, Andrei 
 Alexandrescu wrote:
 On 2/20/12 11:08 AM, Mafi wrote:
 If it's supposed to be simple factorization, then you should 
 replace
 "throw r" with "return r". Then the name of that function 
 doesn't make
 much sense anymore. But then you can better search for throw 
 in user
 code and the stack traces aren't obfuscated anymore.

 throw createEx!AcmeException("....");
I think that's a great idea, thanks. Andrei
I fail to see the point in this. Why is the above better than throw AcmeException("...."); If you want to avoid boilerplate code in the definition of AcmeException, this can be better accomplished with a mixin.
The advantage is that e.g. the compiler can see that flow ends at throw. Other languages have a "none" type that function may return to signal they never end. Andrei
I meant - what's the benefit of: throw createEx!AcmeException("...."); vs. throw AcmeException("...."); As far as I can see, the former has no benefits over the simpler latter option.
Feb 20 2012
next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"foobar" <foo bar.com> wrote in message 
news:uphvtoqkvtshnzlqoaus forum.dlang.org...
 I meant -
 what's the benefit of:
 throw createEx!AcmeException("....");
 vs.
 throw new AcmeException("....");
Fixed.
 As far as I can see, the former has no benefits over the simpler latter 
 option.
The benefit is that you don't have to create any boilerplate ctors in the definition of AcmeException. *However*, I think that: 1. That's an insufficient improvement to justify breaking the ultra-common "throw new NameOfException(args)" idiom. 2. The solution fails to cover the *entire* scope of the *real* problem: Classes that need to write boilerplate ctors which merely forward to the base class's ctors. This issue is *not* limited to Exceptions, but Andrei's proposes solution *only* covers the case with Exceptions. A better solution has already been proposed: class AcmeException : Exception { mixin inheritCtors!(); // Actual name open for bikeshedding } Now, yes, this *is* admittedly more boilerplate than: class AcmeException : Exception {} However, it's superior overall, because: 1. The solution is *contained* to the author of the exception's class. It's *not* a solution that leeks out and infects user code. 2. It solves the *whole* problem in the general case, not just for Exceptions. And you know what? Even if it's still deemed too much bolierplate, we can still just do this: mixin( createInheritedClass("AcmeException", "Exception") ); Or maybe this: // Second param is optional. // Not sure if this can be a template mixin or has to be string mixin. mixin createException!("AcmeException", "Exception");
Feb 20 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 02/20/2012 09:17 PM, Nick Sabalausky wrote:
      // Second param is optional.
      // Not sure if this can be a template mixin or has to be string mixin.
      mixin createException!("AcmeException", "Exception");
It can be a template mixin that mixes in a string mixin declaration.
Feb 20 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 03:17:44PM -0500, Nick Sabalausky wrote:
[...]
 2. The solution fails to cover the *entire* scope of the *real* problem: 
 Classes that need to write boilerplate ctors which merely forward to the 
 base class's ctors.  This issue is *not* limited to Exceptions, but Andrei's 
 proposes solution *only* covers the case with Exceptions.
 
 A better solution has already been proposed:
 
     class AcmeException : Exception
     {
         mixin inheritCtors!();  // Actual name open for bikeshedding
     }
Somewhere in this giant thread, somebody has proposed that there should be language support for implicitly inheriting base class ctors. That is, if you write: class Base { this(string, int, float) { ... } } class Derived : Base { } then the compiler will automatically insert this into Derived: this(string s, int i, float f) { super(s, i, f); } I think this is a very useful mechanism that should be added to D. It will eliminate a lot of ctor boilerplate that you have to put in derived classes (not just exceptions, but in general). For example, if you have: class Base { this(string) {...} this(int) {...} this(float) {...} } then currently, *every single derived class* must write wrappers to forward ctor arguments to each of those ctors, if they want to provide the same construction API as the base class. With the proposed enhancement, you'd just "inherit" all of the ctors automatically. (Not in the strict sense of inherit, but the compiler will create forwarding ctors for you automatically.) T -- Жил-был король когда-то, при нём блоха жила.
Feb 20 2012
parent reply kennytm <kennytm gmail.com> writes:
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:
 On Mon, Feb 20, 2012 at 03:17:44PM -0500, Nick Sabalausky wrote:
 [...]
 2. The solution fails to cover the *entire* scope of the *real* problem: 
 Classes that need to write boilerplate ctors which merely forward to the 
 base class's ctors.  This issue is *not* limited to Exceptions, but Andrei's 
 proposes solution *only* covers the case with Exceptions.
 
 A better solution has already been proposed:
 
     class AcmeException : Exception
     {
         mixin inheritCtors!();  // Actual name open for bikeshedding
     }
Somewhere in this giant thread, somebody has proposed that there should be language support for implicitly inheriting base class ctors. That is, if you write: class Base { this(string, int, float) { ... } } class Derived : Base { } then the compiler will automatically insert this into Derived: this(string s, int i, float f) { super(s, i, f); } I think this is a very useful mechanism that should be added to D. It will eliminate a lot of ctor boilerplate that you have to put in derived classes (not just exceptions, but in general). For example, if you have: class Base { this(string) {...} this(int) {...} this(float) {...} } then currently, *every single derived class* must write wrappers to forward ctor arguments to each of those ctors, if they want to provide the same construction API as the base class. With the proposed enhancement, you'd just "inherit" all of the ctors automatically. (Not in the strict sense of inherit, but the compiler will create forwarding ctors for you automatically.) T
Note that this should work: class Derived : Base { this(T...)(T values) { super(values); } }
Feb 20 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 12:44:11AM +0000, kennytm wrote:
 "H. S. Teoh" <hsteoh quickfur.ath.cx> wrote:
 On Mon, Feb 20, 2012 at 03:17:44PM -0500, Nick Sabalausky wrote:
[...]
 A better solution has already been proposed:
 
     class AcmeException : Exception
     {
         mixin inheritCtors!();  // Actual name open for bikeshedding
     }
Somewhere in this giant thread, somebody has proposed that there should be language support for implicitly inheriting base class ctors. That is, if you write: class Base { this(string, int, float) { ... } } class Derived : Base { } then the compiler will automatically insert this into Derived: this(string s, int i, float f) { super(s, i, f); }
[...]
 Note that this should work:
 
 class Derived : Base {
   this(T...)(T values) { 
     super(values);
   }
 }
You're right, we *could* just put this in a template, which I believe is what inheritCtors() does: mixin template inheritCtors() { this(T...)(T values) { super(values); } } class Derived1 : Base { inheritCtors!(); } class Derived2 : Base { inheritCtors!(); } ... etc. T -- Debian GNU/Linux: Cray on your desktop.
Feb 20 2012
prev sibling next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 21:17, Nick Sabalausky a écrit :
 *However*, I think that:

 1. That's an insufficient improvement to justify breaking the ultra-common
 "throw new NameOfException(args)" idiom.

 2. The solution fails to cover the *entire* scope of the *real* problem:
 Classes that need to write boilerplate ctors which merely forward to the
 base class's ctors.  This issue is *not* limited to Exceptions, but Andrei's
 proposes solution *only* covers the case with Exceptions.

 A better solution has already been proposed:

      class AcmeException : Exception
      {
          mixin inheritCtors!();  // Actual name open for bikeshedding
      }

 Now, yes, this *is* admittedly more boilerplate than:

      class AcmeException : Exception {}
On IRC we discussed the following solution : class with no defined ctor get default ctor forwarding argument to parent's ctor. If one is defined, this default behaviour disapear. People were entusiasts about that. But I think we should discuss that in another thread.
Feb 20 2012
prev sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 20:18:58 UTC, Nick Sabalausky 
wrote:
 "foobar" <foo bar.com> wrote in message
 news:uphvtoqkvtshnzlqoaus forum.dlang.org...
 I meant -
 what's the benefit of:
 throw createEx!AcmeException("....");
 vs.
 throw new AcmeException("....");
Fixed.
Huh? <confused>
 As far as I can see, the former has no benefits over the 
 simpler latter option.
The benefit is that you don't have to create any boilerplate ctors in the definition of AcmeException. *However*, I think that: 1. That's an insufficient improvement to justify breaking the ultra-common "throw new NameOfException(args)" idiom. 2. The solution fails to cover the *entire* scope of the *real* problem: Classes that need to write boilerplate ctors which merely forward to the base class's ctors. This issue is *not* limited to Exceptions, but Andrei's proposes solution *only* covers the case with Exceptions. A better solution has already been proposed: class AcmeException : Exception { mixin inheritCtors!(); // Actual name open for bikeshedding } Now, yes, this *is* admittedly more boilerplate than: class AcmeException : Exception {} However, it's superior overall, because: 1. The solution is *contained* to the author of the exception's class. It's *not* a solution that leeks out and infects user code. 2. It solves the *whole* problem in the general case, not just for Exceptions. And you know what? Even if it's still deemed too much bolierplate, we can still just do this: mixin( createInheritedClass("AcmeException", "Exception") ); Or maybe this: // Second param is optional. // Not sure if this can be a template mixin or has to be string mixin. mixin createException!("AcmeException", "Exception");
I completely agree with the above. Clearly Andrei's template solution here is insufficient and the bigger issue is of course the pollution of user code. I just want to add to the above valid points one comment: instead of the proposed inheritCtors D could e perhaps modified to make ctors more uniform with other methods. ctors could be automatically inherited if sub class does not define its own ctors and does not add new fields. the above would simply become: class AcmeException : Exception {} // inherits super ctors automatically
Feb 20 2012
parent "Nick Sabalausky" <a a.a> writes:
"foobar" <foo bar.com> wrote in message 
news:rsfpoesjcefsiwxoxgzm forum.dlang.org...
 On Monday, 20 February 2012 at 20:18:58 UTC, Nick Sabalausky wrote:
 "foobar" <foo bar.com> wrote in message
 news:uphvtoqkvtshnzlqoaus forum.dlang.org...
 I meant -
 what's the benefit of:
 throw createEx!AcmeException("....");
 vs.
 throw new AcmeException("....");
Fixed.
Huh? <confused>
You forgot the "new" in the second example. I make that mistake in my own code all the time :)
 I just want to add to the above valid points one comment:
 instead of the proposed inheritCtors D could e perhaps modified to make 
 ctors more uniform with other methods. ctors could be automatically 
 inherited if sub class does not define its own ctors and does not add new 
 fields.

 the above would simply become:

 class AcmeException : Exception {} // inherits super ctors automatically
I often forget that doesn't already happen. I think what confuses me is that non-inherited classes implicitly define "this(){}" if there's no other ctors (Which is very handy though. Haxe doesn't do that which I always find irritating).
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 1:04 PM, foobar wrote:
 On Monday, 20 February 2012 at 17:47:35 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 11:32 AM, foobar wrote:
 On Monday, 20 February 2012 at 17:12:17 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 11:08 AM, Mafi wrote:
 If it's supposed to be simple factorization, then you should replace
 "throw r" with "return r". Then the name of that function doesn't make
 much sense anymore. But then you can better search for throw in user
 code and the stack traces aren't obfuscated anymore.

 throw createEx!AcmeException("....");
I think that's a great idea, thanks. Andrei
I fail to see the point in this. Why is the above better than throw AcmeException("...."); If you want to avoid boilerplate code in the definition of AcmeException, this can be better accomplished with a mixin.
The advantage is that e.g. the compiler can see that flow ends at throw. Other languages have a "none" type that function may return to signal they never end. Andrei
I meant - what's the benefit of: throw createEx!AcmeException("...."); vs. throw AcmeException("...."); As far as I can see, the former has no benefits over the simpler latter option.
That's simply a workaround for non-inherited constructors - nothing more should be read into it. Andrei
Feb 20 2012
parent "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhuaue$1rj5$1 digitalmars.com...
 On 2/20/12 1:04 PM, foobar wrote:
 On Monday, 20 February 2012 at 17:47:35 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 11:32 AM, foobar wrote:
 On Monday, 20 February 2012 at 17:12:17 UTC, Andrei Alexandrescu wrote:
 On 2/20/12 11:08 AM, Mafi wrote:
 If it's supposed to be simple factorization, then you should replace
 "throw r" with "return r". Then the name of that function doesn't 
 make
 much sense anymore. But then you can better search for throw in user
 code and the stack traces aren't obfuscated anymore.

 throw createEx!AcmeException("....");
I think that's a great idea, thanks. Andrei
I fail to see the point in this. Why is the above better than throw AcmeException("...."); If you want to avoid boilerplate code in the definition of AcmeException, this can be better accomplished with a mixin.
The advantage is that e.g. the compiler can see that flow ends at throw. Other languages have a "none" type that function may return to signal they never end. Andrei
I meant - what's the benefit of: throw createEx!AcmeException("...."); vs. throw AcmeException("...."); As far as I can see, the former has no benefits over the simpler latter option.
That's simply a workaround for non-inherited constructors - nothing more should be read into it.
The issue is that it's an ugly and unnecessary workaround. A couple alternates have been suggested.
Feb 20 2012
prev sibling next sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 07:10:39 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 12:44 AM, foobar wrote:
 I just died a little reading this. Are you suggesting that in 
 order
 to handle IO exceptions I need to: try { ...whatever... } catch
 (PackageException!"std.io") {...} } catch
 (PackageException!"tango.io") {...} } catch
 (PackageException!"otherLib.io") {...} ...

 What the hell is wrong with just using an IOException?
There's nothing wrong, but there's a possible misunderstanding. If tango.io and otherLib.io cooperate with std, then they'd originate exceptions in std.io (as opposed to their own). Do note that the issue is exactly the same if libraries use IOException - they all must agree on using the same nomenclature, whether it's called PackageException!"std.io" or IOException.
The above is patently wrong. Are you suggesting that tango.io and otherLib.io need to depend on Phobos IO?? If so, that removes the benefits of using 3rd party libraries. If that's not your intention (and I really hope it isn't!) than IOException must be defined in a *separate* module that tango can depend on.
 ModuleException and PackageException have one important thing 
 going for
 them: they automate away a good amount of boilerplate, which 
 makes them
 interesting for me to look at, and worth sharing as long as 
 we're
 brainstorming. The associated issues as clear as the advantages.
 Probably ModuleException is too specific to be interesting, but
 PackageException seems useful.
Per above, your suggestion actually *adds* boilerplate since you now want me to use PackageException!"std.io" instead of just IOException.
 AS Nick wrote, it seems you have a complete lack of 
 understanding of
 how exceptions work which is unsurprising coming from a c++ 
 expert.
Always eager to learn (so please come with all you've got), but quite honest I hope in a way you're exaggerating, seeing as a lot of the stuff I authored for D (the scope statement, exception chaining, a full chapter in TDPL) would be completely broken.
"even a broken clock shows the right time twice a day"
 Also, this entire discussion you started about "improving" 
 exceptions
 looks to me like a combination of NIH syndrome sparkled with 
 heavy
 doses of premature optimization.
What is being optimized here? Thanks, Andrei
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. [Meta] side-note: It's extremely irritating when you demand utmost pedantic reasoning from others while you often answer without providing such pedantic reasoning yourself or worse answer with a single word posts. That shows a complete lack of respect for others. You seem to be of high regard for yourself which is not justified at all given this attitude.
Feb 20 2012
next sibling parent "John Campbell" <je_campbell gmail.com> writes:
On Monday, 20 February 2012 at 09:01:18 UTC, foobar wrote:
...
 AS Nick wrote, it seems you have a complete lack of 
 understanding of
 how exceptions work which is unsurprising coming from a c++ 
 expert.
...
 "even a broken clock shows the right time twice a day"
...
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.

 [Meta] side-note:
 It's extremely irritating when you demand utmost pedantic 
 reasoning from others while you often answer without providing 
 such pedantic reasoning yourself or worse answer with a single 
 word posts. That shows a complete lack of respect for others. 
 You seem to be of high regard for yourself which is not 
 justified at all given this attitude.
Please calm down and don't be so rude. Andrei doesn't have a clue how exceptions work? He just wants to apply his template schtick to everything? Give me a break. I find this discussion rather interesting (and I look forward to hearing Andrei's thoughts on H. S. Teoh's condition handler idea), but I think if all you want is to attack the idea of even discussing this, your voice has already been heard. John
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 3:01 AM, foobar wrote:
 On Monday, 20 February 2012 at 07:10:39 UTC, Andrei Alexandrescu
 wrote:
 On 2/20/12 12:44 AM, foobar wrote:
 I just died a little reading this. Are you suggesting that in
 order to handle IO exceptions I need to: try { ...whatever... }
 catch (PackageException!"std.io") {...} } catch
 (PackageException!"tango.io") {...} } catch
 (PackageException!"otherLib.io") {...} ...

 What the hell is wrong with just using an IOException?
There's nothing wrong, but there's a possible misunderstanding. If tango.io and otherLib.io cooperate with std, then they'd originate exceptions in std.io (as opposed to their own). Do note that the issue is exactly the same if libraries use IOException - they all must agree on using the same nomenclature, whether it's called PackageException!"std.io" or IOException.
The above is patently wrong. Are you suggesting that tango.io and otherLib.io need to depend on Phobos IO?? If so, that removes the benefits of using 3rd party libraries. If that's not your intention (and I really hope it isn't!) than IOException must be defined in a *separate* module that tango can depend on.
Actually that just shuffles the matter around. Any setup does demand that some library (in this case most probably the standard library) will be a dependency knot because it defines the hierarchy that others should use.
 [Meta] side-note: It's extremely irritating when you demand utmost
 pedantic reasoning from others while you often answer without
 providing such pedantic reasoning yourself or worse answer with a
 single word posts. That shows a complete lack of respect for others.
 You seem to be of high regard for yourself which is not justified at
 all given this attitude.
When giving brief answers I was trying to maximize throughput in a couple of cases when the meaning was obvious from the context. Otherwise I do my best to elaborate my points. But I see how that can be irritating, I won't continue it. Andrei
Feb 20 2012
parent reply "foobar" <foo bar.com> writes:
On Monday, 20 February 2012 at 15:50:08 UTC, Andrei Alexandrescu 
wrote:
 On 2/20/12 3:01 AM, foobar wrote:
 On Monday, 20 February 2012 at 07:10:39 UTC, Andrei 
 Alexandrescu
 wrote:
 On 2/20/12 12:44 AM, foobar wrote:
 I just died a little reading this. Are you suggesting that in
 order to handle IO exceptions I need to: try { 
 ...whatever... }
 catch (PackageException!"std.io") {...} } catch
 (PackageException!"tango.io") {...} } catch
 (PackageException!"otherLib.io") {...} ...

 What the hell is wrong with just using an IOException?
There's nothing wrong, but there's a possible misunderstanding. If tango.io and otherLib.io cooperate with std, then they'd originate exceptions in std.io (as opposed to their own). Do note that the issue is exactly the same if libraries use IOException - they all must agree on using the same nomenclature, whether it's called PackageException!"std.io" or IOException.
The above is patently wrong. Are you suggesting that tango.io and otherLib.io need to depend on Phobos IO?? If so, that removes the benefits of using 3rd party libraries. If that's not your intention (and I really hope it isn't!) than IOException must be defined in a *separate* module that tango can depend on.
Actually that just shuffles the matter around. Any setup does demand that some library (in this case most probably the standard library) will be a dependency knot because it defines the hierarchy that others should use.
Not accurate. A 3rd party library that want to be compatible will no doubt depend on the standard library's _exception hierarchy_ but that does *not* mean it should depend on the parallel functionality in the standard library. Per our example with IO, if I use tango.io I certainly do not want my application code to include redundantly both std.io and tango.io. I wanted to use tango.io as an *alternative* to std.io.
 [Meta] side-note: It's extremely irritating when you demand 
 utmost
 pedantic reasoning from others while you often answer without
 providing such pedantic reasoning yourself or worse answer 
 with a
 single word posts. That shows a complete lack of respect for 
 others.
 You seem to be of high regard for yourself which is not 
 justified at
 all given this attitude.
When giving brief answers I was trying to maximize throughput in a couple of cases when the meaning was obvious from the context. Otherwise I do my best to elaborate my points. But I see how that can be irritating, I won't continue it.
Thank you.
 Andrei
Feb 20 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 10:37 AM, foobar wrote:
 On Monday, 20 February 2012 at 15:50:08 UTC, Andrei Alexandrescu wrote:
 Actually that just shuffles the matter around. Any setup does demand
 that some library (in this case most probably the standard library) will
 be a dependency knot because it defines the hierarchy that others should
 use.
Not accurate. A 3rd party library that want to be compatible will no doubt depend on the standard library's _exception hierarchy_ but that does *not* mean it should depend on the parallel functionality in the standard library. Per our example with IO, if I use tango.io I certainly do not want my application code to include redundantly both std.io and tango.io. I wanted to use tango.io as an *alternative* to std.io.
This is a confusion. Using PackageException!"std.io" does not require importing std.io. Conversely, using std.IOException _does_ require importing std.exceptions or whatnot. So from a dependency management viewpoint, PackageException is superior to IOException. The converse disadvantage is that typos won't be caught during compilation. For example, using PackageException!"sdt.io" will go through no problem, but of course won't be caught by people waiting for a PackageException!"std.io". Andrei
Feb 20 2012
parent reply Juan Manuel Cabo <juanmanuel.cabo gmail.com> writes:
With all due respect, I don't see module exception categories
as something good for the categorization of exceptions.

As I said in a previous post, long lost in this mega thread,
please don't create exception categories, unless it makes
sense to write a   catch (MyNewShinyCategory ex) {}  for it.
An artificial category would be in the way of other criteria
that aids catch grouping better.
(For instance, many different modules might want to add a
kind of IOException (not that I advocate the IOException
category, this is just for illustration))

--jm


On 02/20/2012 01:45 PM, Andrei Alexandrescu wrote:
 On 2/20/12 10:37 AM, foobar wrote:
 On Monday, 20 February 2012 at 15:50:08 UTC, Andrei Alexandrescu wrote:
 Actually that just shuffles the matter around. Any setup does demand
 that some library (in this case most probably the standard library) will
 be a dependency knot because it defines the hierarchy that others should
 use.
Not accurate. A 3rd party library that want to be compatible will no doubt depend on the standard library's _exception hierarchy_ but that does *not* mean it should depend on the parallel functionality in the standard library. Per our example with IO, if I use tango.io I certainly do not want my application code to include redundantly both std.io and tango.io. I wanted to use tango.io as an *alternative* to std.io.
This is a confusion. Using PackageException!"std.io" does not require importing std.io. Conversely, using std.IOException _does_ require importing std.exceptions or whatnot. So from a dependency management viewpoint, PackageException is superior to IOException. The converse disadvantage is that typos won't be caught during compilation. For example, using PackageException!"sdt.io" will go through no problem, but of course won't be caught by people waiting for a PackageException!"std.io". Andrei
Feb 20 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 2:10 PM, Juan Manuel Cabo wrote:
 With all due respect, I don't see module exception categories
 as something good for the categorization of exceptions.
Yah, that's been since destroyed. Bane of my life: I'm never right :o). Andrei
Feb 20 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 01:10:39AM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 12:44 AM, foobar wrote:
I just died a little reading this. Are you suggesting that in order
to handle IO exceptions I need to: try { ...whatever... } catch
(PackageException!"std.io") {...} } catch
(PackageException!"tango.io") {...} } catch
(PackageException!"otherLib.io") {...} ...

What the hell is wrong with just using an IOException?
There's nothing wrong, but there's a possible misunderstanding. If tango.io and otherLib.io cooperate with std, then they'd originate exceptions in std.io (as opposed to their own). Do note that the issue is exactly the same if libraries use IOException - they all must agree on using the same nomenclature, whether it's called PackageException!"std.io" or IOException.
[...] The exception hierarchy and the phobos module hierarchy are two different things. They should not be unnecessarily coupled. You *want* exceptions in the exception hierarchy to be maximally reusable by code OUTSIDE of the phobos module that first introduced them. You want things like ParseError and LexicalError instead of StdGetoptError and StdJsonLexicalError, because then user code can *reuse* these exception types! *That* is what reduces boilerplate, since otherwise user code would have to define their own exceptions, thereby incurring lots of boilerplate and code duplication.
 ModuleException and PackageException have one important thing going for
 them: they automate away a good amount of boilerplate, which makes them
 interesting for me to look at, and worth sharing as long as we're
 brainstorming. The associated issues as clear as the advantages.
 Probably ModuleException is too specific to be interesting, but
 PackageException seems useful.
[...] They do not automate away boilerplate; they merely transplant said boilerplate into user code by making it impossible for user code to reuse these exceptions. Which actually amounts to *more* boilerplate, since every user app will be introducing their own exception types rather than reusing what's already in the standard library. T -- "Outlook not so good." That magic 8-ball knows everything! I'll ask about Exchange Server next. -- (Stolen from the net)
Feb 20 2012
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
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) {} -- /Jacob Carlborg
Feb 19 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Feb 19 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-02-19 23:44, Jonathan M Davis wrote:
 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
Oh, as in template constrains, I see. Forgot about that.
 catch(Ex1, Ex2 : Ex0 e)

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

 - Jonathan m Davs
-- /Jacob Carlborg
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 3:26 AM, 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
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). Andrei
Feb 19 2012
next sibling parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
"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).

 Andrei
That would still require changing the language, just not the syntax. (E : A, B) was just the syntax for listing base classes.
Feb 19 2012
prev sibling parent "Nick Sabalausky" <a a.a> writes:
"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); }
Feb 19 2012
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
[...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T -- Gone Chopin. Bach in a minuet.
Feb 19 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
Feb 19 2012
next sibling parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace. -- - Alex
Feb 19 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 8:44 AM, Alex Rnne Petersen wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 That helps. This quite nicely illustrates that types don't necessarily
 need to proliferate. Something not much more constraining can be done
 today:

 try {
 ...
 } catch(IOException e)
 {
 if (e.errno !in subsetYouWantToHandle) throw e;
 ...
 }


 Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace.
I understand, that's a good point. Could the matter be considered an implementation issue? Andrei
Feb 19 2012
parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 19-02-2012 15:46, Andrei Alexandrescu wrote:
 On 2/19/12 8:44 AM, Alex Rnne Petersen wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 That helps. This quite nicely illustrates that types don't necessarily
 need to proliferate. Something not much more constraining can be done
 today:

 try {
 ...
 } catch(IOException e)
 {
 if (e.errno !in subsetYouWantToHandle) throw e;
 ...
 }


 Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace.
I understand, that's a good point. Could the matter be considered an implementation issue? Andrei
Unfortunately, I don't think so. As far as I am aware, there is no good way to solve the problem; you can't know whether the user intended to rethrow (and thus continue the stack trace) as opposed to doing a clean throw. You could say that if the throw statement is "throw new T();", then a new stack trace is created, and in all other cases, the stack trace is continued, however, this doesn't work universally (think: creating and returning an exception object in a function and then throwing it). -- - Alex
Feb 19 2012
parent Jose Armando Garcia <jsancio gmail.com> writes:
On Sun, Feb 19, 2012 at 12:51 PM, Alex R=F8nne Petersen
<xtzgzorex gmail.com> wrote:
 On 19-02-2012 15:46, Andrei Alexandrescu wrote:
 On 2/19/12 8:44 AM, Alex R=F8nne Petersen wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 That helps. This quite nicely illustrates that types don't necessarily
 need to proliferate. Something not much more constraining can be done
 today:

 try {
 ...
 } catch(IOException e)
 {
 if (e.errno !in subsetYouWantToHandle) throw e;
 ...
 }


 Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace.
I understand, that's a good point. Could the matter be considered an implementation issue? Andrei
Unfortunately, I don't think so. As far as I am aware, there is no good w=
ay
 to solve the problem; you can't know whether the user intended to rethrow
 (and thus continue the stack trace) as opposed to doing a clean throw.

 You could say that if the throw statement is "throw new T();", then a new
 stack trace is created, and in all other cases, the stack trace is
 continued, however, this doesn't work universally (think: creating and
 returning an exception object in a function and then throwing it).
In general that is fine because in most cases you will use it as: throw createException(... data ...); What you can't do is cache exceptions: immutable _IOExceptionCache =3D new IOException(...); void someFunction(...) { throw _IOExceptionCache; } I think the best way to abstract exception creation is: throwException(... data ...) { throw new Exception(...); } Thanks, -Jose
 --
 - Alex
Feb 19 2012
prev sibling parent reply Jose Armando Garcia <jsancio gmail.com> writes:
On Sun, Feb 19, 2012 at 12:44 PM, Alex R=F8nne Petersen
<xtzgzorex gmail.com> wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it vi=
a
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constrai=
nt
 will be regarded as "not caught", even if the base type matches.


 T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace.
What? Is there a technical reason why throw resets the stack? Java doesn't work this way. In java the stack is created when the object Throwable is created: "A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. " We should consider changing this to work more like Java. This allows for patterns like: // Log the stack but don't throw auto e =3D new Exception(); writefln(e.stack); Thanks, -Jose
 --
 - Alex
Feb 19 2012
next sibling parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
Are you sure about that?

My understanding is java works the same way in terms of re-evaluating stack 
traces, except that it has
'throw;' which keeps the original intact, specifically for rethrowing.

"Jose Armando Garcia" <jsancio gmail.com> wrote in message 
news:mailman.601.1329663665.20196.digitalmars-d puremagic.com...
On Sun, Feb 19, 2012 at 12:44 PM, Alex Rnne Petersen
<xtzgzorex gmail.com> wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace.
What? Is there a technical reason why throw resets the stack? Java doesn't work this way. In java the stack is created when the object Throwable is created: "A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. " We should consider changing this to work more like Java. This allows for patterns like: // Log the stack but don't throw auto e = new Exception(); writefln(e.stack); Thanks, -Jose
 --
 - Alex 
Feb 19 2012
parent reply Jose Armando Garcia <jsancio gmail.com> writes:
On Sun, Feb 19, 2012 at 1:08 PM, Daniel Murphy <yebblies nospamgmail.com> w=
rote:
 Are you sure about that?

 My understanding is java works the same way in terms of re-evaluating sta=
ck
 traces, except that it has
 'throw;' which keeps the original intact, specifically for rethrowing.
Quoting the source of all Java truth ;): http://docs.oracle.com/javase/6/docs/api/java/lang/Throwable.html I can't keep all this language semantic straight but it is my In Java if you want to reset the stack you need to create a new exception and chain the old one: try { ... } catch (Exception e) { throw new Exception(e); }
 "Jose Armando Garcia" <jsancio gmail.com> wrote in message
 news:mailman.601.1329663665.20196.digitalmars-d puremagic.com...
 On Sun, Feb 19, 2012 at 12:44 PM, Alex R=F8nne Petersen
 <xtzgzorex gmail.com> wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that i=
t
 has too many issues as-is to work. It might be possible to adjust th=
e
 idea to make it workable though. Right now, it's possible to do it v=
ia
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the stac=
k
 trace.
What? Is there a technical reason why throw resets the stack? Java doesn't work this way. In java the stack is created when the object Throwable is created: "A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. " We should consider changing this to work more like Java. This allows for patterns like: // Log the stack but don't throw auto e =3D new Exception(); writefln(e.stack); Thanks, -Jose
 --
 - Alex
Feb 19 2012
parent "Daniel Murphy" <yebblies nospamgmail.com> writes:

Feb 19 2012
prev sibling parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 19-02-2012 16:00, Jose Armando Garcia wrote:
 On Sun, Feb 19, 2012 at 12:44 PM, Alex Rnne Petersen
 <xtzgzorex gmail.com>  wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the stack trace.
What? Is there a technical reason why throw resets the stack? Java doesn't work this way. In java the stack is created when the object Throwable is created: "A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. " We should consider changing this to work more like Java. This allows for patterns like: // Log the stack but don't throw auto e = new Exception(); writefln(e.stack); Thanks, -Jose
 --
 - Alex
If Java really works that way, I'm sorry, but that just makes me think even worse of the language/VM. If you create an exception object in some utility function (or chain of such), you don't want those in your stack trace. You want the stack trace to start from where you throw the object, not where you created it. Anything else is just confusing (and perhaps explains the several hundred lines long stack traces Java programs sometimes spit out...). -- - Alex
Feb 19 2012
next sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Sun, Feb 19, 2012 at 1:11 PM, Alex R=F8nne Petersen
<xtzgzorex gmail.com> wrote:
 On 19-02-2012 16:00, Jose Armando Garcia wrote:
 On Sun, Feb 19, 2012 at 12:44 PM, Alex R=F8nne Petersen
 <xtzgzorex gmail.com> =A0wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that =
it
 has too many issues as-is to work. It might be possible to adjust t=
he
 idea to make it workable though. Right now, it's possible to do it
 via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sor=
t
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the sta=
ck
 trace.
What? Is there a technical reason why throw resets the stack? Java doesn't work this way. In java the stack is created when the object Throwable is created: "A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. " We should consider changing this to work more like Java. This allows for patterns like: // Log the stack but don't throw auto e =3D new Exception(); writefln(e.stack); Thanks, -Jose
 --
 - Alex
If Java really works that way, I'm sorry, but that just makes me think ev=
en
 worse of the language/VM.

 If you create an exception object in some utility function (or chain of
 such), you don't want those in your stack trace. You want the stack trace=
to
 start from where you throw the object, not where you created it. Anything
 else is just confusing (and perhaps explains the several hundred lines lo=
ng
 stack traces Java programs sometimes spit out...).
Proof is in the code: public final class TextException { public static void main(String[] args) { try { throwException(); } catch (Exception e) { e.printStackTrace(); } } private static void throwException() { throw pushStack(3); } private static RuntimeException pushStack(int i) { if (i > 0) return pushStack(i-1); else return new RuntimeException(); } } Output: java.lang.RuntimeException at TextException.pushStack(TextException.java:18) at TextException.pushStack(TextException.java:17) at TextException.pushStack(TextException.java:17) at TextException.pushStack(TextException.java:17) at TextException.throwException(TextException.java:13) at TextException.main(TextException.java:6)
 --
 - Alex
Feb 19 2012
prev sibling parent Jose Armando Garcia <jsancio gmail.com> writes:
On Sun, Feb 19, 2012 at 1:11 PM, Alex R=F8nne Petersen
<xtzgzorex gmail.com> wrote:
 On 19-02-2012 16:00, Jose Armando Garcia wrote:
 On Sun, Feb 19, 2012 at 12:44 PM, Alex R=F8nne Petersen
 <xtzgzorex gmail.com> =A0wrote:
 On 19-02-2012 15:41, Andrei Alexandrescu wrote:
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that =
it
 has too many issues as-is to work. It might be possible to adjust t=
he
 idea to make it workable though. Right now, it's possible to do it
 via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sor=
t
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
As I pointed out on the pull request, this is *evil*. It resets the sta=
ck
 trace.
What? Is there a technical reason why throw resets the stack? Java doesn't work this way. In java the stack is created when the object Throwable is created: "A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. " We should consider changing this to work more like Java. This allows for patterns like: // Log the stack but don't throw auto e =3D new Exception(); writefln(e.stack); Thanks, -Jose
 --
 - Alex
If Java really works that way, I'm sorry, but that just makes me think ev=
en
 worse of the language/VM.

 If you create an exception object in some utility function (or chain of
 such), you don't want those in your stack trace. You want the stack trace=
to
 start from where you throw the object, not where you created it. Anything
 else is just confusing (and perhaps explains the several hundred lines lo=
ng
 stack traces Java programs sometimes spit out...).
Unless you want the Exception object to be immutable which I think it shoul= d be.
 --
 - Alex
Feb 19 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/02/2012 15:41, Andrei Alexandrescu a crit :
 On 2/19/12 7:31 AM, Timon Gehr wrote:
 On 02/19/2012 09:26 AM, H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches. T
Nice.
That helps. This quite nicely illustrates that types don't necessarily need to proliferate. Something not much more constraining can be done today: try { ... } catch(IOException e) { if (e.errno !in subsetYouWantToHandle) throw e; ... } Andrei
This wouldn't work because you'll erase teh stack trace. An alternative is to create a new exception and chain with e. You discussed that in TDPL already. But It is worse than creating new types of Exception IMO, because now, you ends up creating 2 Exceptions. And you introduce the risk of someone silenting Exception, something that you don't want !
Feb 19 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 12:56 AM, Jonathan M Davis wrote:
 I think that being able to have a catch block which took multiple exception
 types would be plenty. There are times when it would be very valuable to be
 able to use the same catch block for multiple exceptions without having to
 catch their base type (which would then potentially catch other exceptions
 which you didn't want to catch). So, something like that second catch block
 that you have there would be very valuable.
So now hierarchies are not good? Andrei
Feb 19 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Feb 19, 2012 at 02:04:50AM -0600, Andrei Alexandrescu wrote:
 On 2/19/12 12:56 AM, Jonathan M Davis wrote:
I think that being able to have a catch block which took multiple
exception types would be plenty. There are times when it would be
very valuable to be able to use the same catch block for multiple
exceptions without having to catch their base type (which would then
potentially catch other exceptions which you didn't want to catch).
So, something like that second catch block that you have there would
be very valuable.
So now hierarchies are not good?
[...] It's not a simple black-or-white decision we're dealing with here. :) Although exception hierarchies are currently the best we have, it's not perfect, and it does have its own flaws. If we can figure out a better system that fixes those flaws without compromising the good points of using a class hierarchy, that would be even better. The core of the problem is this: when an exception happens, it's within a specific context with the associated domain-specific data. This context and domain-specific data can be totally disparate types. For example, a FileNotFound exception occurs in the filesystem, and is associated with a filename. A SeekError also occurs in the filesystem, is associated with a file (not necessarily equivalent to a filename), and a seek offset. By contrast, a NetworkTimeout error happens in the network stack, is associated with a particular protocol (say TCP), a particular network socket (with its own associated IP address, etc.), and a particular timeout value. It has no associated filename, or seek offset. So you have a bunch of exceptions, each of which is associated with a bunch of info that, in general, have no relation with any info associated with another exception. The problem at hand is: how do you represent this info in a useful way? Generally speaking, the only common thing between all exceptions is the fact that they are, well, exceptions. And some of them are more closely related to each other than others -- file-related exceptions may, for example, share the fact that they are tied to a specific file. To me, this is a giant hint that OOP is a good solution. You want a class hierarchy rooted at Exception. And groups of related exceptions probably should be rooted under their respective base classes under Exception. If you have a better way to solve this, I'm waiting to hear it. T -- Gone Chopin. Bach in a minuet.
Feb 19 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 2:49 AM, H. S. Teoh wrote:
[snip]
 To me, this is a giant hint that OOP is a good solution. You want a
 class hierarchy rooted at Exception. And groups of related exceptions
 probably should be rooted under their respective base classes under
 Exception.

 If you have a better way to solve this, I'm waiting to hear it.
I don't. This thread is predicated on the assumption that the current approaches to exception handling are wanting, so it's time to look outside the box. It's also reasonable to assume that people involved in this thread do have an understanding of current mechanisms, so rehashes thereof are unnecessary. Andrei
Feb 19 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 10:19:13 Andrei Alexandrescu wrote:
 On 2/19/12 2:49 AM, H. S. Teoh wrote:
 [snip]
 
 To me, this is a giant hint that OOP is a good solution. You want a
 class hierarchy rooted at Exception. And groups of related exceptions
 probably should be rooted under their respective base classes under
 Exception.
 
 If you have a better way to solve this, I'm waiting to hear it.
I don't. This thread is predicated on the assumption that the current approaches to exception handling are wanting, so it's time to look outside the box. It's also reasonable to assume that people involved in this thread do have an understanding of current mechanisms, so rehashes thereof are unnecessary.
Do you really believe that all of the current approaches are wanting? They may need some tweaking for us, but I don't think that they're necessarily all that broken. their exception hierarchies and then figure out which pieces of that make sense for Phobos and then do something similar. We don't have to copy what they did - we should tweak it according to our needs - but I really think that the basic exception hierarchy that they use tends to work very well. Where you tend to run into problems are where people do stupid things like catch Exception and ignore it. But you can't stop programmers from being stupid. while since I used it though) in how we deal with Exception vs Error. So, we're a step ahead in that regard. But we don't have any kind of organized exception hierarchy right now. We simply have modules declaring their own whether it makes sense or not.uld do something similar. Perhaps it shouldn't We should be looking at the exceptions that we currently have and figure out how they should be reorganized into a proper hierarchy, removing them in some with their exception hierarchies. We should do something similar. Maybe we won't have quite as many exception types, and maybe we organize some things quite differently, but I really think that we should be looking at what they did and and emulate it at least on some level. When and where we can come up with our own improvements, that's great. Let's use those. But let's not throw the baby out with the bath water. I don't think that a complete redesign of how exceptions are designed is necessary. We just need to organize them better. - Jonathan M Davis
Feb 19 2012
prev sibling next sibling parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
The issue here isn't deep vs. flat hierarchy (like the rest of the thread), 
it's about improving code clarity/dry.

This is much like the 'case A, B:' syntax (or fallthrough/goto case) in 
switches.

Even with a flat hierarchy, you might still want to handle a set of 
exceptions deriving from Exception the same way, but don't want to catch 
everything derived from Exception.

"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:jhqaf2$tfk$2 digitalmars.com...
 On 2/19/12 12:56 AM, Jonathan M Davis wrote:
 I think that being able to have a catch block which took multiple 
 exception
 types would be plenty. There are times when it would be very valuable to 
 be
 able to use the same catch block for multiple exceptions without having 
 to
 catch their base type (which would then potentially catch other 
 exceptions
 which you didn't want to catch). So, something like that second catch 
 block
 that you have there would be very valuable.
So now hierarchies are not good? Andrei
Feb 19 2012
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 02:04:50 Andrei Alexandrescu wrote:
 On 2/19/12 12:56 AM, Jonathan M Davis wrote:
 I think that being able to have a catch block which took multiple
 exception
 types would be plenty. There are times when it would be very valuable to
 be
 able to use the same catch block for multiple exceptions without having to
 catch their base type (which would then potentially catch other exceptions
 which you didn't want to catch). So, something like that second catch
 block
 that you have there would be very valuable.
So now hierarchies are not good?
No. Sometimes you want to catch specific types and handle a subset of them in a particular way but don't want to handle _all_ of the exceptions with the same base class the same way. For instance, if you had the following and all of them are derived from FileException (save FileException itself): catch(e : FileNotFoundException, NotAFileException) { //... } catch(AccessDeniedException e) { //... } catch(FileException e) { //... } You want to handle certain exceptions differently and you want to handle some of them the same, but you don't want to handle all FileExceptions the same way. Without a way to put multiple exception types in the same block, you tend to end up with code duplication (and no I'm not necessarily advocating that we have those specific exception types - they're just examples). It's even worse if you don't have much of a hierarchy, since if _everything_ is derived from Exception directly, then catching the common type - Exception - would catch _everything_. For instance, what if you want to handle StringException and UTFException together but FileException differently? You can't currently do catch(e : StringException, UTFException) { //... } catch(FileException e) { //... } Right now, you'd have to have separate catch blocks for StringException and UTFException. A well-designed exception hierarchy reduces the problem considerably, because then there's a much higher chance that catching a common exception will catch what you want and not what you don't, but that doesn't mean that you're never going to run into cases where catching the common type doesn't work. - Jonathan M Davis
Feb 19 2012
parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
That proposed syntax is nicer than this, but at least you can do 
it this way:
just call the same function from both catch blocks.


import std.stdio, std.utf, std.string;
void main() {
	void handleStringAndUtf(Exception ex) {
		if (typeid(ex).name == "std.utf.UTFException") {
			// .. UtfException specific handling ..
			writeln("length: ", (cast(UTFException)ex).len);
		}
		// .. handling of UtfException and StringException in common ..
		writeln(ex.toString);
		writeln(typeid(ex).name);
	}

	try {
		throw new UtfException("");
		//throw new StringException("aaaa");
	} catch (StringException ex) {
		handleStringAndUtf(ex);
	} catch (UTFException ex) {
		handleStringAndUtf(ex);
	}
}


--jm



On Sunday, 19 February 2012 at 09:12:40 UTC, Jonathan M Davis 
wrote:
 On Sunday, February 19, 2012 02:04:50 Andrei Alexandrescu wrote:
 On 2/19/12 12:56 AM, Jonathan M Davis wrote:
No. Sometimes you want to catch specific types and handle a subset of them in a particular way but don't want to handle _all_ of the exceptions with the same base class the same way. For instance, if you had the following and all of them are derived from FileException (save FileException itself): catch(e : FileNotFoundException, NotAFileException) { //... } catch(AccessDeniedException e) { //... } catch(FileException e) { //... } You want to handle certain exceptions differently and you want to handle some of them the same, but you don't want to handle all FileExceptions the same way. Without a way to put multiple exception types in the same block, you tend to end up with code duplication (and no I'm not necessarily advocating that we have those specific exception types - they're just examples). It's even worse if you don't have much of a hierarchy, since if _everything_ is derived from Exception directly, then catching the common type - Exception - would catch _everything_. For instance, what if you want to handle StringException and UTFException together but FileException differently? You can't currently do catch(e : StringException, UTFException) { //... } catch(FileException e) { //... } Right now, you'd have to have separate catch blocks for StringException and UTFException. A well-designed exception hierarchy reduces the problem considerably, because then there's a much higher chance that catching a common exception will catch what you want and not what you don't, but that doesn't mean that you're never going to run into cases where catching the common type doesn't work. - Jonathan M Davis
Feb 19 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 23:26:35 Jose Armando Garcia wrote:
 I think that is a good compromise. The one thing we need to solve is
 that for example Oracle's error code may collide with POSIX error
 code. This is why I suggested using namespace but there may be other
 solutions.
What relation would POSIX and Oracle codes have though? I'd expect them to be on completely different exception types. POSIX error codes would make sense on something like FileException, whereas Oracle codes would make sense on something like DatabaseException or OracleException. The problem you run into is when you also have PostgresException and SQLLiteException which derive from DatabaseException. You could have an error code in it, but it wouldn't necessarily mean much if it weren't tied to the specific database exception type. The hierarchy that can be ignored is when OracleException or PostgresException have derived types based on the error code (which then hopefully provide additional information beyond just the error code). But you still end up with a minimal exception hierarchy that everyone has to use if they want to stick to error codes. People can use FileException and its error code rather than FileNotFoundException and NotFileException, but they'd still have to use FileException rather than Exception to get the error code. - Jonathan M Davis
Feb 18 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 19, 2012 00:26:57 H. S. Teoh wrote:
 On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote:
 [...]
 
 So, while at first glance, it seems like a good idea, I think that it
 has too many issues as-is to work. It might be possible to adjust the
 idea to make it workable though. Right now, it's possible to do it via
 mixins or calling a function inside the catch, but doing something
 similar to this would certainly be nice, assuming that we could sort
 out the kinks.
[...] I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this: try { ... } catch(IOException e) if (e.errno in subsetYouWantToHandle) { ... } Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches.
Interesting. But I don't think that it really buys you all that much, since normally what I'd think that you would want would simply be a list of the exception types that that particular catch block would handle. What you're suggesting would appear to end up being pretty much just ths: try { //... } catch(IOException e) { if(e.errno in subsetYouWantToHandle) { } else { } } save for the fact that if the condition were false, in your example, you could presumably have a catch(Exception e) block following it which would catch it. Honestly though, I think that something like Daniel's suggesting would be plenty, and it's much more explicit. Also, your suggestion might be a bit confusing in that it looks like a template constraint (which is evaluated at compile time) and yet it would have to be evaluated at runtime. That risks too much confusion IMHO. It's an interesting idea though. - Jonathan M Davis
Feb 19 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/18/2012 07:52 PM, Andrei Alexandrescu wrote:
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca


 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is most
 of the time useless.


 Andrei
Would giving std.concurrency.OwnerTerminated and std.concurrency.LinkTerminated a common supertype make sense?
Feb 19 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/19/12 7:37 AM, Timon Gehr wrote:
 On 02/18/2012 07:52 PM, Andrei Alexandrescu wrote:
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca



 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is most
 of the time useless.


 Andrei
Would giving std.concurrency.OwnerTerminated and std.concurrency.LinkTerminated a common supertype make sense?
I am not sure. Andrei
Feb 19 2012
prev sibling next sibling parent Sean Kelly <sean invisibleduck.org> writes:
I think it depends on the application. For user apps where errors may often s=
imply be reported, type probably doesn't matter. For apps that may want to c=
hange their behavior based on certain errors, catching by type can be useful=
. Also, testing in/out contracts for correctness pretty much relies on excep=
tions differentiated by type.=20

As one simple example of where I've found throwing by type useful, I've writ=
ten back-end apps that interact with a DB. If a DB connection occurs during p=
rocessing I need to tear down an array of processing functionality and attem=
pt reconnects in a progressive back-off to avoid spinning on reconnect. Thro=
wing just Exception instead of DbException or whatever means a lot of castin=
g or other inspection to find the useful info and re-throwing if I don't car=
e about the exception. If I'm going to do that I pretty much may as well go b=
ack to C-style programming and checking return values.=20

On Feb 18, 2012, at 10:52 AM, Andrei Alexandrescu <SeeWebsiteForEmail erdani=
.org> wrote:

 There's a discussion that started in a pull request:
=20
 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c=
0dcaca
=20
 Let's come up with a good doctrine for exception defining and handling in P=
hobos. =46rom experience I humbly submit that catching by type is most of th= e time useless.
=20
=20
 Andrei
Feb 19 2012
prev sibling next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 18/02/2012 19:52, Andrei Alexandrescu a crit :
 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca


 Let's come up with a good doctrine for exception defining and handling
 in Phobos. From experience I humbly submit that catching by type is most
 of the time useless.


 Andrei
By reading the discussion, and by travelling alone by car the whole day, I had plenty of time to think about all that. Here are some conclusion and a proposal. 1/ Andrei's proposal about transient is great. This is a useful property to know about Exceptions and so we should integrate it. This property must be pure and const, see why at point 4. 2/ Modules in phobos are organised according to what the do, not how they fail. So the Exception hierarchy using modules and package is not a good approach. We should use Exception in phobos that user or third party lib writter can use, to avoid Exception type proliferation. 3/ An Exception deserve its own type if it can additionnal carry data about the problem faced, or is supposed to be handled a very different way (ie, FileNotFoundException is likely to be handled differently that SecurityException on a function like remove). Most of the time, theses 2 caracteristics goes with each other. 4/ Sometime, Exception doesn't deserve its own type, but you may want to not match all of them. For example, if we have an OracleException with an errorcode as defined by oracle documentation, we may want to handle some of them differently. For such a thing, proposal has been made in this thread. Let me expose mine. This is not revolutionary and is largely inspired by what was posted in this thread. The point is to specify it more clearly. Let's start with the syntax : try { // Stuff . . . } catch(Exception e) if(e.transient == false) { // Handling . . . } If what is in the if is evaluated to false, then the catch block isn't selected and the Exception continue its propagation just like no catch block were present. This syntax has proven to be convenient in template instanciation, and we should reuse it as much as possible (even if in our case, it is evaluated at runtime and not compiletime). To ensure that it make sense, we need that what is in the if is pure and the Exception const. The execution of the test in the if must have no side effect. If what is in the if block throw, then it happen the same than if something in the catch block throw : the new Exception is propagated, and the old one is linked to that new one. Alternatively, we can decide that what is in the if must be nothrow too, but we still have to consider Errors, so it doesn't solve the problem and add useless constraints. I used the transient property here, but basically any test that is pure should be usable. If the Exception is a more specific type, then we can use it's property and method, as long as it is pure. Amaury
Feb 20 2012
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, February 20, 2012 11:15:08 H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 01:52:15PM -0500, Jonathan M Davis wrote:
 [...]
 
 So, instead of
 
 catch(SpecificException1 e)
 {
 
 //use fields specific to this exception to do whatever you need to do
 
 }
 catch(SpecificException2 e)
 {
 
 //use fields specific to this exception to do whatever you need to do
 
 }
 catch(GenericException e)
 {
 
 //handle the generic exception as best you can given the lack of
 //a specific one
 
 }
 
 you end up with something effectively along the lines of
 
 catch(GenericException e)
 {
 
 if(/* e is SpecificException1 */)
 {
 //use fields specific to this exception to do whatever you need to do
 }
 else if(/* e is SpecificException2 */)
 {
 //use fields specific to this exception to do whatever you need to do
 }
 else
 {
 //handle the generic exception as best you can given the lack of
 //a specific one
 }
 
 }
[...] I think what Andrei wants to do is more along these lines: catch(Exception e) { writeln(formatLocaleString(e.fmt, e.data)); } I think there's some merit to this idea. However, I'm still unsure about storing stuff in a Variant.
Except that if you're not printing out the message, if all of the data is in e.data as a variant, then you _do_ end up with code like I gave. The variant is a bad idea for anything trying to actually handle the exception rather than just print out a message.
 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
Feb 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 20:27, Jonathan M Davis a écrit :
 On Monday, February 20, 2012 11:15:08 H. S. Teoh wrote:
 On Mon, Feb 20, 2012 at 01:52:15PM -0500, Jonathan M Davis wrote:
 [...]

 So, instead of

 catch(SpecificException1 e)
 {

 //use fields specific to this exception to do whatever you need to do

 }
 catch(SpecificException2 e)
 {

 //use fields specific to this exception to do whatever you need to do

 }
 catch(GenericException e)
 {

 //handle the generic exception as best you can given the lack of
 //a specific one

 }

 you end up with something effectively along the lines of

 catch(GenericException e)
 {

 if(/* e is SpecificException1 */)
 {
 //use fields specific to this exception to do whatever you need to do
 }
 else if(/* e is SpecificException2 */)
 {
 //use fields specific to this exception to do whatever you need to do
 }
 else
 {
 //handle the generic exception as best you can given the lack of
 //a specific one
 }

 }
[...] I think what Andrei wants to do is more along these lines: catch(Exception e) { writeln(formatLocaleString(e.fmt, e.data)); } I think there's some merit to this idea. However, I'm still unsure about storing stuff in a Variant.
Except that if you're not printing out the message, if all of the data is in e.data as a variant, then you _do_ end up with code like I gave. The variant is a bad idea for anything trying to actually handle the exception rather than just print out a message.
Have you considered this : http://forum.dlang.org/thread/jhos0l$102t$1 digitalmars.com?page=25#post-jhtus4:2416tp:241:40digitalmars.com
 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.
Feb 20 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
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. - Jonathan M Davis
Feb 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 20:45, Jonathan M Davis a écrit :
 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. - Jonathan M Davis
Agreed !
Feb 20 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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 ... } Andrei
Feb 20 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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? Barring that, a temporary workaround for lack of RTTI would be to have a template that generates actual fields along with an index of fields via mixins, something like this: // NOTE: not actual syntax template GenFormattingFields(T..., T fields) { ... } class RoxorException : Exception { GenFormattingFields!( string, "filename", int, "line", int, "column" ); int nonFormattingField; ... } The template would generate code along these lines: // Generated code (not actual syntax) class RoxorException : Exception { string filename; int line; int column; override Variant[string] getFormattingFields() { return [ "filename": filename, "line": line, "column": column ]; } int nonFormattingField; ... } where getFormattingFields is a virtual function defined by Exception. Then you can get the fields for formatting at runtime, *and* have static type checking of direct accesses to RoxorException.filename, etc., at compile-time. T -- Trying to define yourself is like trying to bite your own teeth. -- Alan Watts
Feb 20 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
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?
Feb 20 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/20/12 8:33 PM, Robert Jacques wrote:
 Variant e = new MyException();
 writeln( e.filename, e.line, e.column);

 Aren't __traits and opDispatch fun?
I thought the same, but was afraid to bring the nuclear bomb out :o). Andrei
Feb 20 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 08:41:47PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 8:33 PM, Robert Jacques wrote:
Variant e = new MyException();
writeln( e.filename, e.line, e.column);

Aren't __traits and opDispatch fun?
I thought the same, but was afraid to bring the nuclear bomb out :o).
[...] Mmm... but *this* nuclear bomb, I like. :-) T -- Heads I win, tails you lose.
Feb 20 2012
parent "Robert Jacques" <sandford jhu.edu> writes:
On Mon, 20 Feb 2012 21:08:07 -0600, H. S. Teoh <hsteoh quickfur.ath.cx> wrote:

 On Mon, Feb 20, 2012 at 08:41:47PM -0600, Andrei Alexandrescu wrote:
 On 2/20/12 8:33 PM, Robert Jacques wrote:
Variant e = new MyException();
writeln( e.filename, e.line, e.column);

Aren't __traits and opDispatch fun?
I thought the same, but was afraid to bring the nuclear bomb out :o).
[...] Mmm... but *this* nuclear bomb, I like. :-) T
It gets better, you can also create prototype objects: Variant p1 = Tuple!(real,"x",real,"y")(1,2); Variant p2; // An unitialized Variants attemps to convert p2.x = 1; // itself to Variant[string] on assignment p2.__reflect("y",Variant(2) ); p2["opBinary!+"] = (Variant a, real b ){return b; }; // Create an 'operator' p2["opBinary!+"] ~= (Variant a, Variant b){return a.x;}; // overload set assert( (p2 + 4) == 4 && (p2 + p1) == 1);
Feb 21 2012
prev sibling next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques 
wrote:
 Nope. See 
 (https://jshare.johnshopkins.edu/rjacque2/public_html/ )
Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox.
Feb 21 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Tue, 21 Feb 2012 09:12:57 -0600, Adam D. Ruppe <destructionator gmail.com>
wrote:

 On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques
 wrote:
 Nope. See
 (https://jshare.johnshopkins.edu/rjacque2/public_html/ )
Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox.
I'm in the process of cleaning up the required patches and submitting them. The first one's already in (https://github.com/sandford/phobos/commit/8b845d2a50bc20993afed7 06f3d84921e4ac54b). I've almost got appender ready.
Feb 21 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 6:11 PM, Robert Jacques wrote:
 On Tue, 21 Feb 2012 09:12:57 -0600, Adam D. Ruppe
 <destructionator gmail.com> wrote:

 On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques
 wrote:
 Nope. See
 (https://jshare.johnshopkins.edu/rjacque2/public_html/ )
Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox.
I'm in the process of cleaning up the required patches and submitting them. The first one's already in (https://github.com/sandford/phobos/commit/8b845d2a50bc20993afed7306f3d84921e4ac54b). I've almost got appender ready.
Are the changes substantial enough to put them through the review process? Andrei
Feb 21 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Tue, 21 Feb 2012 21:51:34 -0600, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 On 2/21/12 6:11 PM, Robert Jacques wrote:
 On Tue, 21 Feb 2012 09:12:57 -0600, Adam D. Ruppe
 <destructionator gmail.com> wrote:

 On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques
 wrote:
 Nope. See
 (https://jshare.johnshopkins.edu/rjacque2/public_html/ )
Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox.
I'm in the process of cleaning up the required patches and submitting them. The first one's already in (https://github.com/sandford/phobos/commit/8b845d2a50bc20993afed7306f3d84921e4ac54b). I've almost got appender ready.
Are the changes substantial enough to put them through the review process? Andrei
To Variant? Yes, definitely. To Appender? I don't think so. There is an slight change in API behavior necessitated by performance considerations, but I don't think it warrants a review by the community at large. Specifically, appender's internal data structure is now sealed, which means that .data must always make a copy. My preference would be to deprecate .data in favor of a .dup/.idup pair. It'll break a bunch of code (which I don't like), but it will make sure no one is calling .data twice in a row, resulting in a silent performance problem.
Feb 22 2012
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 08:19:38 Robert Jacques wrote:
 To Variant? Yes, definitely. To Appender? I don't think so. There is an
 slight change in API behavior necessitated by performance considerations,
 but I don't think it warrants a review by the community at large.
 Specifically, appender's internal data structure is now sealed, which means
 that .data must always make a copy. My preference would be to deprecate
 .data in favor of a .dup/.idup pair. It'll break a bunch of code (which I
 don't like), but it will make sure no one is calling .data twice in a row,
 resulting in a silent performance problem.
I've definitely written code that needed to get at data while appending. I would consider it to be a huge downside to not be able to efficiently get at the current state of the array within the appender. Why on earth would you seal it? - Jonathan M Davis
Feb 22 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Wed, 22 Feb 2012 11:33:57 -0600, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:
 On Wednesday, February 22, 2012 08:19:38 Robert Jacques wrote:
 To Variant? Yes, definitely. To Appender? I don't think so. There is an
 slight change in API behavior necessitated by performance  
 considerations,
 but I don't think it warrants a review by the community at large.
 Specifically, appender's internal data structure is now sealed, which  
 means
 that .data must always make a copy. My preference would be to deprecate
 .data in favor of a .dup/.idup pair. It'll break a bunch of code (which  
 I
 don't like), but it will make sure no one is calling .data twice in a  
 row,
 resulting in a silent performance problem.
I've definitely written code that needed to get at data while appending. I would consider it to be a huge downside to not be able to efficiently get at the current state of the array within the appender. Why on earth would you seal it? - Jonathan M Davis
There's a big difference between sealed and not accessible. .data's API requires exposing an array, and there's no way to do this without leaking memory like a sieve in one way or another. However, if all you need is to iterate the contents, that's easy to do. I'm currently adding backwards iteration. Even indexing is fairly efficient (not yet implemented), particularly relative indexing (i.e. n from back or front). I haven't seen too many use cases yet where accessing the underlying array is important, nor has it come up on bugzilla. I've found one case in Phobos where appender was used as a stack. What's your example? What features does it have to support and how efficient does it have to be?
Feb 22 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote:
 There's a big difference between sealed and not accessible. .data's API
 requires exposing an array, and there's no way to do this without leaking
 memory like a sieve in one way or another. However, if all you need is to
 iterate the contents, that's easy to do. I'm currently adding backwards
 iteration. Even indexing is fairly efficient (not yet implemented),
 particularly relative indexing (i.e. n from back or front).
 
 I haven't seen too many use cases yet where accessing the underlying array
 is important, nor has it come up on bugzilla. I've found one case in
 Phobos where appender was used as a stack. What's your example? What
 features does it have to support and how efficient does it have to be?
It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it. - Jonathan M Davis
Feb 22 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Wed, 22 Feb 2012 13:12:07 -0600, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:
 On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote:
 There's a big difference between sealed and not accessible. .data's API
 requires exposing an array, and there's no way to do this without  
 leaking
 memory like a sieve in one way or another. However, if all you need is  
 to
 iterate the contents, that's easy to do. I'm currently adding backwards
 iteration. Even indexing is fairly efficient (not yet implemented),
 particularly relative indexing (i.e. n from back or front).

 I haven't seen too many use cases yet where accessing the underlying  
 array
 is important, nor has it come up on bugzilla. I've found one case in
 Phobos where appender was used as a stack. What's your example? What
 features does it have to support and how efficient does it have to be?
It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it. - Jonathan M Davis
I view appender's purpose as array building, which is slightly different from simply speeding up array appending. Simply put, an array is a terrible data structure for building arrays. But, I can appreciate the need for mutation and if a particular array building algorithm can't be performed on appender, then appender has failed. Would exposing a bidirectional array be sufficient for your usages? A random access range?
Feb 22 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 14:24:49 Robert Jacques wrote:
 I view appender's purpose as array building, which is slightly different
  from simply speeding up array appending. Simply put, an array is a
 terrible data structure for building arrays. But, I can appreciate the
 need for mutation and if a particular array building algorithm can't be
 performed on appender, then appender has failed. Would exposing a
 bidirectional array be sufficient for your usages? A random access range?
Well, as long as you don't have access to the actual array, you're going to lose something. There are functions that you just won't be able to use because they take an array. However, a range with essentially the same properties as a range (bidirectional, random access, etc.) would cover a large number of the functions that you might want to call. And if you need to hide the array for speed for efficiency for some reason - especially if it results in a large increase in speed - then that could at least partially outweigh the cost of losing the array (especially if Appender has an API to at least use it as a range). But I'd definitely be against hiding it simply for safety, since speed is really its whole point of existance. I don't know why else you'd ever use appender. So, I don't really like the idea of losing access to the underlying array, but if you can provide at least make it so that you could use it with range-based functions, and the changes provides a sigificant speed improvement, then the need for speed arguably outweighs the loss of being able to use the internal array directly. - Jonathan M Davis
Feb 22 2012
parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Wednesday, 22 February 2012 at 20:59:15 UTC, Jonathan M Davis 
wrote:
 speed [...] is really its whole point of existance. I don't 
 know why else you'd ever use appender.
[...]
 - Jonathan M Davis
A use case is to give identity to a built-in array. Consider this: class MyClass { private MyData[] theData; public property MyData[] data() { return theData; } ... } MyClass m = new MyClass(); m.data ~= new MyData(); //Nothing got appended: assert(m.data.length == 0); For the 95% of the use cases, that is the desired behaviour. You don't want anyone appending to your private array. If you wanted to, you would have defined MyClass.append(myData). But there are a few cases where you want to give identity to the array, and let anyone who has a "handle" to it, to be able to append it. (another case is while porting code from languages that don't represent arrays as ranges, and return them as getters). --jm
Feb 22 2012
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 14:12:07 Jonathan M Davis wrote:
 On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote:
 There's a big difference between sealed and not accessible. .data's API
 requires exposing an array, and there's no way to do this without leaking
 memory like a sieve in one way or another. However, if all you need is to
 iterate the contents, that's easy to do. I'm currently adding backwards
 iteration. Even indexing is fairly efficient (not yet implemented),
 particularly relative indexing (i.e. n from back or front).
 
 I haven't seen too many use cases yet where accessing the underlying array
 is important, nor has it come up on bugzilla. I've found one case in
 Phobos where appender was used as a stack. What's your example? What
 features does it have to support and how efficient does it have to be?
It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it.
Also, wouldn't it be less efficient if you _had_ to copy the array once you were done with the appender? That would seem to go against what appender is trying to do. - Jonathan M Davis
Feb 22 2012
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Wed, 22 Feb 2012 14:17:09 -0600, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:
 On Wednesday, February 22, 2012 14:12:07 Jonathan M Davis wrote:
 On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote:
 There's a big difference between sealed and not accessible. .data's  
API
 requires exposing an array, and there's no way to do this without  
leaking
 memory like a sieve in one way or another. However, if all you need  
is to
 iterate the contents, that's easy to do. I'm currently adding  
backwards
 iteration. Even indexing is fairly efficient (not yet implemented),
 particularly relative indexing (i.e. n from back or front).

 I haven't seen too many use cases yet where accessing the underlying  
array
 is important, nor has it come up on bugzilla. I've found one case in
 Phobos where appender was used as a stack. What's your example? What
 features does it have to support and how efficient does it have to be?
It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it.
Also, wouldn't it be less efficient if you _had_ to copy the array once you were done with the appender? That would seem to go against what appender is trying to do. - Jonathan M Davis
No, because the array doesn't actually exist until appender makes copy. Internally, appender is using a list of arrays to store the data (similar to a VList and string builders from other languages). So it's little o(2N) for both memory and speed; the current appender is much worse than that. In terms of actual performance, on a clean machine I'm substantially faster for N < 4k (thanks to a free list), about the same for things of a few k in size, and then as things get bigger the current appender tanks.
Feb 22 2012
parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
 No, because the array doesn't actually exist until appender 
 makes copy.
Will one be able to use the sort!()() algorithm directly on your appender, that is, without accessing/creating the underlying array? --jm
Feb 22 2012
parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
(And not talking about some cheesy insertion sort!!)

If you build an array once and for all, and all you want
is to do binary search on it later, it doesn't make sense to
allocate that big contiguous .data. I'd rather leave it
as an appender.

--jm


On Wednesday, 22 February 2012 at 23:22:35 UTC, Juan Manuel Cabo 
wrote:
 No, because the array doesn't actually exist until appender 
 makes copy.
Will one be able to use the sort!()() algorithm directly on your appender, that is, without accessing/creating the underlying array? --jm
Feb 22 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, February 23, 2012 01:38:05 Juan Manuel Cabo wrote:
 (And not talking about some cheesy insertion sort!!)
 
 If you build an array once and for all, and all you want
 is to do binary search on it later, it doesn't make sense to
 allocate that big contiguous .data. I'd rather leave it
 as an appender.
 
 --jm
 
 
 On Wednesday, 22 February 2012 at 23:22:35 UTC, Juan Manuel Cabo
 
 wrote:
 No, because the array doesn't actually exist until appender
 makes copy.
Will one be able to use the sort!()() algorithm directly on your appender, that is, without accessing/creating the underlying array?
If appender ends up with multiple arrays in it, then random access is no longer O(1) and is therefore unacceptable. As such, most sort algorithms wouldn't work with it. Also, your bit about using appender to pass an array around wouldn't work either, because it wouldn't simply be wrapper around an array anymore. - Jonathan M Davis P.S. Please don't top post. Replies should go _after_ the preceding message.
Feb 22 2012
next sibling parent "Robert Jacques" <sandford jhu.edu> writes:
On Wed, 22 Feb 2012 18:51:27 -0600, Jonathan M Davis <jmdavisProg gmx.com>
wrote:
 On Thursday, February 23, 2012 01:38:05 Juan Manuel Cabo wrote:
 (And not talking about some cheesy insertion sort!!)

 If you build an array once and for all, and all you want
 is to do binary search on it later, it doesn't make sense to
 allocate that big contiguous .data. I'd rather leave it
 as an appender.

 --jm


 On Wednesday, 22 February 2012 at 23:22:35 UTC, Juan Manuel Cabo

 wrote:
 No, because the array doesn't actually exist until appender
 makes copy.
Will one be able to use the sort!()() algorithm directly on your appender, that is, without accessing/creating the underlying array?
If appender ends up with multiple arrays in it, then random access is no longer O(1) and is therefore unacceptable. As such, most sort algorithms wouldn't work with it. Also, your bit about using appender to pass an array around wouldn't work either, because it wouldn't simply be wrapper around an array anymore. - Jonathan M Davis P.S. Please don't top post. Replies should go _after_ the preceding message.
Well, a VList (which is a list of arrays) is has O(1) amortized indexing. Actual performance of my implementation would be O(N / (4080/T.sizeof)), which isn't so bad. And anything under a page of ram would be O(1).
Feb 22 2012
prev sibling next sibling parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Thursday, 23 February 2012 at 00:51:38 UTC, Jonathan M Davis 
wrote:
[...]
 If appender ends up with multiple arrays in it, then random 
 access is no longer O(1) and is therefore unacceptable. As 
 such, most sort algorithms wouldn't work with it.
If all I want is binary search on a big appender, then it is O(k * n * log(n)), and that k right there doesn't bother me. Also, binary search is absolutely not cpu cache friendly to begin with.
 Also, your bit about using appender to pass an array around 
 wouldn't work either, because it wouldn't simply be wrapper
 around an array anymore.

 - Jonathan M Davis
Yeah, but I don't care about the underlying array. I care about multiple places referencing the same Appender. If I from any place that references it, it appends to the same appender. The Appender "array" has identity. Ranges do not: int[] bla = [1,2,3]; int[] ble = bla; ble ~= 4; assert(bla.length == 3); This is very easy to solve with appender. This is what happens in Java: ArrayList<Integer> bla = new ArrayList<Integer>(); bla.add(1); ArrayList<Integer> ble = bla; ble.add(2); //prints 2 System.out.println(Integer.toString(bla.size())); //prints 2 System.out.println(Integer.toString(ble.size())); (yikes, aint that verbose!) The ArrayList has identity. It is a class, so that it many variables reference the _same_ object. (this can be accomplished with structs too though, but not with ranges).
 P.S. Please don't top post. Replies should go _after_ the 
 preceding message.
Sorry, got it. --jm
Feb 22 2012
next sibling parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Thursday, 23 February 2012 at 01:36:32 UTC, Juan Manuel Cabo 
wrote:
 If all I want is binary search on a big appender, then it
 is O(k * n * log(n)), and that k right there doesn't
 bother me.
(Where binary search is of course O(log(n)) and accessing individual elements with the proposed Appender is O(N / (4080/T.sizeof)), so k == 4080/T.sizeof) --jm
Feb 22 2012
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, February 23, 2012 02:36:31 Juan Manuel Cabo wrote:
 Yeah, but I don't care about the underlying array. I care
 about multiple places referencing the same Appender. If I
 from any place that references it, it appends to the same
 appender. The Appender "array" has identity. Ranges do not:
 
 int[] bla = [1,2,3];
 int[] ble = bla;
 ble ~= 4;
 assert(bla.length == 3);
 
 This is very easy to solve with appender.
 This is what happens in Java:
 ArrayList<Integer> bla = new ArrayList<Integer>();
 bla.add(1);
 ArrayList<Integer> ble = bla;
 ble.add(2);
 //prints 2
 System.out.println(Integer.toString(bla.size()));
 //prints 2
 System.out.println(Integer.toString(ble.size()));
 
 (yikes, aint that verbose!)
 The ArrayList has identity. It is a class, so that it
 many variables reference the _same_ object.
 (this can be accomplished with structs too though, but
 not with ranges).
The D equivalent would really be Array, not Appender. I'm not sure that it's a great idea to use Appender as a container - particularly when there are types specifically intended to be used as containers. Appender is geared specifically towards array building (like StringBuilder in Java, except generalized for all arrays). If it's a container that you're looking for, then I really think that you should use a container. - Jonathan M Davis
Feb 22 2012
next sibling parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Wed, 22 Feb 2012 19:57:37 -0600, Jonathan M Davis <jmdavisProg gmx.com>
wrote:

 On Thursday, February 23, 2012 02:36:31 Juan Manuel Cabo wrote:
 Yeah, but I don't care about the underlying array. I care
 about multiple places referencing the same Appender. If I
 from any place that references it, it appends to the same
 appender. The Appender "array" has identity. Ranges do not:

 int[] bla = [1,2,3];
 int[] ble = bla;
 ble ~= 4;
 assert(bla.length == 3);

 This is very easy to solve with appender.
 This is what happens in Java:
 ArrayList<Integer> bla = new ArrayList<Integer>();
 bla.add(1);
 ArrayList<Integer> ble = bla;
 ble.add(2);
 //prints 2
 System.out.println(Integer.toString(bla.size()));
 //prints 2
 System.out.println(Integer.toString(ble.size()));

 (yikes, aint that verbose!)
 The ArrayList has identity. It is a class, so that it
 many variables reference the _same_ object.
 (this can be accomplished with structs too though, but
 not with ranges).
The D equivalent would really be Array, not Appender. I'm not sure that it's a great idea to use Appender as a container - particularly when there are types specifically intended to be used as containers. Appender is geared specifically towards array building (like StringBuilder in Java, except generalized for all arrays). If it's a container that you're looking for, then I really think that you should use a container. - Jonathan M Davis
StringBuilder in .Net is implemented using lists and doesn't expose iteration nor indexing; why are we worrying about the indexing and container performance of D's appender?
Feb 22 2012
parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, February 22, 2012 20:07:37 Robert Jacques wrote:
 StringBuilder in .Net is implemented using lists and doesn't expose
 iteration nor indexing; why are we worrying about the indexing and
 container performance of D's appender?l
Because we're losing something that we currently have and which is potentially useful. Now, improving the performance of appending may outweigh that, but if we don't need to lose it, then we shouldn't.
From the sounds of it though, we don't have much choice if we want to really 
improve Appender's appending performance. - Jonathan M Davis
Feb 22 2012
prev sibling parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Thursday, 23 February 2012 at 01:57:49 UTC, Jonathan M Davis 
wrote:
 The D equivalent would really be Array, not Appender.
Array!T in D is ref counted and more geared towards T being a struct. And I had big trouble sorting it with sort!()() in D2.056, so I made my own sort just to be able to use Array!(T). I know that that situation will not remain forever (and I didn't check if it was already fixed in D2.058).
 I'm not sure that it's a great idea to use Appender as a
 container - particularly when there are types
 specifically intended to be used as containers. Appender is 
 geared specifically  towards array building (like StringBuilder 
 in Java, except generalized for all
 arrays). If it's a container that you're looking for, then I 
 really think that
 you should use a container.

 - Jonathan M Davis
If Appender supports the range interface, then it is a container. Someone will use it that way, because in the real world people take the things for what they are, not for what they are named. Appender can work with T classes well. Contains items. It would be GC managed (as opposed to Array!T). So it is a container. If it is not O(1) to access, it should be said big in the ddoc though. It is a recurrent trend in your posts, that you post just because you have some ideallistic concern or opinion. If we are going to get ideallistic, this is an extract of a poem by Jorge Luis Borges (argentinian writer) that illustrates my point: If (as the Greek states in the Cratilo) the name is the archetype of the thing, in the letters of rose it is the rose and all the Nile is in the word Nile Si como escribió el griego en el Crátilo, el nombre es arquetipo de la cosa, en el nombre de rosa está la rosa y todo el Nilo en la palabra Nilo. --Jorge Luis Borges --jm
Feb 22 2012
parent reply "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Thursday, 23 February 2012 at 02:12:20 UTC, Juan Manuel Cabo 
wrote:

 If we are going to get ideallistic [..]
I'm sorry. I went over the top. I apollogize. ..I won't post for a while. This thread is almost poping a vein in my neck.. Passion can do that! I love D. Love all your good work guys!!! --jm
Feb 22 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Feb 23, 2012 at 04:00:04AM +0100, Juan Manuel Cabo wrote:
[...]
 I'm sorry. I went over the top. I apollogize.
I apologize too, for some of the inflammatory things I said in the heat of the moment in some of my replies to this thread.
 ..I won't post for a while.
 This thread is almost poping a vein in my neck..
Heh, several times I had to stop myself from hitting Send, take a deep breath, clear my head, and go back to edit out my inflammatory remarks before posting it.
 Passion can do that!
 I love D. Love all your good work guys!!!
[...] Me too. D is the closest to what I've always wanted in an ideal programming language. It's not perfect, and its implementation still has a ways to go, but it already far surpasses all other languages that I've looked at in terms of what I consider important in an ideal language. I'm loving it so much I just can't convince myself to go back to C++ anymore in my personal projects, except for maintenance. (At work I just grit my teeth and comfort myself that they're paying me for it, so I'll tolerate C/C++. :P) T -- Без труда не выловишь и рыбку из пруда.
Feb 22 2012
prev sibling parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Thursday, 23 February 2012 at 01:36:32 UTC, Juan Manuel Cabo 
wrote:
 On Thursday, 23 February 2012 at 00:51:38 UTC, Jonathan M Davis 
 wrote:
 [...]
 If appender ends up with multiple arrays in it, then random 
 access is no longer O(1) and is therefore unacceptable. As 
 such, most sort algorithms wouldn't work with it.
If all I want is binary search on a big appender, then it is O(k * n * log(n)), and that k right there doesn't bother me. Also, binary search is absolutely not cpu cache friendly to begin with.
 Also, your bit about using appender to pass an array around 
 wouldn't work either, because it wouldn't simply be wrapper
 around an array anymore.

 - Jonathan M Davis
Yeah, but I don't care about the underlying array. I care about multiple places referencing the same Appender. If I from any place that references it, it appends to the same appender. The Appender "array" has identity. Ranges do not: int[] bla = [1,2,3]; int[] ble = bla; ble ~= 4; assert(bla.length == 3); This is very easy to solve with appender. This is what happens in Java: ArrayList<Integer> bla = new ArrayList<Integer>(); bla.add(1); ArrayList<Integer> ble = bla; ble.add(2); //prints 2 System.out.println(Integer.toString(bla.size())); //prints 2 System.out.println(Integer.toString(ble.size())); (yikes, aint that verbose!) The ArrayList has identity. It is a class, so that it many variables reference the _same_ object. (this can be accomplished with structs too though, but not with ranges).
I meant ref counted structs.
 P.S. Please don't top post. Replies should go _after_ the 
 preceding message.
Sorry, got it. --jm
Feb 22 2012
prev sibling parent "Juan Manuel Cabo" <juanmanuel.cabo gmail.com> writes:
On Thursday, 23 February 2012 at 00:51:38 UTC, Jonathan M Davis 
wrote:
 P.S. Please don't top post. Replies should go _after_ the 
 preceding message.
P.S: You are right though, that it wouldn't be O(1) anymore and it should be said big in the documentation that it is amortized. --jm
Feb 22 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Feb 22, 2012 at 07:51:27PM -0500, Jonathan M Davis wrote:
[...]
 P.S. Please don't top post. Replies should go _after_ the preceding message.
Answer: Because it breaks the normal flow of conversation. Question: Why is it bad to top-post? T -- Why waste time learning, when ignorance is instantaneous? -- Hobbes, from Calvin & Hobbes
Feb 22 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
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.
Feb 21 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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?
Feb 21 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 20:01, H. S. Teoh a crit :
 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?
I'm not aware of any language that have both template and RTTI. So I'm clueless about this.
Feb 22 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Feb 22, 2012 at 11:18:13AM +0100, deadalnix wrote:
 Le 21/02/2012 20:01, H. S. Teoh a crit :
[...]
Does RTTI handle template methods?
I'm not aware of any language that have both template and RTTI. So I'm clueless about this.
Doesn't C++ have RTTI? T -- "The number you have dialed is imaginary. Please rotate your phone 90 degrees and try again."
Feb 22 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 22/02/2012 16:33, H. S. Teoh a crit :
 On Wed, Feb 22, 2012 at 11:18:13AM +0100, deadalnix wrote:
 Le 21/02/2012 20:01, H. S. Teoh a crit :
[...]
 Does RTTI handle template methods?
I'm not aware of any language that have both template and RTTI. So I'm clueless about this.
Doesn't C++ have RTTI?
You could argue that, but IMO, it look more like a stub than a real functionnality of the language.
Feb 22 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/21/2012 07:57 PM, deadalnix wrote:
 opDispatch is nice, but rather incomplete. It doesn't handle template
 methods for example.
It surely does. struct S{ template opDispatch(string op){ auto opDispatch(T...)(T args){ writeln(op, args); } } } void main() { S s; s.foo(1,2.0,"bar"); }
Feb 25 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 25/02/2012 14:10, Timon Gehr a crit :
 On 02/21/2012 07:57 PM, deadalnix wrote:
 opDispatch is nice, but rather incomplete. It doesn't handle template
 methods for example.
It surely does. struct S{ template opDispatch(string op){ auto opDispatch(T...)(T args){ writeln(op, args); } } } void main() { S s; s.foo(1,2.0,"bar"); }
Explicit template argument, I should have precised.
Feb 26 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 02/26/2012 01:12 PM, deadalnix wrote:
 Le 25/02/2012 14:10, Timon Gehr a crit :
 On 02/21/2012 07:57 PM, deadalnix wrote:
 opDispatch is nice, but rather incomplete. It doesn't handle template
 methods for example.
It surely does. struct S{ template opDispatch(string op){ auto opDispatch(T...)(T args){ writeln(op, args); } } } void main() { S s; s.foo(1,2.0,"bar"); }
Explicit template argument, I should have precised.
Please validate your non-trivial claims before you make them. Otherwise, time gets wasted discussing non-existent problems. A good way to ensure what you claim is true is to always provide a minimal code example that demonstrates the claim, after having verified that the code indeed exposes the problematic behavior when compiled with the latest version of the reference compiler.
Feb 26 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/02/2012 21:57, Andrei Alexandrescu a écrit :
 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 ... } Andrei
Why not using std.rtti and generate run time reflection info from compile time reflexion capability ? This would enable that feature without writing it the language, so it would prevent to bloat the binary when size matter and reflexion isn't needed.
Feb 21 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 4:34 AM, deadalnix wrote:
 Le 20/02/2012 21:57, Andrei Alexandrescu a écrit :
 class DRoxException : Exception
 {
 mixin(enableRTTI);
 ... normal implementation ...
 }


 Andrei
Why not using std.rtti and generate run time reflection info from compile time reflexion capability ? This would enable that feature without writing it the language, so it would prevent to bloat the binary when size matter and reflexion isn't needed.
Yes, that's the idea. Andrei
Feb 21 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Feb 20, 2012 at 02:45:25PM -0500, 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.
[...] I agree, we should use tupleof for this purpose. I always view Variant has a last resort hack that we should only use if there's absolutely no other way around it. The lack of compile-time type checking on Variant makes it prone to nasty issues like everything compiling with no error and then blowing up at runtime. This is especially bad in code related to exceptions, because they are by definition exceptional, so problems like these often won't get caught until they crash the customer's production server. T -- Right now I'm having amnesia and deja vu at the same time. I think I've forgotten this before.
Feb 20 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
All of this heated debate has led me to reconsider our whole concept of
exceptions. It seems that we're squabbling over little details in
existing paradigms. But what of the big picture? What *is* an exception
anyway? We all know the textbook definition, but clearly something is
missing since we can't seem to agree how it should be implemented.


DEFINITION

So I'd like to propose this definition: an exception is an abnormal
condition that causes a particular operation not to be completed, *but
which may have one or more ways of recovery*.

I'm not interested in problems with no method of recovery: we might as
well just terminate the program right there and call it a day. The fact
that there's such a thing as try-catch means that we're interested in
problems that we have *some* way of handling.

Before I proceed, I'd like to propose that we temporarily forget about
the current implementation details of exceptions. Let's for the time
being forget about class hierarchies, try-catch, Variant hashes, etc.,
and let's consider the *concept* of an exception.

In discussing what is an exception, we can jump into the nitty-gritty
details, and argue all day about how to handle the particulars of case X
and how to reconcile it with case Y, but I'd like to approach this from
a different angle.  Yes, if we know the nitty-gritty, then we can deal
with it in a nitty-gritty way.  Let it suffice to say that sometimes, we
*want* to get into the nitty-gritty of an exception, so in any
implementation there should be a way to do this.

But let's talk about the generics.  What if we *don't* know the
nitty-gritty? Can we still say something useful about exception handling
*independently* of the details of the exception? If module X calls
module Y and a problem happens, but X doesn't understand the
implementation details of Y or how the problem relates to it, can X
still do something meaningful to handle the problem?

To this end, I decided to categorize exceptions according to their
general characteristics, rather than their particulars. In making this
categorization, I'm not looking for artificial, ivory-tower
idealizations of exception types; I'm looking for categories that would
allow us to handle an unknown exception meaningfully. In other words,
categories for which *there are recovery methods are available* to
handle that exception. I don't care whether it's an I/O error, a network
error, or an out-of-memory error; what I want to know is, *what can I do
about it*?


TRANSITIVITY

One more point before I get into the categories themselves: in finding
useful categorizations of exceptions, it's useful to find categories
which are *transitive*. And by that I mean, a category C is transitive
if an exception E in this category remains in the same category when the
stack unwinds from Y, where E was first thrown, to X, which called Y.
For example, Andrei's is_transient property constitutes a transitive
category. If X calls Y and Y experiences a transient error, then by
extension X has also experienced a transient error. Therefore, the
is_transient category is transitive.

Why should we care if an exception category is transitive? Because it
allows us to make meaningful decisions far up the call stack from where
the actual problem happened. If an exception category is not transitive,
then as soon as the stack unwinds past the caller of the function which
threw the exception, we can no longer reasonably assume that the
exception still belongs to the same category as before. An illegal input
error, for example, is not necessarily transitive: if X calls Y and Y
calls Z, and Z says "illegal input!", then it means Y passed bad data to
Z, but it doesn't necessarily mean that X passed bad data to Y. It may
be Y's fault that the input to Z was bad. So trying to recover from the
bad data when the stack has unwound to X is not useful: X may not have
any way of changing what Y passes to Z. But if Y merely takes the data
passed to it by X and hands it to Z, then the illegal input *is*
transitive up to X. In *that* case, X can meaningfully attempt a
recovery by fixing the bad data. But if X was the one who created that
data, then X's caller cannot do anything about it. So input errors are
only conditionally transitive -- up to the origin of the input data.


CATEGORIES

Here are the categories I've found so far. I don't claim this is
anywhere near complete, but I'd like to put it on the table so that
y'all can discuss about it, and hopefully refine this idea better. Each
category is associated with a list of recovery actions that are
meaningful for that category. Note that it doesn't mean that *every*
exception in that category will have all listed recovery actions
available; some recovery actions aren't possible in all cases. In an
implementation, there would need to be a way to indicate which of the
listed recovery actions are actually possible given a particular
exception.

Also, the recovery actions are deliberately generic. This will be
explained later, but the idea is to let generic code decide on a general
course of action without knowing the details of the actual
implementation, which is determined by the code that triggered the
original condition, or by an intermediate handler midway up the call
stack who knows how to deal with the condition.


INPUT ERRORS:

Definition: errors that are caused by passing bad input to function X,
such that X doesn't know how to compute a meaningful output or execute a
meaningful operation.

Transitivity: conditional, only up to origin of the input data.

Recovery actions:
- Attempt to repair the input and try the operation again. Only possible
  if there exists a mechanism of repairing the input.

- Skip over the bad input and attempt to continue. Only applicable if
  the input is a list, say, and the program can still function (albeit
  not to the full extent) without the erroneous input. Otherwise, this
  recovery action is meaningless.


COMPONENT FAILURE:

Definition: the operation being attempted depends on the normal
functioning of sub-operations X1, X2, ..., but one (or more) of them
isn't functioning normally, so the operation can't be completed.

Transitivity: Yes. If X calls Y and Y calls Z, and one of Z's
subcomponents failed, then from Y's perspective, Z has also failed.

Recovery actions:
- Retry the operation using an alternative component, if one is
  available. For example, if X is a DNS resolver and Y is a
  non-responding DNS server, then X could try DNS server Z instead.
  Transitively, if X can't recover (doesn't know an alternative DNS
  server to try), then X's caller can attempt to bypass X and use W
  instead, which looks up a local DNS cache, say.  (Note that the
  high-level code that handles a component failure does not need to know
  the details of how this component swapping is implemented, or exactly
  what is being swapped in/out. It only knows that this is a possible
  course of action.)


CONSISTENCY ERROR:

Definition: the operation being attempted depends on a suboperation X,
which is operating normally, however, the result returned by X fails a
consistency check. (I'm not sure if this warrants separate treatment
from component failure; they are very similar and the recovery actions
are also very similar. Maybe they can be lumped together as a single
category.)

Examples: Numerical overflow/underflow, which would throw off any
remaining calculations.

Transitivity: Yes. If X calls Y and Y calls Z, and Y discovers the Z
produced an inconsistent result, then by extension Y would have also
produced an inconsistent result had it decided to blindly charge ahead
with the operation.

Recovery actions:
- Retry the operation by using an alternative component, if available.
  For example, a numerical overflow might be repaired by switching to a
  BigNum library for the troublesome part of the computation.


LACK OF RESOURCES:

Definition: the operation being attempted would have completed normally,
had there been sufficient resources available, but there aren't, so it
can't continue.

Transitivity: Yes. If X calls Y and Y runs out of resources to finish,
then by extension X doesn't have the resources to finish either.

Recovery actions:
- Free up some resources and try again. This one is debatable, since it
  may not be clear which resources need to be freed up, or whether they
  *can* be freed at all. If it's a full disk, for example, it would be
  unwise to just go and randomly delete files. But some cases can be
  handled, e.g., if memory runs out, trigger the GC. (But presumably the
  GC does this automatically, so this may not be an actual use case that
  needs manual handling.) All in all, this category may not be easy to
  recover from, so it may be of limited utility.


TRANSIENT ERROR:

Definition: the operation depends on component X, which is known to
sometimes fail. Example: a network server may sometimes go down due to
intermittent network problems, timeouts, etc..

Transitivity: Yes. If operation X calls operation Y and Y has a
transient error, then X also has a transient error by extension.

Recovery actions:
- Retry the operation: it may succeed next time.

Sidenote: Here I'd like to say that at first, I was very skeptical about
Andrei's is_transient proposal, because I didn't have the proper context
to understand its utility. I felt that something was missing.  And that
missing something was that is_transient is but a part of a larger
framework of generic exception categories. Without this larger context,
the value of is_transient is not immediately obvious. It seems like just
an arbitrary thing out of the blue. How could it possibly be useful??
But when viewed as part of a larger system, is_transient can be seen to
be an extremely useful concept: it is a *transitive* category, which
means you can do something meaningful with it at any point up the call
stack.


CREDENTIALS ERROR:

Definition: there's no problem with the input, and all subcomponents are
functioning properly, but because of lack of (or improper) credentials,
the operation could not be completed.

Transitivity: Yes(?). Not sure about this one, not because it doesn't
fit the definition, but because it's unclear how to correctly handle the
recovery action. A single operation may consist of many sub-operations,
each requiring a different set of credentials. Just because one of the
sub-operations raises a credentials error doesn't mean the exception
handler knows where to find alternative credentials, or even what kind
of credentials they are.

Recovery actions:
- Retry the operation with different credentials. E.g., prompt user for
  a different password. But I'm unsure if/how this can be generally
  implemented, as described above.


These are all the general categories I found. There may be more.


IMPLEMENTATION

Alright. All of this grand talk about generics and categories is all
good, but how can this actually be implemented in real life?

The try-catch mechanism is not adequate to implement all the recovery
actions described above. As I've said before when discussing what I
called the Lispian model, some of these recovery actions need to happen
*in the context where the exception was thrown*. Once the stack unwinds,
it may not be possible to recover anymore, because the execution context
of the original code is gone.

One peculiarity about Andrei's is_transient is that you *can* re-attempt
the operation after unwinding the stack. Which is what makes it useful
in the current try/catch exception system that we have.

But not all recovery actions can be implemented this way. Some, such as
repair bad input, or try alternate component, makes no sense after the
stack has unwound: the execution context of the failing component and
its caller is long gone; to try an alternate component would require
painstaking passing of retry information all the way down the function
call chain, polluting normal function parameters with retry parameters
and producing very ugly code. Repair bad input, in particular, *must* be
done before the stack unwinds past the origin of the input, otherwise
it's impossible to correct it.

This is where the Lispian model really shines. To summarize:

1) When we encounter a problem, we raise a Condition (instead of throw
an exception immediately).

2) Every Condition is associated with a set of recovery actions. These
actions are generic; basically we're mapping each exception category to
a Condition. The raiser of the Condition will specialize each recovery
action with code specific to itself.

3) High-level code may register Handlers (in the form of a delegate) for
particular Conditions.  These registrations are limited by scope; once
the function registering the handler exits, any handlers it registered
are removed from the system. The handler registered closest to the
origin of a Condition has priority over other matching handlers.

4) When a Condition is raised, the condition-handling system first
checks a list of registered condition handlers to see if any handler is
willing to handle the condition. The handler is passed the Condition
with its associated set of recovery options. The handler decides, based
on high-level information, which recovery action to take, and informs
the condition-handling system. The recovery action is then executed *in
the context of the function that raised the Condition*, *without
unwinding the stack*. If no handler is found, or the handler decides to
abort the operation, then the condition-handling system converts the
Condition into an exception and throws it. A function higher up the call
chain may decide to catch this exception and raise a corresponding
Condition, to allow (other) handlers to deal with the situation at the
higher level. If nothing is caught or all attempts to fix the problem
failed, we eventually percolate up the call stack to the top and fail
the program.

Advantages of this system:

- Complex recovery actions are possible, because we don't unwind the
  stack until we decide to abort the operation after all.

- Recovery actions run in the context where failure is first seen,
  thereby taking advantage of the immediate context to recover in a
  specific way.

- High-level code gets to make decisions about which recovery action to
  pursue (via the delegate handler). It gets to do this *without* need
  to know the nitty-gritty of the low-level code; it is given the
  generic problem category and a list of generic recovery actions that
  can be attempted. The low-level code implements various recovery
  options, the high-level code chooses between them.

- If nobody knows how to handle the situation, we unwind the stack, as
  in the traditional try/catch model.

- If an intermediate function up the stack has a way to deal with the
  situation, it can catch the associated exception and raise a Condition
  that has recovery actions *run at its level*. The high-level delegate
  still gets to make decisions, but now the recovery actions are run at
  a higher level than the original locus of the problem. In some cases,
  this is a better position for attempting recovery. E.g., a network
  timeout may be seen at the packet level, but to repair the problem
  requires reconnecting from, say, the HTTP request level, so we need to
  unwind the stack up until that point. This is actually superior to the
  try/catch mechanism, because at the HTTP request level, we don't
  necessarily have enough context to decide what course of action to
  take; but by passing the condition a higher-level delegate, it can
  make decisions the HTTP module can't make, and the HTTP module can
  correct the problem without unwinding the stack all the way to where
  the delegate was registered.


In a previous post, I had a skeletal implementation of this system, but
the major problem was that it was too specialized: every piece of code
that wanted to implement recovery needed to define a specific Condition
with its own set of recovery strategies, leading to reams and reams of
code just to achieve something simple. Furthermore, the high-level
handler needed to know the nitty-gritty low-level details of what each
Condition represented and what options are available to deal with it, so
there was no way to write a *generic* handler that can decide what to do
with conditions whose details it knows nothing about.

But by using generic exception categories, we can finally get rid of
that bloat and still be able to implement problem-specific recovery
strategies. The high-level code need only know which generic category
the Condition belongs to, and based on this it knows which recovery
actions are available. It never needs to know what the details are
(unless it's intended to be a very specific handler dealing with a very
specific condition whose details it knows). The low-level code provides
the implementation of the recovery actions by implementing the generic
interface of that particular category.


Currently, I'm still unsure whether Conditions and Exceptions should be
unified, or they should be kept separate; deadalnix recommended they be
kept separate, but I'd like to open it for discussion.


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.


T

-- 
Life is complex. It consists of real and imaginary parts. -- YHL
Feb 21 2012
next sibling parent dennis luehring <dl.soluz gmx.net> writes:
Am 21.02.2012 09:15, schrieb H. S. Teoh:
 Sorry for this super-long post
perfect length, helps to clean the brain and focus on the real problems again
Feb 21 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
First great post. Don't be sorry, it is insightful. The Condition 
catgory make a lot of sense to me.

Le 21/02/2012 09:15, H. S. Teoh a crit :
 Currently, I'm still unsure whether Conditions and Exceptions should be
 unified, or they should be kept separate; deadalnix recommended they be
 kept separate, but I'd like to open it for discussion.
I think we could reconciliate the 2 worlds. Condition may provide property Exception exception() so you can get the Exception. Eventually, the Condition can return itself so it is the Exception. This Exception can be used by the handler to get information about the problem more specifically (if required). The Exception can also be used to throw it, if no handler is provided, or if the handler decide it is the right thing to do. I suggested this mecanism should be implemented into std.condition and still think this is the way to go. Addition to the language itself should be as limited as possible (ie only occur if the compiler can take advantage of this, for example to optimize, or if this isn't implementable using the current state of the language). This mecanism is not intended to replace Exception, but to provide a more convenient, higher level (and probably more performant) than the Exception mecanism. We fail back to Exceptions if this mecanism fail.
Feb 21 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 11:55, deadalnix a crit :
 First great post. Don't be sorry, it is insightful. The Condition
 catgory make a lot of sense to me.

 Le 21/02/2012 09:15, H. S. Teoh a crit :
 Currently, I'm still unsure whether Conditions and Exceptions should be
 unified, or they should be kept separate; deadalnix recommended they be
 kept separate, but I'd like to open it for discussion.
I think we could reconciliate the 2 worlds. Condition may provide property Exception exception() so you can get the Exception. Eventually, the Condition can return itself so it is the Exception. This Exception can be used by the handler to get information about the problem more specifically (if required). The Exception can also be used to throw it, if no handler is provided, or if the handler decide it is the right thing to do. I suggested this mecanism should be implemented into std.condition and still think this is the way to go. Addition to the language itself should be as limited as possible (ie only occur if the compiler can take advantage of this, for example to optimize, or if this isn't implementable using the current state of the language). This mecanism is not intended to replace Exception, but to provide a more convenient, higher level (and probably more performant) than the Exception mecanism. We fail back to Exceptions if this mecanism fail.
I come up with a sample code with the transient case. What do you think ?
Feb 21 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-02-21 09:15, H. S. Teoh wrote:
 All of this heated debate has led me to reconsider our whole concept of
 exceptions. It seems that we're squabbling over little details in
 existing paradigms. But what of the big picture? What *is* an exception
 anyway? We all know the textbook definition, but clearly something is
 missing since we can't seem to agree how it should be implemented.

  ...
I think I like the idea in general. -- /Jacob Carlborg
Feb 21 2012
prev sibling next sibling parent reply "Vincent" <thornik gmail.com> writes:
On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei 
Alexandrescu wrote:
 From experience I humbly submit that catching by type is most 
 of the time useless.
Completely disagree. Types allow to control place for "catch". Say, some deeply nested function catches its own exceptions, while outer function catches the rest - exceptions higher in hierarchy. But to have benefit you have to create exceptions hierarchy - this is the main point.
Feb 21 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 11:40, Vincent a écrit :
 On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei Alexandrescu wrote:
 From experience I humbly submit that catching by type is most of the
 time useless.
Completely disagree. Types allow to control place for "catch". Say, some deeply nested function catches its own exceptions, while outer function catches the rest - exceptions higher in hierarchy. But to have benefit you have to create exceptions hierarchy - this is the main point.
Well, we must consider that the only thing we have to select Exception if the type. But do we really want to catch on type, or do we use the type because we lack of a better mecanism select exceptions we want to handle ? A new type should be added only if it make sense to provide additionnal informatiosn about the issue faced. The pertienent information will depend on the issue, so some issue will require new types of Exception. Plus, sometime, things doesn't fit in a Tree shape. But creating Exception just to catch specifically thoses don't make a lot of sense. This is why several person proposed ways to add a way to select more precisely what we want to catch.
Feb 21 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/21/12 4:40 AM, Vincent wrote:
 On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei Alexandrescu wrote:
 From experience I humbly submit that catching by type is most of the
 time useless.
Completely disagree. Types allow to control place for "catch". Say, some deeply nested function catches its own exceptions, while outer function catches the rest - exceptions higher in hierarchy. But to have benefit you have to create exceptions hierarchy - this is the main point.
As the next hundreds of messages discuss, matters are not all that simple :o). Andrei
Feb 21 2012
parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, February 21, 2012 08:15:29 Andrei Alexandrescu wrote:
 On 2/21/12 4:40 AM, Vincent wrote:
 On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei Alexandrescu wrote:
 From experience I humbly submit that catching by type is most of the
 time useless.
Completely disagree. Types allow to control place for "catch". Say, some deeply nested function catches its own exceptions, while outer function catches the rest - exceptions higher in hierarchy. But to have benefit you have to create exceptions hierarchy - this is the main point.
As the next hundreds of messages discuss, matters are not all that simple :o).
It probably should be though. I think that there's a lot of overthinking going on here. We need to have our exceptions organized into a hierarchy so that you can catch them based on what went wrong. That is the main thing that we're missing IMHO. Adding extra capabilities along side that is fine as long as they make sense. Adding a Variant[string] property for the purpose of putting non-standard information in your exceptions which don't merit a new exception type makes some sense. But if it makes sense to catch an exception based on that information, then it probably merits a new type. Adding improved string formatting cabilities also makes some sense. But I don't think that we need to drastically overhaul how exceptions work, and a lot of this discussion is straying into left field. - Jonathan M Davis
Feb 21 2012
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
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. You made some good points there, and had an interesting idea, so I'll just comment on that.
 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. 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? 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? artur
Feb 21 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Feb 21 2012
parent reply deadalnix <deadalnix gmail.com> writes:
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 ?
Feb 21 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Feb 21 2012
parent reply deadalnix <deadalnix gmail.com> writes:
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 ?
Feb 21 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Feb 21 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 21/02/2012 20:00, H. S. Teoh a crit :
 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.
Yes, I'm aware of the problem and it is real. I have no good solution for it now.
 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.
Yes indeed. Exception should provide data about what the problem IS, condition on what are option to recover.
 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.
Agreed.
 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?
It is exactly what lead me to create template for such cases. Additionnaly, thoses templates should include a way to generate the Exception, but I ran into a strangeness of D when exprimenting with and did had time to come up with something working for that. The condition handling and the actual operation must be separated to avoid code duplication and ensure separation of concerns. The current design allow to create new Conditions, in the lib but also in user code. If we can find a way to handle properly the Exception crazy casting problem we have here a very nice way to handle errors.
Feb 21 2012
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 08:56:03PM +0100, deadalnix wrote:
 Le 21/02/2012 20:00, H. S. Teoh a crit :
[...]
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.
Yes, I'm aware of the problem and it is real. I have no good solution for it now.
I have an idea. What if handlers took *two* arguments, a Condition, and a (possibly derived) Exception object? The raise system would then match conditions to handlers by both the Condition type and derived Exception type. The handler then has access to the particular derived object that it wants to handle without needing to downcast. So it sorta simulates a catch(DerivedException). It's a bit harder to implement, though... we'll need to store TypeInfo for each handler and match it up with the Condition being raised. But I'm assuming this isn't too much more than what the runtime is already doing with Exceptions anyway, so performance-wise it should be acceptable. And while we're at it, I think we should just stop pretending this is still the same system as Lisp, and just rename Condition to RecoveryStrategy or RecoveryOpts or something along those lines, since that's what it is. [...]
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.
Yes indeed. Exception should provide data about what the problem IS, condition on what are option to recover.
Yeah, I think it makes sense to rename Condition to RecoveryStrategy or RecoveryOpts. The recovery choices aren't a "condition"; the Exception object is the condition, the choices are recovery strategies. [...]
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?
It is exactly what lead me to create template for such cases. Additionnaly, thoses templates should include a way to generate the Exception, but I ran into a strangeness of D when exprimenting with and did had time to come up with something working for that. The condition handling and the actual operation must be separated to avoid code duplication and ensure separation of concerns.
Yep.
 The current design allow to create new Conditions, in the lib but also
 in user code.
 
 If we can find a way to handle properly the Exception crazy casting
 problem we have here a very nice way to handle errors.
Would it be possible to add that second argument to handlers? I think that will solve the problem. But it will take a bit more effort to implement. T -- "How are you doing?" "Doing what?"
Feb 21 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 22/02/2012 08:32, H. S. Teoh a crit :
 On Tue, Feb 21, 2012 at 08:56:03PM +0100, deadalnix wrote:
 Le 21/02/2012 20:00, H. S. Teoh a crit :
[...]
 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.
Yes, I'm aware of the problem and it is real. I have no good solution for it now.
I have an idea. What if handlers took *two* arguments, a Condition, and a (possibly derived) Exception object? The raise system would then match conditions to handlers by both the Condition type and derived Exception type. The handler then has access to the particular derived object that it wants to handle without needing to downcast. So it sorta simulates a catch(DerivedException).
Well I have no idea how to implement that. Let's put some thinking into this, it certainly an option. However, this require to create the Exception, even if in many cases, it will not be usefull. About the name, it matters, but isn't the big issue here. When we have something working well, we could think about names. As the concept may be changed again, naming isn't that important.
Feb 22 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Feb 22, 2012 at 11:14:11AM +0100, deadalnix wrote:
 Le 22/02/2012 08:32, H. S. Teoh a crit :
[...]
I have an idea. What if handlers took *two* arguments, a Condition,
and a (possibly derived) Exception object? The raise system would
then match conditions to handlers by both the Condition type and
derived Exception type. The handler then has access to the particular
derived object that it wants to handle without needing to downcast.
So it sorta simulates a catch(DerivedException).
Well I have no idea how to implement that. Let's put some thinking into this, it certainly an option. However, this require to create the Exception, even if in many cases, it will not be usefull.
I was thinking about using TypeInfo to match stuff up at runtime. But I need to work through the details first to see if it's actually implementable. This seems to be an area where language support would help a lot. Though it's better if we can do it without language support, so that we can get this thing up and running and test it with real-life usage before making language changes that may not benefit in the long run. :)
 About the name, it matters, but isn't the big issue here. When we have
 something working well, we could think about names. As the concept may
 be changed again, naming isn't that important.
Names are just identifiers, sure, but it does help to have a useful name to help us think about the problem from a useful angle. For example if we renamed Exception to Bug, the mechanics of the it (try, throw, catch) would still work the same, but we would be likely to use it in the wrong ways because "Bug" doesn't help us think about it in a useful way. T -- Why ask rhetorical questions? -- JC
Feb 22 2012
prev sibling parent reply "Kapps" <opantm2+spam gmail.com> writes:
On Wednesday, 22 February 2012 at 07:30:54 UTC, H. S. Teoh wrote:
 I have an idea. What if handlers took *two* arguments, a 
 Condition, and
 a (possibly derived) Exception object? The raise system would 
 then match
 conditions to handlers by both the Condition type and derived 
 Exception
 type.
 *snip*
The Condition approach you propose is fairly similar to what I was thinking of, as an approach to fixing Problems that occur (not necessarily exceptions). One of the fundamental problems I have with exceptions is that, despite intended to be recoverable as opposed to Errors, they are not recoverable. The operation still fails when an exception is thrown, and you have to restart all over again (even if it's something easily recoverable from). The other issue, is that exceptions are just poorly designed for multithreaded and event based applications. If you queue some work to be done, you likely won't even know if an exception is thrown. If you have a callback / event based approach, you have to do the equivalent of returning error codes (something exceptions were designed to avoid), except with passing in error codes instead. An example is .NET forcing all asynchronous operations to have a Begin____ and an End____ with the End____ rethrowing whatever problems occurred during the operation (even if they were easily solveable if the caller simply had immediate access to handle them). The first one, is solveable with something similar to Conditions. A Problem would generally be a state issue. For example, you're writing a simple file transfer tool. The client authenticates with the server and gets a session ID. The server then starts transferring to the client, when suddenly the client loses connection for a moment (maybe they lost Wi-Fi signal, maybe someone unplugged their router momentarily, maybe they just had a bad route). With the exception based approach, the only way to notify this is to stop the entire operation, reconnect, reauthenticate, create a new session, negotiate what was being transfered, negotiate how far in the transfer completed, and other annoying things. Instead, we could register a handler to solve network problems. At the lowest level, the socket could have a handler that attempts to reconnect if any socket operations fail due to a connection error. Then, the transfer protocol could have a handler that, if the dependent socket reconnect handler is successful, notifies the server of the session key (and maybe the last packet it received) and carries on seamlessly without having to do a bunch of work. If the socket reconnect fails, the network problem handler reports a failure, and the transfer protocol handler does not get executed. If there is no transfer protocol handler, or there is one that basically says a solution is not possible, the same happens. Instead, that Problem is executed into an Exception, as the operation could not continue. (Unfortunately, unless you have a global problem handler for the entire transfer operation, this still suffers from the second issue about how those exceptions do not get carried up to the caller that starte the operation.) The transfer approach might not be the greatest example, but it demonstrates something that (IMO) Exceptions are misused for, which affects the design of Exceptions themselves as it attempts to hammer a solution they're not great for in with them.
Feb 22 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Feb 23, 2012 at 04:37:37AM +0100, Kapps wrote:
[...]
 The Condition approach you propose is fairly similar to what I was
 thinking of, as an approach to fixing Problems that occur (not
 necessarily exceptions). One of the fundamental problems I have with
 exceptions is that, despite intended to be recoverable as opposed to
 Errors, they are not recoverable. The operation still fails when an
 exception is thrown, and you have to restart all over again (even if
 it's something easily recoverable from).
Exactly!! As soon as the stack unwinds, you've just left behind the very context that would have been the best position to fix problems from. In a properly-written program, the caller of the function treats the throwing function as a black box, so once the stack unwinds back to the caller, there's no more possibility of repairing the problem. The best you can do is to repeat the operation from scratch and hope it works this time. That's what I like so much about the Lisp approach. The high-level code registers handlers that are qualified to make high-level decisions, and the low-level code implements the low-level details of how to go about fixing the problem.
 The other issue, is that exceptions are just poorly designed for
 multithreaded and event based applications. If you queue some work to
 be done, you likely won't even know if an exception is thrown. If you
 have a callback / event based approach, you have to do the equivalent
 of returning error codes (something exceptions were designed to
 avoid), except with passing in error codes instead. An example is .NET
 forcing all asynchronous operations to have a Begin____ and an End____
 with the End____ rethrowing whatever problems occurred during the
 operation (even if they were easily solveable if the caller simply had
 immediate access to handle them).
I've thought about this too, though I don't currently know what's the right way to implement it. For an event-based program, Conditions aren't good enough, because high-level handlers are deregistered once the registering function's scope exits. This is to prevent unrelated functions from polluting each other's handler stack, with handlers from one function catching stuff it shouldn't from other code. But for an event-based program, you *need* persistent problem handlers. The question is how to prevent persistent handlers from getting problem notifications that they weren't designed to handle. One idea, which I haven't really thought through yet, is to introduce the concept of Domains. A Domain represents a particular operation carried out by some sequence of events and their respective callbacks. For instance, in a network app, a "Login" operation involves calling a Login function in the communications layer, with a callback to notify when the login process is completed. The Login function in turn calls the Connection layer to connect to the remote server, with a callback to inform the Login function when the server is connected. This callback in turn calls the SendMsg function to initiate the authentication process, with its respective callback, etc.. The sum total of all these components: the Login function, its callback, the Connection layer, its callback, etc.., comprise the Login domain. Domains may be nested; for example, the Connection layer may call the Socket layer and the Packet layer, all of which have their own set of callbacks. This "initiate connection" process constitutes a subdomain of the Login domain, and the "send packet" process constitutes a subdomain of the "initiate connection" domain. Each problem handler is then associated with a particular domain (and its subdomains). Whenever a problem occurs, the problem handling system identifies in which domain the problem happened, and searches up the domain hierarchy (starting from the lowest-level domain, like "send packet", up to "initiate connection" up to "Login") for problem handlers. The problem handler then can take the necessary actions to correct the problem. The Condition system can (and probably should) be adapted to this model too. For example if the "initiate connection" domain gets a timeout error from the packet layer, the best place to attempt retry is right there in the Connection layer. However, that's not the best place to decide whether or not to retry; it may be the case that a higher-level handler (i.e. handler higher up the domain hierarchy) is in a better position to make this decision. So somehow they need to be able to cooperate with each other. The details of how this can work still need to be worked out.
 [...]
 (Unfortunately, unless you have a global problem handler for the
 entire transfer operation, this still suffers from the second issue
 about how those exceptions do not get carried up to the caller that
 starte the operation.)
I think with the concept of Domains, we can solve this. Problems that are found in lower level domains bubble up the domain hierarchy until they find a handler that can deal with them. Eventually if they are unhandled, they get to the top level, which is the caller that started the operation. But I haven't worked out how to actually implement such a system.
 The transfer approach might not be the greatest example, but it
 demonstrates something that (IMO) Exceptions are misused for, which
 affects the design of Exceptions themselves as it attempts to hammer a
 solution they're not great for in with them.
The transfer example is a pretty good use case for how to propagate errors in an event-based system. It's definitely worth investigating, although it's probably beyond the scope of the Conditions system. T -- It's amazing how careful choice of punctuation can leave you hanging:
Feb 22 2012
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
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
Feb 21 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, February 21, 2012 00:15:48 H. S. Teoh wrote:
 TRANSITIVITY
I still contend that this useless, because you need to know what went wrong to know whether you actually want to retry anything. And just because the particular operation that threw could be retried again doesn't mean that the code that catches the exception can retry the function that _it_ called which resulted in an exception somewhere down the call stack. And often, you don't even know which function it was that was called within the try block. So, I don't see how transivity really matters. If you know what what wrong - which the type of the exception will tell you - then _that_ is what helps your code make a useful decision as to what to do, not a transivity property. So, yes. A particular exception type is generally transitive or not, but you know that by the nature of the type and the problem that it represents. I don't see how focusing on transivity is useful.
 The try-catch mechanism is not adequate to implement all the recovery
 actions described above. As I've said before when discussing what I
 called the Lispian model, some of these recovery actions need to happen
 *in the context where the exception was thrown*. Once the stack unwinds,
 it may not be possible to recover anymore, because the execution context
 of the original code is gone.
 This is where the Lispian model really shines. To summarize:
try-catch and Exceptions are built into the language. Exceptions are part of not only the standard library but the runtime. They're not perfect, but they _work_. I really think that this whole thing is being way overthought and blown out of proportion. Using another recovery mechanism in your programs (built on top of exceptions or otherwise) is fine, but I really don't see a need to seriously alter how error handling is done in the language as a whole. We're _way_ past the point where it makes sense to completely redesign how exceptions work. And I _really_ don't think that it's a good idea to change anything major about how error handling is done in the standard library without a lot of real world experience with whatever you want to replace it with. And we don't have that. So, I see no problem with you experimenting, but I don't think that we should be drastically changing how the standard library functions with regards to exceptions. We would definitely gain something by cleaning up how they're organized, and maybe adding additional capabilites to improve their printing abilities like Andrei wants to do would be of some value, but we don't need a complete redesign, just some tweaks. - Jonathan M Davis
Feb 21 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 02:40:30PM -0500, Jonathan M Davis wrote:
 On Tuesday, February 21, 2012 00:15:48 H. S. Teoh wrote:
 TRANSITIVITY
I still contend that this useless, because you need to know what went wrong to know whether you actually want to retry anything. And just because the particular operation that threw could be retried again doesn't mean that the code that catches the exception can retry the function that _it_ called which resulted in an exception somewhere down the call stack. And often, you don't even know which function it was that was called within the try block. So, I don't see how transivity really matters. If you know what what wrong - which the type of the exception will tell you - then _that_ is what helps your code make a useful decision as to what to do, not a transivity property.
The point of my little exercise was not to try to solve *every* case of exception handling, but to isolate those cases for which generic handling *does* make sense. I don't pretend that my categories cover *every* case. They obviously don't, as you point out. For those cases where it doesn't make sense, you still catch the specific exception and do your specific recovery, as before. What I'm trying to do is to evaluate the possibility/feasibility of an error recovery system where you *don't* have to know what the specific error is, in order to do something useful with it. What are the errors that *can* be recovered this way, if there are such errors. This is why transitivity is useful, because it allows you to not have to worry about what the specific problem is, and yet still be able to do something meaningful, because the problem doesn't change in nature as you move up the call stack. Obviously, non-transitive errors have to be handled on a case-by-case basis; I'm not negating that at all. The importance of transitivity to generic handling can be seen as follows: Suppose X calls Y and Y calls Z. Z encounters a problem of some sort, let's say for instance it's an input error. So from Y's point of view, the problem can be corrected if it passes different input to Z. But from X's point of view, this is not necessarily true: X's input to Y may have nothing to do with Y's input to Z. So trying to generically handle Z's problem at X's level makes no sense. The problem is not transitive, so no generic handling is possible. You have to catch by type and recover by type. End of story. But suppose Z encounters a transitive problem. Say for instance it's a component failure: one of the functions that Z calls has failed. Well, by extension, that also means Z itself has failed. Now, from Y's point of view, it can attempt to recover by calling W in lieu of Z, if there exists a W that performs an equivalent function to Z. But since the problem is transitive, we can go up to X's level. From X's point of view, it knows nothing about Z, but it *does* know that Y has encountered a component failure (since component failure is transitive). Viewed from X's perspective, Y is the problem (it doesn't know about Z). So if there's an alternative to Y that performs an equivalent function, X can call that in lieu of Y. So you see, even though X has absolute no idea what kind of problem Z encountered, or even that such a thing as Z exists, it *can* make a meaningful effort to recover from the problem that Z encountered. That's what I'm trying to get at: generic handling. Now, to avoid misunderstandings, I'm *not* saying that at every level of the call stack there needs to be a recovery mechanism, such that Y has an alternative to Z and X has an alternative to Y, and so on, all the way up the stack. That would be foolish, since you'll be wasting time writing alternative versions of everything. How this approach benefits real-life programs is that you can insert recovery mechanisms at strategic points in the call chain, so that when problems occur lower down the stack, you can handle it at those points without needing to unwind the stack all the way to the top. Obviously, the try-catch mechanism already does this; but the difference is that those strategic points may not be high enough up the call chain to be able to make a decision about *which* recovery approach to take. They know how to implement the recovery, but they don't know if they should try, or just give up. So this is here is where the high-level delegates come in. *They* know how to decide whether to attempt recovery or just give up, but they *don't* know how to implement recovery, because the lower-level code is a black box to them. By tying the two together via the "Lispian system", you have the possibility of making decisions high up the call stack, and yet still be able to effect low-level recovery strategies. To go back to the earlier example: if Z fails, then from X's point of view Y has also failed, since it doesn't know what Z is. However, if X registers a component failure handler and then calls Y, and Y is capable of switching to W instead of Z, then when Z fails, X's delegate gets to say "Oh, we have a component failure somewhere in Y. Let's try to replace that component" -- even though X has no idea how to do this, nor even what that failed component was. But Y does, so after X's delegate says "try to replace the failed component", Y swaps in W, and continues doing what it was supposed to. If you still doubt the usefulness of such a system, consider this: X implements a complex numerical computation, among the many operations of which includes solving a linear system, which is done by Y. To solve the system, Y at some point calls Z, which, say, inverts a matrix. But due to the particular algorithm that Z uses for matrix inversion, it runs into a numerical overflow, so it throws an error. Y catches the error, and informs X's delegate, "hey, I've encountered a problem while solving your linear system, but I know of another algorithm that I can try that may give you the result you want; should I proceed?". X's delegate then can decide to give up - "No, this is a real-time system, and there's not enough time left to attempt another calculation", or it can decide to go ahead "I want that system solved, no matter what!". If X's delegate decides to continue, Y will swap in an alternative way of inverting the matrix that avoids overflow, and thus be able to finish its computation. If there was no way for Y to talk to X (via X's delegate) while control is still in the context of Y, then Y would be forced to make a decision it's not qualified to make (it may be just a generic algebraic package, it doesn't know it's running in a real-time environment or not), or it has to unwind the stack back to X, at which point it's too late for X to salvage the situation (it has to call Y all over again if it wants to re-attempt the computation -- and even then there may not be a way for X to tell Y to replace Z with W, because X doesn't even know what Z and W are).
 So, yes. A particular exception type is generally transitive or not,
 but you know that by the nature of the type and the problem that it
 represents. I don't see how focusing on transivity is useful.
[...] It's funny, earlier in this thread I argued the same thing about Andrei's is_transient proposal. But I've since come to realize what Andrei was trying to get at: yes we know how to deal with specific errors in a specific way, that's given; but *are* there cases for which we don't *need* to know the specifics? Can we take advantage of generic handling for those cases, so as to reduce (not eliminate!) the amount of specific handling we need to do? I think that angle is worth pursuing, even if it's to conclude at the end that, no, generic handling is not possible/feasible. OK, lesson learned. We move on. But if there are cases for which generic handling *is* possible, and if we can implement powerful error recovery schemes in a generic way, then we'd be foolish not to take advantage of it. [...]
 So, I see no problem with you experimenting, but I don't think that we
 should be drastically changing how the standard library functions with
 regards to exceptions. We would definitely gain something by cleaning
 up how they're organized, and maybe adding additional capabilites to
 improve their printing abilities like Andrei wants to do would be of
 some value, but we don't need a complete redesign, just some tweaks.
[...] Currently deadalnix & myself are experimenting with ways of implementing the "Lispian system" as a separate Phobos module that offers enhanced exception handling, on top of the usual try/catch mechanism. I'm not proposing to replace anything, at least not in the short term. Should this system prove worthwhile in the long run, *then* we can start thinking about whether we can make extensive use of it in the library code, or reworking exception handling at the language level. T -- Two wrongs don't make a right; but three rights do make a left...
Feb 21 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Feb 21, 2012 at 07:15:29PM +0100, Artur Skawina wrote:
 On 02/21/12 17:56, H. S. Teoh wrote:
[...]
 I don't think something like this can reliably work - handling unknown
 error conditions in code not expecting them is not a good idea.
I'm not proposing we do this for *every* error. Only for those for which it makes sense. And the code *does* have to expect what it's handling: a particular category of problems, a category for which it makes sense to handle recovery in a generic way. I'm *not* proposing this: try {...} catch(Exception e) { // catch everything // generic code that magically handles everything } If we could do that, this thread would've been over after 3 posts. :)
 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.
Which is what I'm trying to do with the categorization. Internal handling, of course, can and should be done in those cases where it's crystal clear how one should proceed. I'm addressing the case where it can't be handled internally (else why would it throw an exception / raise a condition in the first place?)
 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.
The delegate is the predefined policy. Except that it's much more flexible than a global on/off setting. It has access to the registering function's scope, based on which it can make decisions based on the large-scale state of the program, which the low-level code doesn't (and shouldn't) know about. Multiple delegates can be registered for the same condition, and the most relevant one (the closest to the problem locus) takes priority. Each delegate can implement its own policy on how to deal with problems. The higher-level delegates only see problems that the lower-level delegates decide to pass on. In this way, lower-level delegates filter out stuff that they can handle on their own, only deferring to the higher-level delegates when they don't know how to proceed. Your higher-level handler won't be handling all sorts of trivial low-level problems, only the major issues that the lower-level handlers can't already handle.
 If retrying occurs often during normal operation then throwing an
 exception every time is probably not the best way to handle this.
In fact, some experimental code that deadalnix & I are playing with currently implements retry without try/catch, except when we need to unwind the stack. So no performance hit there.
 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...
[...] We're currently playing with using templates to generate the boilerplate stuff, so all you need to do is to write the template name and wrap a block around the code to be retried. I think that's acceptable overhead -- it's no worse than writing "try ... catch", and arguably far more powerful. T -- Trying to define yourself is like trying to bite your own teeth. -- Alan Watts
Feb 21 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday, February 23, 2012 07:47:55 H. S. Teoh wrote:
 On Thu, Feb 23, 2012 at 02:57:43AM -0800, Jonathan M Davis wrote:
 [...]
 
 DbC tends to work better with internal stuff where you control both
 the caller and the callee, whereas defensive programming works better
 with public APIs.  But regardless, which is best to use depends on the
 situtation and what you're goals are.
[...] The way I understand it, DbC is used for ensuring *program* correctness (ensure that program logic does not get itself into a bad state); defensive programming is for sanitizing *user input* (ensure that no matter what the user does, the program doesn't get into a bad state). That's why DbC is compiled out in release mode -- the assumption is that you have thoroughly tested your program logic and verified there are no logic problems. Input sanitizing is never compiled out, because you never know what users will do, so you always have to check. The two do somewhat overlap, of course. For example, failing to sanitize user input may eventually lead to passing invalid arguments to an internal function.
Exactly. But where things tend to blur is the concept of "user input." For instance, if you're using a 3rd party library, should it be asserting on the arguments that you pass it? Unless you compile it in non-release mode, it obviously won't, which could be an argument for using exceptions, but regardless of that, from the library's perspective, you're a user. If it used DbC, it would be putting assertions it in its own code to test _your_ code. And since you're a user, it arguably should use exceptions to make sure that the arguments that it gets are correct. So, it all tends to get blurry, and the best decision varies from situation to situation. It's also part of what makes the concept of assertion vs exception harder for some folks (and as nice as enforce may be it, it blurs the line even further for many people IMHO, since it then makes exceptions the same as assertions syntactically). Arguably, the best thing would be if there was a way for the caller to indicate whether it wanted the callee to have DbC enable and possibly even indicate whether it wanted the callee to use DbC or defensive programming. But there's no way to do that in D, and I'm not sure that it could even be done with the C linking model - at least, there's no way to it without templatizing everything and giving an argument to the template indicating what you want, which obviously isn't a good solution (and won't work at all in the case of virtual functions, since they can't be templatized). - Jonathan M Davis
Feb 23 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Feb 23, 2012 at 12:07:40PM -0800, Jonathan M Davis wrote:
 On Thursday, February 23, 2012 07:47:55 H. S. Teoh wrote:
[...]
 The way I understand it, DbC is used for ensuring *program*
 correctness (ensure that program logic does not get itself into a
 bad state); defensive programming is for sanitizing *user input*
 (ensure that no matter what the user does, the program doesn't get
 into a bad state).
 
 That's why DbC is compiled out in release mode -- the assumption is
 that you have thoroughly tested your program logic and verified
 there are no logic problems. Input sanitizing is never compiled out,
 because you never know what users will do, so you always have to
 check.
 
 The two do somewhat overlap, of course. For example, failing to
 sanitize user input may eventually lead to passing invalid arguments
 to an internal function.
Exactly. But where things tend to blur is the concept of "user input." For instance, if you're using a 3rd party library, should it be asserting on the arguments that you pass it?
In my book, a linked library shares equal status with the "main program", therefore the definition of "user input" still sits at the internal-to-program and external boundary.
 Unless you compile it in non-release mode, it obviously won't, which
 could be an argument for using exceptions, but regardless of that,
 from the library's perspective, you're a user.
I believe the traditional way is to ship a debug or devel version of the library which is linked when you compile in non-release mode (e.g., libc-dbg), and then in release mode the release mode library is linked (libc proper). That way DbC will be enforced in non-release mode by the library, and suppressed in the release mode binary. If libraries only ship in release mode, then that sorta defeats the point of DbC, which is to ensure program correctness before release and not get in the way after. Now the library has to be paranoid and always sanitize all inputs.
 If it used DbC, it would be putting assertions it in its own code to
 test _your_ code.  And since you're a user, it arguably should use
 exceptions to make sure that the arguments that it gets are correct.
No, the library should ship a development version with all contracts compiled-in, so that contract violations will be enforced during development & testing. Sadly, this isn't often done in practice, which leads to the sad situation where the program/library boundary has a lot of overhead, because the library must be paranoid and always sanitize all inputs no matter what. [...]
 Arguably, the best thing would be if there was a way for the caller to
 indicate whether it wanted the callee to have DbC enable and possibly
 even indicate whether it wanted the callee to use DbC or defensive
 programming. But there's no way to do that in D, and I'm not sure that
 it could even be done with the C linking model - at least, there's no
 way to it without templatizing everything and giving an argument to
 the template indicating what you want, which obviously isn't a good
 solution (and won't work at all in the case of virtual functions,
 since they can't be templatized).
[...] No need to templatize anything, just ship two versions of the library, one with DbC compiled in, one without. Let the user decide which one to link in. T -- Never trust an operating system you don't have source for! -- Martin Schulze
Feb 23 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote:
 On Thu, Feb 23, 2012 at 12:07:40PM -0800, Jonathan M Davis wrote:
 In my book, a linked library shares equal status with the "main
 program", therefore the definition of "user input" still sits at the
 internal-to-program and external boundary.
Yes, "in your book." Some people will agree with you and some won't. It really depends on what the code is doing though IMHO. In some cases, one is better and in some cases, the other is better. But it _is_ important to remember that there's a big difference between linking against a library over which you have control and a 3rd party library. And there are times when it just plain makes more sense to have a function which throws an exception on bad input regardless of whether it's an "internal" function or not. For instance, if you want to convert a string to something else (e.g. with SysTime's fromISOExtString or even just with std.conv.to), you need to actually verify that the string has a value which can be correctly converted. It's actually cheaper to have the function doing the conversion do the checking rather than have another function do a check first, and then have the converting function not check (save perhaps for an assertion outside of release mode), because then you'll be processing the string _twice_. This is _not_ a cut-and-dried issue. Sometimes DbC makes more sense, and sometimes defensive programming does. You pick the one that works best for a given situation. The whole thing is a gray area, and you're not going to get a consensus that a library should always use DbC on its functions or that it should always use defensive programming.
 Arguably, the best thing would be if there was a way for the caller to
 indicate whether it wanted the callee to have DbC enable and possibly
 even indicate whether it wanted the callee to use DbC or defensive
 programming. But there's no way to do that in D, and I'm not sure that
 it could even be done with the C linking model - at least, there's no
 way to it without templatizing everything and giving an argument to
 the template indicating what you want, which obviously isn't a good
 solution (and won't work at all in the case of virtual functions,
 since they can't be templatized).
 No need to templatize anything, just ship two versions of the library,
 one with DbC compiled in, one without. Let the user decide which one to
 link in.
There _is_ a need to do that if the caller wants to control whether an assertion or an exception is used. There's also a need if you want to enable it in some places and not in others. However, the reality of the matter is that using a debug version of a library is as close as you're likely to get. And I'm certainly not arguing that templatizing functions in this manner would be a good idea. I'm just pointing aut that there are issues with how DbC is currently implemented. And the primary problem with how DbC is implemented is the fact that its assertions test the caller, not the callee, but the assertions end up in the callee. So, the assertions are separated from the code that they're actually testing. It's the best that we can do at this point, but it does result in a weird situation where you end up using assertions to test _other_ people's code rather than your own. - Jonathan M Davis
Feb 23 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Feb 23, 2012 at 07:06:02PM -0500, Jonathan M Davis wrote:
 On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote:
 On Thu, Feb 23, 2012 at 12:07:40PM -0800, Jonathan M Davis wrote:
 In my book, a linked library shares equal status with the "main
 program", therefore the definition of "user input" still sits at the
 internal-to-program and external boundary.
Yes, "in your book." Some people will agree with you and some won't. It really depends on what the code is doing though IMHO. In some cases, one is better and in some cases, the other is better. But it _is_ important to remember that there's a big difference between linking against a library over which you have control and a 3rd party library. And there are times when it just plain makes more sense to have a function which throws an exception on bad input regardless of whether it's an "internal" function or not. For instance, if you want to convert a string to something else (e.g. with SysTime's fromISOExtString or even just with std.conv.to), you need to actually verify that the string has a value which can be correctly converted. It's actually cheaper to have the function doing the conversion do the checking rather than have another function do a check first, and then have the converting function not check (save perhaps for an assertion outside of release mode), because then you'll be processing the string _twice_.
I guess this is a judgment call. Personally, I would consider arguments to string conversion functions to be "user input" even though, technically speaking, you could just be passing a byte array literal to it, in which case it's just a case of bad input parameters.
 This is _not_ a cut-and-dried issue. Sometimes DbC makes more sense,
 and sometimes defensive programming does. You pick the one that works
 best for a given situation.
 
 The whole thing is a gray area, and you're not going to get a
 consensus that a library should always use DbC on its functions or
 that it should always use defensive programming.
I wasn't trying to say that library code should always use DbC and application code should always use defensive programming. I'm saying that if it makes sense for a function to use DbC (or vice versa) then it should use DbC regardless of whether it's in a library or not. If I were to write a string conversion function, for example, I wouldn't use contracts to enforce the right encoding, regardless of whether it's in a library or in application code. I would use exceptions, simply because that's what makes sense in this case. Just because something is in the library shouldn't change whether DbC or defensive programming is used. It's the semantics that matter, not whether it's in a library. [...]
 No need to templatize anything, just ship two versions of the
 library, one with DbC compiled in, one without. Let the user decide
 which one to link in.
There _is_ a need to do that if the caller wants to control whether an assertion or an exception is used. There's also a need if you want to enable it in some places and not in others. However, the reality of the matter is that using a debug version of a library is as close as you're likely to get. And I'm certainly not arguing that templatizing functions in this manner would be a good idea. I'm just pointing aut that there are issues with how DbC is currently implemented.
[...] Actually, I wonder if it makes sense for the compiler to insert in-contract code in the *caller* instead of the callee. Conceptually speaking, an in-contract means "you have to fulfill these conditions before calling this function". So why not put the check in the caller? Similarly, an out-contract means "this function's return value will satisfy these conditions" - so let the caller verify that this is true. Semantically it amounts to the same thing, but this gives us more flexibility: the library doesn't have to be compiled with contracts on/off, the contracts are in the library API, and the user tells the compiler whether or not to wrap the contract code around each call to the library. (Yes this bloats the code everywhere a DbC function is called, but this is supposed to be done in non-release builds only anyway, so I don't think that matters so much.) T -- Тише едешь, дальше будешь.
Feb 24 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Feb 24, 2012 at 07:57:13AM -0800, H. S. Teoh wrote:
 On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote:
 In my book, a linked library shares equal status with the "main
 program", therefore the definition of "user input" still sits at
 the internal-to-program and external boundary.
[...]
 I wasn't trying to say that library code should always use DbC and
 application code should always use defensive programming. I'm saying
 that if it makes sense for a function to use DbC (or vice versa) then
 it should use DbC regardless of whether it's in a library or not.
[...] Argh, I just realized that my first post was so poorly worded it made no sense at all. My second post was what I meant to say. :) What I was trying to express in the first post was that "user input" comes from a source external to the program, whether from a user typing at the keyboard, or from a file or network resource, and this data traverses program code paths until eventually they are converted into the internal form the program uses for further processing. Input sanitization should be done along this code path until the input is processed into program-internal form, at which point, DbC begins to take effect, the assumption being that after preprocessing by the input sanitization code, all data should be valid, and if not, it's a failure of the input processing code and represents a logic flaw in the program, therefore an assertion should be thrown. T -- Those who don't understand Unix are condemned to reinvent it, poorly.
Feb 24 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, February 24, 2012 07:57:13 H. S. Teoh wrote:
 Actually, I wonder if it makes sense for the compiler to insert
 in-contract code in the *caller* instead of the callee. Conceptually
 speaking, an in-contract means "you have to fulfill these conditions
 before calling this function". So why not put the check in the caller?
 
 Similarly, an out-contract means "this function's return value will
 satisfy these conditions" - so let the caller verify that this is true.
 
 Semantically it amounts to the same thing, but this gives us more
 flexibility: the library doesn't have to be compiled with contracts
 on/off, the contracts are in the library API, and the user tells the
 compiler whether or not to wrap the contract code around each call to
 the library.
 
 (Yes this bloats the code everywhere a DbC function is called, but this
 is supposed to be done in non-release builds only anyway, so I don't
 think that matters so much.)
It wouldn't work unless the source were available, because otherwise you just have the function signature. So, the result would be inconsistent, and in the case of non-templated functions built against a non-release version of the callee, you'd end up having the checks twice, because the callee would have to have them regardless (since it's compiled separately, and not every function calling it would necessarily have its source). For templated functions, it already depends on the caller whether the assertions are enabled, because it's instantiated when the module with the caller in it is built. It's an interesting idea though. - Jonathan M Davis
Feb 24 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, February 24, 2012 08:27:44 H. S. Teoh wrote:
 On Fri, Feb 24, 2012 at 07:57:13AM -0800, H. S. Teoh wrote:
 On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote:
 In my book, a linked library shares equal status with the "main
 program", therefore the definition of "user input" still sits at
 the internal-to-program and external boundary.
[...]
 I wasn't trying to say that library code should always use DbC and
 application code should always use defensive programming. I'm saying
 that if it makes sense for a function to use DbC (or vice versa) then
 it should use DbC regardless of whether it's in a library or not.
[...] Argh, I just realized that my first post was so poorly worded it made no sense at all. My second post was what I meant to say. :) What I was trying to express in the first post was that "user input" comes from a source external to the program, whether from a user typing at the keyboard, or from a file or network resource, and this data traverses program code paths until eventually they are converted into the internal form the program uses for further processing. Input sanitization should be done along this code path until the input is processed into program-internal form, at which point, DbC begins to take effect, the assumption being that after preprocessing by the input sanitization code, all data should be valid, and if not, it's a failure of the input processing code and represents a logic flaw in the program, therefore an assertion should be thrown.
Yes. In general, that's the core difference between assertions and exceptions. If an assertion fails, it's a bug in the code, whereas if an exception is thrown, then it may or may not be caused by a program bug (and is frequently caused by interacting with I/O - be it directly or indirectly). But that does require a judgement call sometimes as to which approach is better in a particular situation, and if you're being utterly paranoid (which some programs probably need to be but most don't), then you could end up using exceptions where you'd normally use assertions simply because you want to _guarantee_ that the check is always done. But hopefully, that sort of thing would be kept to a minimum. Regarldess, at the core, assertions are for verifying program correctness, and exceptions are for reporting error conditions caused by bad stuff happening during the normal operation of the program. - Jonathan M Davis
Feb 24 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Feb 24, 2012 at 01:46:56PM -0500, Jonathan M Davis wrote:
 On Friday, February 24, 2012 07:57:13 H. S. Teoh wrote:
 Actually, I wonder if it makes sense for the compiler to insert
 in-contract code in the *caller* instead of the callee. Conceptually
 speaking, an in-contract means "you have to fulfill these conditions
 before calling this function". So why not put the check in the
 caller?
 
 Similarly, an out-contract means "this function's return value will
 satisfy these conditions" - so let the caller verify that this is
 true.
 
 Semantically it amounts to the same thing, but this gives us more
 flexibility: the library doesn't have to be compiled with contracts
 on/off, the contracts are in the library API, and the user tells the
 compiler whether or not to wrap the contract code around each call
 to the library.
 
 (Yes this bloats the code everywhere a DbC function is called, but
 this is supposed to be done in non-release builds only anyway, so I
 don't think that matters so much.)
It wouldn't work unless the source were available, because otherwise you just have the function signature.
[...] In my mind, contract code belongs in the function signature, because they document how the function expects to be called, and what it guarantees in return. It doesn't seem to make sense to me that contracts would be hidden from the user of the library. Sorta defeats the purpose, since how is the user supposed to know what the function expects? Rely on documentation, perhaps, but docs aren't as reliable as actual contract code. T -- Без труда не выловишь и рыбку из пруда.
Feb 24 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/24/12 1:13 PM, H. S. Teoh wrote:
 In my mind, contract code belongs in the function signature, because
 they document how the function expects to be called, and what it
 guarantees in return. It doesn't seem to make sense to me that contracts
 would be hidden from the user of the library. Sorta defeats the purpose,
 since how is the user supposed to know what the function expects? Rely
 on documentation, perhaps, but docs aren't as reliable as actual
 contract code.
Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei
Feb 24 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/24/2012 08:14 PM, Andrei Alexandrescu wrote:
 On 2/24/12 1:13 PM, H. S. Teoh wrote:
 In my mind, contract code belongs in the function signature, because
 they document how the function expects to be called, and what it
 guarantees in return. It doesn't seem to make sense to me that contracts
 would be hidden from the user of the library. Sorta defeats the purpose,
 since how is the user supposed to know what the function expects? Rely
 on documentation, perhaps, but docs aren't as reliable as actual
 contract code.
Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei
'In' contracts are hardly usable at all at the moment, because they are not inherited by default.
Feb 25 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/25/12 7:19 AM, Timon Gehr wrote:
 On 02/24/2012 08:14 PM, Andrei Alexandrescu wrote:
 On 2/24/12 1:13 PM, H. S. Teoh wrote:
 In my mind, contract code belongs in the function signature, because
 they document how the function expects to be called, and what it
 guarantees in return. It doesn't seem to make sense to me that contracts
 would be hidden from the user of the library. Sorta defeats the purpose,
 since how is the user supposed to know what the function expects? Rely
 on documentation, perhaps, but docs aren't as reliable as actual
 contract code.
Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei
'In' contracts are hardly usable at all at the moment, because they are not inherited by default.
I thought that was fixed. Is there a bug report on it? Thanks! Andrei
Feb 25 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 02/25/2012 09:18 PM, Andrei Alexandrescu wrote:
 On 2/25/12 7:19 AM, Timon Gehr wrote:
 On 02/24/2012 08:14 PM, Andrei Alexandrescu wrote:
 On 2/24/12 1:13 PM, H. S. Teoh wrote:
 In my mind, contract code belongs in the function signature, because
 they document how the function expects to be called, and what it
 guarantees in return. It doesn't seem to make sense to me that
 contracts
 would be hidden from the user of the library. Sorta defeats the
 purpose,
 since how is the user supposed to know what the function expects? Rely
 on documentation, perhaps, but docs aren't as reliable as actual
 contract code.
Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei
'In' contracts are hardly usable at all at the moment, because they are not inherited by default.
I thought that was fixed. Is there a bug report on it? Thanks! Andrei
This is the bug report: http://d.puremagic.com/issues/show_bug.cgi?id=6856 ( Furthermore, there is an issue with the design: http://d.puremagic.com/issues/show_bug.cgi?id=7584 )
Feb 25 2012
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sat, 18 Feb 2012 13:52:05 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 There's a discussion that started in a pull request:

 https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca

 Let's come up with a good doctrine for exception defining and handling  
 in Phobos. From experience I humbly submit that catching by type is most  
 of the time useless.
OK, so after reading about 100 or so of these messages, I stopped. Sorry if this has been said before, but here is my take: many many times when dealing with exceptions I hate to do this: class ExceptionTypeA : Exception {...} class ExceptionTypeB : Exception {...} try { } catch(ExceptionTypeA ex) { // code } catch(ExceptionTypeB ex) { // same f'ing code } I know, I could do this: catch(Exception e) { if(cast(ExceptionTypeA)e || cast(ExceptionTypeB)e) { // code } else throw e; } But that sucks, and as others have pointed out, it kills the stack trace. So I love the idea that others have specified to have a 'template constraint' type piece for catch. IMO, it should not be a template, but a runtime check. i.e., I don't think we need to templatize the catch, we just need to add extra code to the 'should we catch' check that currently consists of 'does the type match'. This would be a huge improvement over existing exception catching techniques. So the above would become: catch(Exception e) if(e is ExceptionTypeA or ExceptionTypeB) // not sure of the exact syntax, maybe the if statement I used above? I agree the constraints should be pure and nothrow. On to my second point. One of the issues I have with Java is that exceptions are *overused*. For example, EOF should not be an exception, most files have ends, it's not a very exceptional situation. If there is an intuitive way to use an existing return value to convey an error rather than an exception, I'd prefer the return value. There is a runtime cost just for setting up a try/catch block, even if no exceptions are thrown. -Steve
Feb 24 2012
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Feb 24, 2012 at 04:38:31PM -0500, Steven Schveighoffer wrote:
[...]
 On to my second point.  One of the issues I have with Java is that
 exceptions are *overused*.  For example, EOF should not be an
 exception, most files have ends, it's not a very exceptional
 situation.  If there is an intuitive way to use an existing return
 value to convey an error rather than an exception, I'd prefer the
 return value.  There is a runtime cost just for setting up a try/catch
 block, even if no exceptions are thrown.
[...] Yeah, EOF shouldn't be an exception. It should simply be a terminal state that file-reading code gets into, whereupon it will simply return some special value (e.g., dchar.init, or empty array) upon further reading. Preferably also have a .eof or .empty method or something equivalent that indicates EOF, so you can write "while (!obj.eof) { ... }". Using an exception for EOF is abuse, IMO. Another beef I have about EOFs is that I've seen code where EOF is returned only once, and then you're not supposed to call that code ever again after that because it will crash. I think that's really stupid. If you hit EOF, attempting to read further should simply return EOF again without changing the state of the system any further. Multiple attempts to read past EOF should still return EOF. (One would think this was obvious, but obviously it's not very obvious...) T -- To err is human; to forgive is not our policy. -- Samuel Adler
Feb 24 2012
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, February 24, 2012 16:18:59 H. S. Teoh wrote:
 On Fri, Feb 24, 2012 at 06:48:47PM -0500, Jonathan M Davis wrote:
 1. Being able to annotate catch blocks in some manner to enable them
 to catch multiple, specific exceptions - either by using some kind of
 condition
 
 catch(Ex1 e) if(condition)
Even though I was (one of) the one(s) who proposed this syntax, I think a better syntax would be something like: catch(Ex1 e: condition) but that's just bikeshedding.
 which, as you point out, would have to be done at runtime, or by doing
 something similar to what Java 7 is doing and make it so that multiple
 exceptions can be caught with the same block
 
 catch(Ex1, Ex2, Ex3 e)
 
 and e ends up being the most derived type that is common to them.
This can also be done by catch conditions by using is-expressions or derived class casts, though it would be more verbose. We may not need to implement both.
You do run into the who rethrowing issue with that though, if you catch the wrong type. It also can force you to combine disparate catch blocks, since you caught the common type, and you're forced to use if-else statements to separate out the various cases instead of letting catch take care of it. All in all, it would just be _way_ cleaner to have a way to give a list of exception types that you want that catch to catch. Also, I think that having a syntax for simply giving a list of exceptions that an exception block catches is plenty without needing the whole condition thing. The condition thing just seems like overkill. But maybe someone has a valid use case where giving a specific list of exceptions doesn't cut it.
 I don't think D's job is to stop people from doing stupid things.
 Otherwise we might as well go back to Pascal or something. :)
Indeed. What we need is a setup which enables (and hopefully encourages) people to do the right thing. We can't stop people from being stupid. For instance, much as Java generally tries to protect you from your own stupidity, the fact that it has very good exception hierarchy doesn't stop people (and in some cases the developers of its standard library) from doing stupid things with exceptions. And D, which tries to protect you without preventing you from being stupid (unlike Java, which generally tries to prevent you from doing something stupid), certainly isn't going to stop you from being stupid. - Jonathan M Davis
Feb 24 2012